@lemonadejs/dropdown 5.2.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/README.md CHANGED
@@ -1,172 +1,173 @@
1
- # JavaScript Dropdown
2
-
3
- [The Dropdown Documentation](https://lemonadejs.com/docs/plugins/dropdown)
4
-
5
- Compatible with Vanilla JavaScript, LemonadeJS, React, VueJS or Angular.
6
-
7
- The LemonadeJS Dropdown is a versatile solution for efficient option management. It is a framework-agnostic JavaScript plugin designed for seamless integration with Vue, React, and Angular. This feature-rich dropdown incorporates autocomplete for swift selections, grouping for organized options, and lazy loading for optimized performance, contributing to a smooth and improved user experience.
8
-
9
- ## Features
10
-
11
- - Lightweight: The JavaScript Dropdown is only about 2 KBytes;
12
- - Integration: It can be used as a standalone library or integrated with any modern framework;
13
-
14
- ## Getting Started
15
-
16
- You can install using NPM or using directly from a CDN.
17
-
18
- ### npm Installation
19
-
20
- To install it in your project using npm, run the following command:
21
-
22
- ```bash
23
- $ npm install @lemonadejs/dropdown
24
- ```
25
-
26
- ### CDN
27
-
28
- To use Dropdown via a CDN, include the following script tags in your HTML file:
29
-
30
- ```html
31
- <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/lemonadejs/dist/lemonade.min.js"></script>
32
- <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@lemonadejs/modal/dist/index.min.js"></script>
33
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lemonadejs/modal/dist/style.min.css" />
34
- <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@lemonadejs/dropdown/dist/index.min.js"></script>
35
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lemonadejs/dropdown/dist/style.min.css" />
36
- ```
37
-
38
- ### Usage
39
-
40
- Quick example with Lemonade
41
-
42
- ```javascript
43
- import Dropdown from '@lemonadejs/dropdown';
44
- import '@lemonadejs/dropdown/dist/style.css'
45
- import '@lemonadejs/modal/dist/style.css';
46
-
47
- export default function App() {
48
- const self = this;
49
-
50
- self.data = [
51
- { text: "Red", value: 1 },
52
- { text: "Blue", value: 2 },
53
- { text: "Green", value: 3 },
54
- { text: "Yellow", value: 4 },
55
- { text: "Purple", value: 5 },
56
- { text: "Gray", value: 6 },
57
- ]
58
-
59
- return `<div>
60
- <Dropdown :data="self.data" :value="1" />
61
- </div>`
62
- }
63
- ```
64
-
65
- Quick example with React
66
-
67
- ```jsx
68
- import React, { useRef } from 'react';
69
- import Dropdown from '@lemonadejs/dropdown/dist/react';
70
- import '@lemonadejs/dropdown/dist/style.css'
71
- import '@lemonadejs/modal/dist/style.css';
72
-
73
- const data = [
74
- { text: "Red", value: 1 },
75
- { text: "Blue", value: 2 },
76
- { text: "Green", value: 3 },
77
- { text: "Yellow", value: 4 },
78
- { text: "Purple", value: 5 },
79
- { text: "Gray", value: 6 },
80
- ]
81
- export default function App() {
82
- const dropdown = useRef();
83
- return (
84
- <div>
85
- <Dropdown data={data} ref={dropdown} value={1} />
86
- </div>);
87
- }
88
- ```
89
-
90
- Quick example with Vue
91
-
92
- ```vue
93
- <template>
94
- <Dropdown :data="data" :value="1" />
95
- </template>
96
-
97
- <script>
98
- import Dropdown from '@lemonadejs/dropdown/dist/vue'
99
- import '@lemonadejs/dropdown/dist/style.css'
100
- import '@lemonadejs/modal/dist/style.css'
101
-
102
- export default {
103
- name: 'App',
104
- components: {
105
- Dropdown
106
- },
107
- data() {
108
- return {
109
- data: [
110
- { text: "Red", value: 1 },
111
- { text: "Blue", value: 2 },
112
- { text: "Green", value: 3 },
113
- { text: "Yellow", value: 4 },
114
- { text: "Purple", value: 5 },
115
- { text: "Gray", value: 6 },
116
- ]
117
- };
118
- },
119
- }
120
- </script>
121
- ```
122
-
123
- ### Settings
124
-
125
- | Attribute | Description |
126
- |------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
127
- | data: Item[] | An array of options to be displayed. Each item should follow the structure defined in the 'Item Details' section. |
128
- | multiple?: boolean | If provided, enables the multiple selection mode, allowing users to select more than one option simultaneously. |
129
- | autocomplete?: boolean | If provided, enables the autocomplete feature, displaying an input field that allows users to filter and quickly find options in the Dropdown. |
130
- | value?: string | Represents the current value or selected option in the Dropdown. |
131
- | type?: string | Specifies the type of display the Dropdown will use. It can be "searchbar" for a search bar interface, "picker" for a selection picker, or "default" for the default dropdown appearance. |
132
- | insert?: boolean | Enables the `insert` feature, allowing users to add a new option directly by typing the title text and clicking on the plus symbol. |
133
- | format?: number | Data format type, usually in the form of { id: name } or { value: text } |
134
- | width?: number | Specifies the width of the dropdown. |
135
- | placeholder?: string | Placeholder text to guide users in the dropdown. |
136
- | url?: string | Specifies the URL for fetching the data. Do not declare the `data` attribute for the url to function properly. |
137
-
138
- ### Item Details
139
-
140
- | Attribute | Description |
141
- |--------------------------|---------------------------------------------------------------------------------------------|
142
- | value?: number or string | Represents the identifier or unique value associated with the option. |
143
- | group?: string | Indicates the group to which the option belongs, allowing for segregation and organization. |
144
- | text?: string | Displays the label or text associated with the option. |
145
- | image?: string | Specifies the image URL to be displayed alongside the option. |
146
- | synonyms?: string[] | Keywords for easier item identification |
147
- | disabled?: boolean | Indicates whether the item is currently disabled |
148
- | color?: string | Specifies the color associated with the item |
149
-
150
- ### Events
151
-
152
- | Event | Trigger |
153
- |-------------------------------------------|----------------------------------------------------------------------------|
154
- | onclose?: () => void | Invoked when the dropdown modal is closed. |
155
- | onbeforeinsert?: (instance, Item) => void | Invoked before an item is added to the options through the insert feature. |
156
- | oninsert?: (instance, Item) => void | Invoked after an item is added to the options through the insert feature. |
157
- | onchange?: (instance, Item) => void | Invoked when the value changes. |
158
- | onload?: (instance, Item) => void | Invoked when appended to the DOM. |
159
- | onsearch?: (instance, Item) => void | Invoked when searching for an item.
160
- |
161
- | onbeforesearch?: (instance, Item) => void | Invoked before initiating a search.
162
- |
163
- | onopen?: (instance) => void | Invoked when the dropdown is opened. |
164
-
165
- ## License
166
-
167
- The [LemonadeJS](https://lemonadejs.net) LemonadeJS Dropdown is released under the MIT.
168
-
169
- ## Other Tools
170
-
171
- - [jSuites](https://jsuites.net/docs)
172
- - [Jspreadsheet Data Grid](https://jspreadsheet.com)
1
+ # JavaScript Dropdown
2
+
3
+ [The Dropdown Documentation](https://lemonadejs.com/docs/plugins/dropdown)
4
+
5
+ Compatible with Vanilla JavaScript, LemonadeJS, React, VueJS or Angular.
6
+
7
+ The LemonadeJS Dropdown is a versatile solution for efficient option management. It is a framework-agnostic JavaScript plugin designed for seamless integration with Vue, React, and Angular. This feature-rich dropdown incorporates autocomplete for swift selections, grouping for organized options, and lazy loading for optimized performance, contributing to a smooth and improved user experience.
8
+
9
+ ## Features
10
+
11
+ - Lightweight: The JavaScript Dropdown is only about 2 KBytes;
12
+ - Integration: It can be used as a standalone library or integrated with any modern framework;
13
+
14
+ ## Getting Started
15
+
16
+ You can install using NPM or using directly from a CDN.
17
+
18
+ ### npm Installation
19
+
20
+ To install it in your project using npm, run the following command:
21
+
22
+ ```bash
23
+ $ npm install @lemonadejs/dropdown
24
+ ```
25
+
26
+ ### CDN
27
+
28
+ To use Dropdown via a CDN, include the following script tags in your HTML file:
29
+
30
+ ```html
31
+ <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/lemonadejs/dist/lemonade.min.js"></script>
32
+ <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@lemonadejs/modal/dist/index.min.js"></script>
33
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lemonadejs/modal/dist/style.min.css" />
34
+ <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@lemonadejs/dropdown/dist/index.min.js"></script>
35
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lemonadejs/dropdown/dist/style.min.css" />
36
+ ```
37
+
38
+ ### Usage
39
+
40
+ Quick example with Lemonade
41
+
42
+ ```javascript
43
+ import Dropdown from '@lemonadejs/dropdown';
44
+ import '@lemonadejs/dropdown/dist/style.css';
45
+ import '@lemonadejs/modal/dist/style.css';
46
+
47
+ export default function App() {
48
+ const self = this;
49
+
50
+ self.data = [
51
+ { text: 'Red', value: 1 },
52
+ { text: 'Blue', value: 2 },
53
+ { text: 'Green', value: 3 },
54
+ { text: 'Yellow', value: 4 },
55
+ { text: 'Purple', value: 5 },
56
+ { text: 'Gray', value: 6 },
57
+ ];
58
+
59
+ return `<div>
60
+ <Dropdown :data="self.data" :value="1" />
61
+ </div>`;
62
+ }
63
+ ```
64
+
65
+ Quick example with React
66
+
67
+ ```jsx
68
+ import React, { useRef } from 'react';
69
+ import Dropdown from '@lemonadejs/dropdown/dist/react';
70
+ import '@lemonadejs/dropdown/dist/style.css';
71
+ import '@lemonadejs/modal/dist/style.css';
72
+
73
+ const data = [
74
+ { text: 'Red', value: 1 },
75
+ { text: 'Blue', value: 2 },
76
+ { text: 'Green', value: 3 },
77
+ { text: 'Yellow', value: 4 },
78
+ { text: 'Purple', value: 5 },
79
+ { text: 'Gray', value: 6 },
80
+ ];
81
+ export default function App() {
82
+ const dropdown = useRef();
83
+ return (
84
+ <div>
85
+ <Dropdown data={data} ref={dropdown} value={1} />
86
+ </div>
87
+ );
88
+ }
89
+ ```
90
+
91
+ Quick example with Vue
92
+
93
+ ```vue
94
+ <template>
95
+ <Dropdown :data="data" :value="1" />
96
+ </template>
97
+
98
+ <script>
99
+ import Dropdown from '@lemonadejs/dropdown/dist/vue';
100
+ import '@lemonadejs/dropdown/dist/style.css';
101
+ import '@lemonadejs/modal/dist/style.css';
102
+
103
+ export default {
104
+ name: 'App',
105
+ components: {
106
+ Dropdown,
107
+ },
108
+ data() {
109
+ return {
110
+ data: [
111
+ { text: 'Red', value: 1 },
112
+ { text: 'Blue', value: 2 },
113
+ { text: 'Green', value: 3 },
114
+ { text: 'Yellow', value: 4 },
115
+ { text: 'Purple', value: 5 },
116
+ { text: 'Gray', value: 6 },
117
+ ],
118
+ };
119
+ },
120
+ };
121
+ </script>
122
+ ```
123
+
124
+ ### Settings
125
+
126
+ | Attribute | Description |
127
+ | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
128
+ | data: Item[] | An array of options to be displayed. Each item should follow the structure defined in the 'Item Details' section. |
129
+ | multiple?: boolean | If provided, enables the multiple selection mode, allowing users to select more than one option simultaneously. |
130
+ | autocomplete?: boolean | If provided, enables the autocomplete feature, displaying an input field that allows users to filter and quickly find options in the Dropdown. |
131
+ | value?: string | Represents the current value or selected option in the Dropdown. |
132
+ | type?: string | Specifies the type of display the Dropdown will use. It can be "searchbar" for a search bar interface, "picker" for a selection picker, or "default" for the default dropdown appearance. |
133
+ | insert?: boolean | Enables the `insert` feature, allowing users to add a new option directly by typing the title text and clicking on the plus symbol. |
134
+ | format?: number | Data format type, usually in the form of { id: name } or { value: text } |
135
+ | width?: number | Specifies the width of the dropdown. |
136
+ | placeholder?: string | Placeholder text to guide users in the dropdown. |
137
+ | url?: string | Specifies the URL for fetching the data. Do not declare the `data` attribute for the url to function properly. |
138
+
139
+ ### Item Details
140
+
141
+ | Attribute | Description |
142
+ | ------------------------ | ------------------------------------------------------------------------------------------- |
143
+ | value?: number or string | Represents the identifier or unique value associated with the option. |
144
+ | group?: string | Indicates the group to which the option belongs, allowing for segregation and organization. |
145
+ | text?: string | Displays the label or text associated with the option. |
146
+ | image?: string | Specifies the image URL to be displayed alongside the option. |
147
+ | synonyms?: string[] | Keywords for easier item identification |
148
+ | disabled?: boolean | Indicates whether the item is currently disabled |
149
+ | color?: string | Specifies the color associated with the item |
150
+
151
+ ### Events
152
+
153
+ | Event | Trigger |
154
+ | ----------------------------------------- | -------------------------------------------------------------------------- |
155
+ | onclose?: () => void | Invoked when the dropdown modal is closed. |
156
+ | onbeforeinsert?: (instance, Item) => void | Invoked before an item is added to the options through the insert feature. |
157
+ | oninsert?: (instance, Item) => void | Invoked after an item is added to the options through the insert feature. |
158
+ | onchange?: (instance, Item) => void | Invoked when the value changes. |
159
+ | onload?: (instance, Item) => void | Invoked when appended to the DOM. |
160
+ | onsearch?: (instance, Item) => void | Invoked when searching for an item. |
161
+ | |
162
+ | onbeforesearch?: (instance, Item) => void | Invoked before initiating a search. |
163
+ | |
164
+ | onopen?: (instance) => void | Invoked when the dropdown is opened. |
165
+
166
+ ## License
167
+
168
+ The [LemonadeJS](https://lemonadejs.net) LemonadeJS Dropdown is released under the MIT.
169
+
170
+ ## Other Tools
171
+
172
+ - [jSuites](https://jsuites.net/docs)
173
+ - [Jspreadsheet Data Grid](https://jspreadsheet.com)
package/dist/index.d.ts CHANGED
@@ -18,13 +18,13 @@ declare namespace Dropdown {
18
18
  /** Name of the group where the item belongs to */
19
19
  group?: string;
20
20
  /** Keywords to help finding one item */
21
- synonym?: string[];
21
+ synonym?: string | string[];
22
22
  /** Item is disabled */
23
23
  disabled?: boolean;
24
24
  /** Color for the item */
25
25
  color?: string;
26
26
  // Keywords
27
- keywords?: string;
27
+ keywords?: string | string[];
28
28
  }
29
29
 
30
30
  interface Options {
@@ -48,9 +48,13 @@ declare namespace Dropdown {
48
48
  insert?: boolean;
49
49
  /** Specifies the URL for fetching the data */
50
50
  url?: string;
51
+ /** Item is disabled */
52
+ disabled?: boolean;
51
53
  /** Enables another ways to option insert */
52
54
  prompt?: boolean | string | (() => string);
53
- /** Allow empty. Default: true */
55
+ /** Remote search */
56
+ remote?: boolean;
57
+ /** Allow empty in single dropdown options. Default: true */
54
58
  allowEmpty?: boolean;
55
59
  /** Event handler for value changes */
56
60
  onchange?: (self: object, newValue: string|number) => void;
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
  }
@@ -43,7 +43,7 @@ if (!Modal && typeof (require) === 'function') {
43
43
  let a = Object.values(options);
44
44
  return method(...a);
45
45
  } else if (this.tagName) {
46
- this.dispatchEvent(new CustomEvents(type, options));
46
+ return this.dispatchEvent(new CustomEvents(type, options));
47
47
  }
48
48
  }
49
49
 
@@ -351,19 +351,6 @@ if (!Modal && typeof (require) === 'function') {
351
351
  return (o instanceof Element || o instanceof HTMLDocument || o instanceof DocumentFragment);
352
352
  }
353
353
 
354
- const compatibilityMapping = function(s) {
355
- const props = {
356
- newOptions: 'insert',
357
- allowempty: 'allowEmpty'
358
- }
359
- Object.keys(props).forEach((k) => {
360
- if (typeof(s[k]) !== 'undefined') {
361
- s[props[k]] = s[k];
362
- delete s[k];
363
- }
364
- });
365
- }
366
-
367
354
  const Dropdown = function (children, { onchange, onload }) {
368
355
  let self = this;
369
356
  // Data
@@ -378,9 +365,8 @@ if (!Modal && typeof (require) === 'function') {
378
365
  let lazyloading = null;
379
366
  // Tracking changes
380
367
  let changesDetected = false;
381
-
382
- // Compatibility with jSuites
383
- compatibilityMapping(self);
368
+ // Debounce timer for search
369
+ let searchTimeout = null;
384
370
 
385
371
  // Data
386
372
  if (! Array.isArray(self.data)) {
@@ -403,6 +389,11 @@ if (!Modal && typeof (require) === 'function') {
403
389
  let change = self.onchange;
404
390
  self.onchange = null;
405
391
 
392
+ // Compatibility
393
+ if (typeof self.newOptions !== 'undefined') {
394
+ self.insert = self.newOptions;
395
+ }
396
+
406
397
  // Cursor controllers
407
398
  const setCursor = function (index, force) {
408
399
  let item = self.rows[index];
@@ -556,7 +547,9 @@ if (!Modal && typeof (require) === 'function') {
556
547
  }
557
548
 
558
549
  // Update label
559
- updateLabel();
550
+ if (self.isClosed()) {
551
+ updateLabel();
552
+ }
560
553
 
561
554
  // Component onchange
562
555
  if (! ignoreEvent) {
@@ -572,14 +565,13 @@ if (!Modal && typeof (require) === 'function') {
572
565
  if (value && value.length) {
573
566
  return value.filter(v => v.selected).map(i => i.value);
574
567
  }
575
- return [];
576
568
  } else {
577
569
  if (value && value.length) {
578
570
  return value[0].value;
579
- } else {
580
- return '';
581
571
  }
582
572
  }
573
+
574
+ return null;
583
575
  }
584
576
 
585
577
  const onopen = function () {
@@ -657,7 +649,22 @@ if (!Modal && typeof (require) === 'function') {
657
649
  });
658
650
  }
659
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
+ });
663
+ }
664
+ }
665
+
660
666
  const loadData = function(result) {
667
+ result = normalizeData(result);
661
668
  // Loading controls
662
669
  lazyloading = lazyLoading(self);
663
670
  // Loading new data from a remote source
@@ -677,53 +684,99 @@ if (!Modal && typeof (require) === 'function') {
677
684
  instance: self
678
685
  });
679
686
  // Remove loading spin
680
- self.el.classList.remove('lm-dropdown-loading');
687
+ self.input.classList.remove('lm-dropdown-loading');
688
+ }
689
+
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
+ });
702
+ }
703
+ self.rows = r;
704
+ // Remove loading spin
705
+ self.input.classList.remove('lm-dropdown-loading');
706
+
707
+ // Event
708
+ Dispatch.call(self, self.onsearch, 'search', {
709
+ instance: self,
710
+ result: result,
711
+ });
681
712
  }
682
713
 
683
714
  const getInput = function() {
684
715
  return self.input;
685
716
  }
686
717
 
687
- const search = function(str) {
718
+ const search = function(query) {
688
719
  if (! self.isClosed() && self.autocomplete) {
689
- // Filter options
690
- let temp;
691
-
692
- const find = (prop) => {
693
- return prop && prop.toString().toLowerCase().includes(str);
694
- }
695
720
 
696
- if (! str) {
697
- temp = data;
698
- } else {
699
- temp = data.filter(item => {
700
- return item.selected === true || find(item.text) || find(item.group) || find(item.keywords);
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,
701
739
  });
702
- }
703
740
 
704
- let ret = Dispatch.call(self, self.onbeforesearch, 'beforesearch', {
705
- instance: self,
706
- query: str,
707
- result: temp,
708
- });
709
-
710
- if (typeof(ret) !== 'undefined') {
711
741
  if (ret === false) {
712
- } else if (Array.isArray(ret)) {
713
- temp = ret;
742
+ return;
714
743
  }
715
- }
716
744
 
717
- // Cursor
718
- removeCursor(true);
719
- // Update the data from the dropdown
720
- self.rows = temp;
721
- // Event
722
- Dispatch.call(self, self.onsearch, 'search', {
723
- instance: self,
724
- query: str,
725
- result: temp,
726
- });
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;
779
+ }
727
780
  }
728
781
  }
729
782
 
@@ -826,6 +879,13 @@ if (!Modal && typeof (require) === 'function') {
826
879
  }
827
880
 
828
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
+
829
889
  if (self.multiple === true) {
830
890
  let position = value.indexOf(s);
831
891
  if (position === -1) {
@@ -871,25 +931,13 @@ if (!Modal && typeof (require) === 'function') {
871
931
  value: text,
872
932
  }
873
933
 
874
- // Event
875
- if (typeof(self.onbeforeinsert) === 'function') {
876
- self.el.classList.add('lm-dropdown-loading');
877
- let ret = await self.onbeforeinsert(self, s);
878
- self.el.classList.remove('lm-dropdown-loading');
879
- if (ret === false) {
880
- return;
881
- } else if (ret) {
882
- s = ret;
883
- }
884
- }
885
-
886
934
  self.add(s);
887
935
 
888
936
  e.preventDefault();
889
937
  }
890
938
 
891
939
  const select = function (e, s) {
892
- if (s) {
940
+ if (s && s.disabled !== true) {
893
941
  selectItem(s);
894
942
  // Close the modal
895
943
  if (self.multiple !== true) {
@@ -908,10 +956,21 @@ if (!Modal && typeof (require) === 'function') {
908
956
  }
909
957
  }
910
958
 
911
- self.add = function (newItem) {
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;
969
+ }
970
+ }
912
971
  // Process the data
913
- data.unshift(newItem);
914
-
972
+ data.push(newItem);
973
+ self.data.push(newItem);
915
974
  // Refresh screen
916
975
  self.result.unshift(newItem);
917
976
  self.rows.unshift(newItem);
@@ -924,7 +983,7 @@ if (!Modal && typeof (require) === 'function') {
924
983
  }
925
984
 
926
985
  self.open = function () {
927
- if (self.modal) {
986
+ if (self.modal && ! self.disabled) {
928
987
  if (self.isClosed()) {
929
988
  if (autoType) {
930
989
  self.type = window.innerWidth > 640 ? self.type = 'default' : (self.autocomplete ? 'searchbar' : 'picker');
@@ -1004,8 +1063,24 @@ if (!Modal && typeof (require) === 'function') {
1004
1063
  Modal(self.el.children[1], self.modal);
1005
1064
  }
1006
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
+
1007
1082
  // Autocomplete will be forced to be true when insert action is active
1008
- if ((self.insert || self.type === 'searchbar') && ! self.autocomplete) {
1083
+ if ((self.insert === true || self.type === 'searchbar' || self.remote === true) && ! self.autocomplete) {
1009
1084
  self.autocomplete = true;
1010
1085
  }
1011
1086
 
@@ -1018,6 +1093,8 @@ if (!Modal && typeof (require) === 'function') {
1018
1093
  self.input.remove();
1019
1094
  // New input
1020
1095
  self.input = input;
1096
+ } else {
1097
+ self.el.children[0].style.position = 'relative';
1021
1098
  }
1022
1099
 
1023
1100
  // Default width
@@ -1043,14 +1120,20 @@ if (!Modal && typeof (require) === 'function') {
1043
1120
 
1044
1121
  // Load remote data
1045
1122
  if (self.url) {
1046
- // Loading spin
1047
- self.el.classList.add('lm-dropdown-loading');
1048
- // Load remote data
1049
- fetch(self.url, {
1050
- headers: {
1051
- 'Content-Type': 'text/json',
1052
- }
1053
- }).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
+ }
1054
1137
  } else {
1055
1138
  loadData();
1056
1139
  }
@@ -1069,7 +1152,7 @@ if (!Modal && typeof (require) === 'function') {
1069
1152
  }
1070
1153
  });
1071
1154
 
1072
- return render => render`<div class="lm-dropdown" data-state="{{self.state}}" data-insert="{{self.insert}}" data-type="{{self.type}}" :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">
1073
1156
  <div class="lm-dropdown-header">
1074
1157
  <div class="lm-dropdown-input" placeholder="{{self.placeholder}}" :ref="self.input" tabindex="0"></div>
1075
1158
  <button class="lm-dropdown-add" onclick="${add}" tabindex="0"></button>
@@ -1081,7 +1164,7 @@ if (!Modal && typeof (require) === 'function') {
1081
1164
  <div class="lm-dropdown-content">
1082
1165
  <div>
1083
1166
  <div :loop="self.result" :ref="self.container" :rows="self.rows">
1084
- <div class="lm-dropdown-item" onclick="${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}}">
1085
1168
  <div><img :src="self.image" /> <div>{{self.text}}</div></div>
1086
1169
  </div>
1087
1170
  </div>
package/dist/style.css CHANGED
@@ -6,9 +6,13 @@
6
6
  vertical-align: top;
7
7
  }
8
8
 
9
+ .lm-dropdown[data-disabled="true"] {
10
+ opacity: 0.5;
11
+ pointer-events: none;
12
+ }
13
+
9
14
  .lm-dropdown-header {
10
15
  display: flex;
11
- position: relative;
12
16
  align-items: center;
13
17
  }
14
18
 
@@ -24,7 +28,7 @@
24
28
  padding: 15px;
25
29
  font-weight: bold;
26
30
  border: 0;
27
- color: var(--lm-main-color);
31
+ color: var(--lm-font-color);
28
32
  margin: 1px;
29
33
  }
30
34
 
@@ -50,38 +54,25 @@
50
54
  cursor: pointer;
51
55
  box-sizing: border-box;
52
56
  width: 100%;
53
- background: var(--lm-background-color, #fff);
57
+ background: var(--lm-background-color-input, #fff);
54
58
  border: 1px solid var(--lm-border-color, #767676);
55
59
  border-radius: var(--lm-border-radius, 2px);
56
60
  position: relative;
57
61
  }
58
62
 
59
- @media (prefers-color-scheme: dark) {
60
- .lm-dropdown-input {
61
- background: var(--lm-background-color, #3b3b3b);
62
- border: 1px solid var(--lm-border-color, #858585);
63
- color: var(--lm-text-color, #fff);
64
- }
65
- }
66
-
67
63
  .lm-dropdown-input > br {
68
64
  display: none;
69
65
  }
70
66
 
71
- .lm-dropdown-input:focus {
72
- outline: 2px solid var(--lm-border-outline, #000);
73
- outline-offset: -1px;
74
- }
75
-
76
67
  .lm-dropdown-input::after {
77
68
  content: '';
78
69
  position: absolute;
79
70
  width: 24px;
80
- height: 100%;
81
- top: 0;
82
- right: 4px;
71
+ height: 24px;
72
+ top: calc(50% - 12px);
73
+ right: 0;
83
74
  background-repeat: no-repeat;
84
- background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath d='M7 10l5 5 5-5H7z' fill='gray'/%3E%3C/svg%3E");
75
+ background-image: url("data:image/svg+xml,%0A%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%2724%27 height=%2724%27 viewBox=%270 0 24 24%27%3E%3Cpath fill=%27none%27 d=%27M0 0h24v24H0V0z%27/%3E%3Cpath d=%27M7 10l5 5 5-5H7z%27 fill=%27gray%27/%3E%3C/svg%3E");
85
76
  background-position: center;
86
77
  transition: transform .1s ease-in-out;
87
78
  }
@@ -89,17 +80,22 @@
89
80
  .lm-application .lm-dropdown-add,
90
81
  .lm-dropdown-add {
91
82
  position: absolute;
92
- padding: 6px 6px;
93
- right: 30px;
94
- display: none;
83
+ padding: 0;
95
84
  margin: 0;
96
85
  border: 0;
97
86
  background: transparent;
98
87
  cursor: pointer;
88
+ right: 30px;
89
+ top: calc(50% - 8px);
90
+ display: none;
99
91
  }
100
92
 
101
93
  .lm-dropdown-add::after {
102
94
  content: '+';
95
+ width: 16px;
96
+ height: auto;
97
+ display: block;
98
+ color: var(--lm-font-color);
103
99
  }
104
100
 
105
101
  .lm-dropdown[data-insert="true"] .lm-dropdown-input {
@@ -131,14 +127,19 @@
131
127
  box-sizing: border-box;
132
128
  }
133
129
 
130
+ .lm-dropdown-item[data-disabled="true"] {
131
+ opacity: 0.5;
132
+ pointer-events: none;
133
+ }
134
+
134
135
  .lm-dropdown-item[data-group]::before {
135
136
  content: attr(data-group);
136
137
  display: block;
137
138
  padding: 8px;
138
139
  font-weight: bold;
139
140
  width: 100%;
140
- background-color: #FFF;
141
- color: #000;
141
+ background-color: var(--lm-background-color-input, #fff);
142
+ color: var(--lm-font-color, #000);
142
143
  box-sizing: border-box;
143
144
  }
144
145
 
@@ -147,7 +148,7 @@
147
148
  text-align: left;
148
149
  text-overflow: ellipsis;
149
150
  overflow: hidden;
150
- color: #000;
151
+ color: var(--lm-font-color, #000);
151
152
  display: flex;
152
153
  align-items: center;
153
154
  padding: 4px 40px 4px 10px;
@@ -175,20 +176,12 @@
175
176
  }
176
177
 
177
178
  .lm-dropdown-item > div:hover {
178
- background-color: dodgerblue !important;
179
+ background-color: #1e90ff;
179
180
  color: white;
180
181
  }
181
182
 
182
- .lm-dropdown-item[data-cursor="true"] > div {
183
- background-color: #eee;
184
- }
185
-
186
- .lm-dropdown-item[data-cursor="true"]:hover > div {
187
- background-color: dodgerblue;
188
- }
189
-
190
183
  .lm-dropdown-item[data-selected="true"] > div {
191
- background-color: dodgerblue;
184
+ background-color: #1e90ff;
192
185
  color: white;
193
186
  }
194
187
 
@@ -199,6 +192,18 @@
199
192
  line-height: 24px;
200
193
  }
201
194
 
195
+ .lm-dropdown-item[data-cursor="true"] > div {
196
+ background-color: var(--lm-background-color-highlight);
197
+ }
198
+
199
+ .lm-dropdown-item[data-cursor="true"]:hover > div {
200
+ background-color: var(--lm-background-color-active);
201
+ }
202
+
203
+ .lm-dropdown-item[data-cursor="true"].lm-dropdown-item[data-selected="true"] > div {
204
+ background-color: #1d86ed;
205
+ }
206
+
202
207
  /** Picker */
203
208
 
204
209
  .lm-dropdown[data-type="picker"][data-state="true"] .lm-dropdown-content .lm-modal {
@@ -358,18 +363,18 @@
358
363
  width: 12px;
359
364
  }
360
365
 
361
- .lm-dropdown.lm-dropdown-loading .lm-dropdown-add::after {
366
+ .lm-dropdown .lm-dropdown-loading::after {
362
367
  position: absolute;
363
368
  content: '';
364
369
  width: 12px;
365
370
  height: 12px;
366
- margin-top: -7px;
367
- margin-left: -12px;
371
+ margin: 5px;
368
372
  border-style: solid;
369
373
  border-color: #888;
370
374
  border-top-color: transparent;
371
375
  border-width: 1px;
372
376
  border-radius: 50%;
377
+ background-image: none;
373
378
  animation: lm-dropdown-spin .8s linear infinite;
374
379
  }
375
380
 
@@ -382,10 +387,6 @@
382
387
  }
383
388
  }
384
389
 
385
- .lm-dark-mode .lm-dropdown-item > div {
386
- color: white;
387
- }
388
-
389
390
  /** Lazyloading */
390
391
 
391
392
  .lm-lazy {
package/package.json CHANGED
@@ -1,23 +1,23 @@
1
- {
2
- "name": "@lemonadejs/dropdown",
3
- "title": "LemonadeJS dropdown",
4
- "description": "LemonadeJS dropdown integration.",
5
- "author": {
6
- "name": "Contact <contact@lemonadejs.net>",
7
- "url": "https://lemonadejs.net"
8
- },
9
- "keywords": [
10
- "javascript dropdown",
11
- "lemonadejs plugins",
12
- "js",
13
- "library",
14
- "javascript plugins"
15
- ],
16
- "dependencies": {
17
- "lemonadejs": "^5.2.0",
18
- "@lemonadejs/modal": "^5.2.0"
19
- },
20
- "main": "dist/index.js",
21
- "types": "dist/index.d.ts",
22
- "version": "5.2.0"
23
- }
1
+ {
2
+ "name": "@lemonadejs/dropdown",
3
+ "title": "LemonadeJS dropdown",
4
+ "description": "LemonadeJS dropdown integration.",
5
+ "author": {
6
+ "name": "Contact <contact@lemonadejs.net>",
7
+ "url": "https://lemonadejs.net"
8
+ },
9
+ "keywords": [
10
+ "javascript dropdown",
11
+ "lemonadejs plugins",
12
+ "js",
13
+ "library",
14
+ "javascript plugins"
15
+ ],
16
+ "dependencies": {
17
+ "lemonadejs": "^5.3.2",
18
+ "@lemonadejs/modal": "^5.8.0"
19
+ },
20
+ "main": "dist/index.js",
21
+ "types": "dist/index.d.ts",
22
+ "version": "5.8.0"
23
+ }