@lemonadejs/dropdown 5.2.0 → 5.8.1

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;
@@ -98,7 +102,9 @@ declare namespace Dropdown {
98
102
  isClosed: () => boolean;
99
103
  /** Get current value */
100
104
  getValue: () => number | string | number[] | string[];
101
- /** Get current value */
105
+ /** Get current text (label). Returns string if single, string[] if multiple */
106
+ getText: () => string | string[];
107
+ /** Set current value */
102
108
  setValue: (value: number | string | number[] | string[]) => void;
103
109
  /** Get current data */
104
110
  getData: () => Item[];
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];
@@ -548,7 +539,13 @@ if (!Modal && typeof (require) === 'function') {
548
539
 
549
540
  if (Array.isArray(data)) {
550
541
  data.map(function (s) {
551
- s.selected = newValue.some(v => v == s.value);
542
+ s.selected = newValue.some(v => {
543
+ // Use strict equality when either value is empty string to avoid '' == 0 being true
544
+ if (v === '' || s.value === '') {
545
+ return v === s.value;
546
+ }
547
+ return v == s.value;
548
+ });
552
549
  if (s.selected) {
553
550
  value.push(s);
554
551
  }
@@ -556,7 +553,9 @@ if (!Modal && typeof (require) === 'function') {
556
553
  }
557
554
 
558
555
  // Update label
559
- updateLabel();
556
+ if (self.isClosed()) {
557
+ updateLabel();
558
+ }
560
559
 
561
560
  // Component onchange
562
561
  if (! ignoreEvent) {
@@ -572,14 +571,27 @@ if (!Modal && typeof (require) === 'function') {
572
571
  if (value && value.length) {
573
572
  return value.filter(v => v.selected).map(i => i.value);
574
573
  }
575
- return [];
576
574
  } else {
577
575
  if (value && value.length) {
578
576
  return value[0].value;
579
- } else {
580
- return '';
581
577
  }
582
578
  }
579
+
580
+ return null;
581
+ }
582
+
583
+ const getText = function () {
584
+ if (self.multiple) {
585
+ if (value && value.length) {
586
+ return value.filter(v => v.selected).map(i => i.text);
587
+ }
588
+ } else {
589
+ if (value && value.length) {
590
+ return value[0].text;
591
+ }
592
+ }
593
+
594
+ return null;
583
595
  }
584
596
 
585
597
  const onopen = function () {
@@ -657,7 +669,22 @@ if (!Modal && typeof (require) === 'function') {
657
669
  });
658
670
  }
659
671
 
672
+ const normalizeData = function(result) {
673
+ if (result && result.length) {
674
+ return result.map((v) => {
675
+ if (typeof v === 'string' || typeof v === 'number') {
676
+ return { value: v, text: v };
677
+ } else if (typeof v === 'object' && v.hasOwnProperty('name')) {
678
+ return { value: v.id, text: v.name };
679
+ } else {
680
+ return v;
681
+ }
682
+ });
683
+ }
684
+ }
685
+
660
686
  const loadData = function(result) {
687
+ result = normalizeData(result);
661
688
  // Loading controls
662
689
  lazyloading = lazyLoading(self);
663
690
  // Loading new data from a remote source
@@ -677,53 +704,103 @@ if (!Modal && typeof (require) === 'function') {
677
704
  instance: self
678
705
  });
679
706
  // Remove loading spin
680
- self.el.classList.remove('lm-dropdown-loading');
707
+ self.input.classList.remove('lm-dropdown-loading');
708
+ }
709
+
710
+ const resetData = function(result) {
711
+ result = normalizeData(result);
712
+ // Reset cursor
713
+ removeCursor(true);
714
+ let r = data.filter(item => {
715
+ return item.selected === true;
716
+ });
717
+ // Loading new data from a remote source
718
+ if (result) {
719
+ result.forEach((v) => {
720
+ r.push(v);
721
+ });
722
+ }
723
+ self.rows = r;
724
+ // Remove loading spin
725
+ self.input.classList.remove('lm-dropdown-loading');
726
+
727
+ // Event
728
+ Dispatch.call(self, self.onsearch, 'search', {
729
+ instance: self,
730
+ result: result,
731
+ });
681
732
  }
682
733
 
683
734
  const getInput = function() {
684
735
  return self.input;
685
736
  }
686
737
 
687
- const search = function(str) {
738
+ const search = function(query) {
688
739
  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
740
 
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);
741
+ // Remote or normal search
742
+ if (self.remote === true) {
743
+ // Clear existing timeout
744
+ if (searchTimeout) {
745
+ clearTimeout(searchTimeout);
746
+ }
747
+ // Loading spin
748
+ self.input.classList.add('lm-dropdown-loading');
749
+ // Headers
750
+ let http = {
751
+ headers: {
752
+ 'Content-Type': 'text/json',
753
+ }
754
+ }
755
+ let ret = Dispatch.call(self, self.onbeforesearch, 'beforesearch', {
756
+ instance: self,
757
+ http: http,
758
+ query: query,
701
759
  });
702
- }
703
760
 
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
761
  if (ret === false) {
712
- } else if (Array.isArray(ret)) {
713
- temp = ret;
762
+ return;
714
763
  }
715
- }
716
764
 
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
- });
765
+ // Debounce the search with 300ms delay
766
+ searchTimeout = setTimeout(() => {
767
+ let url = self.url;
768
+ url += url.indexOf('?') === -1 ? '?' : '&';
769
+ url += `q=${query}`;
770
+
771
+ fetch(url, http).then(r => r.json()).then(resetData).catch((error) => {
772
+ resetData([]);
773
+ });
774
+ }, 300);
775
+ } else {
776
+ // Filter options
777
+ let temp;
778
+
779
+ const find = (prop) => {
780
+ if (prop) {
781
+ if (Array.isArray(prop)) {
782
+ // match if ANY element contains the query (case-insensitive)
783
+ return prop.some(v => v != null && v.toString().toLowerCase().includes(query));
784
+ }
785
+ // handle strings/numbers/others
786
+ return prop.toString().toLowerCase().includes(query);
787
+ }
788
+ return false;
789
+ };
790
+
791
+ if (! query) {
792
+ temp = data;
793
+ } else {
794
+ temp = data.filter(item => {
795
+ return item.selected === true || find(item.text) || find(item.group) || find(item.keywords) || find(item.synonym);
796
+ });
797
+ }
798
+
799
+ // Cursor
800
+ removeCursor(true);
801
+ // Update the data from the dropdown
802
+ self.rows = temp;
803
+ }
727
804
  }
728
805
  }
729
806
 
@@ -826,6 +903,13 @@ if (!Modal && typeof (require) === 'function') {
826
903
  }
827
904
 
828
905
  const selectItem = function(s) {
906
+ if (self.remote === true) {
907
+ if (data.indexOf(s) === -1) {
908
+ self.data.push(s);
909
+ data.push(s);
910
+ }
911
+ }
912
+
829
913
  if (self.multiple === true) {
830
914
  let position = value.indexOf(s);
831
915
  if (position === -1) {
@@ -871,25 +955,13 @@ if (!Modal && typeof (require) === 'function') {
871
955
  value: text,
872
956
  }
873
957
 
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
958
  self.add(s);
887
959
 
888
960
  e.preventDefault();
889
961
  }
890
962
 
891
963
  const select = function (e, s) {
892
- if (s) {
964
+ if (s && s.disabled !== true) {
893
965
  selectItem(s);
894
966
  // Close the modal
895
967
  if (self.multiple !== true) {
@@ -908,10 +980,21 @@ if (!Modal && typeof (require) === 'function') {
908
980
  }
909
981
  }
910
982
 
911
- self.add = function (newItem) {
983
+ self.add = async function (newItem) {
984
+ // Event
985
+ if (typeof(self.onbeforeinsert) === 'function') {
986
+ self.input.classList.add('lm-dropdown-loading');
987
+ let ret = await self.onbeforeinsert(self, newItem);
988
+ self.input.classList.remove('lm-dropdown-loading');
989
+ if (ret === false) {
990
+ return;
991
+ } else if (ret) {
992
+ newItem = ret;
993
+ }
994
+ }
912
995
  // Process the data
913
- data.unshift(newItem);
914
-
996
+ data.push(newItem);
997
+ self.data.push(newItem);
915
998
  // Refresh screen
916
999
  self.result.unshift(newItem);
917
1000
  self.rows.unshift(newItem);
@@ -924,7 +1007,7 @@ if (!Modal && typeof (require) === 'function') {
924
1007
  }
925
1008
 
926
1009
  self.open = function () {
927
- if (self.modal) {
1010
+ if (self.modal && ! self.disabled) {
928
1011
  if (self.isClosed()) {
929
1012
  if (autoType) {
930
1013
  self.type = window.innerWidth > 640 ? self.type = 'default' : (self.autocomplete ? 'searchbar' : 'picker');
@@ -965,6 +1048,10 @@ if (!Modal && typeof (require) === 'function') {
965
1048
  return self.value;
966
1049
  }
967
1050
 
1051
+ self.getText = function() {
1052
+ return getText();
1053
+ }
1054
+
968
1055
  self.setValue = function(v) {
969
1056
  self.value = v;
970
1057
  }
@@ -1004,8 +1091,24 @@ if (!Modal && typeof (require) === 'function') {
1004
1091
  Modal(self.el.children[1], self.modal);
1005
1092
  }
1006
1093
 
1094
+ if (self.remote === 'true') {
1095
+ self.remote = true;
1096
+ }
1097
+
1098
+ if (self.autocomplete === 'true') {
1099
+ self.autocomplete = true;
1100
+ }
1101
+
1102
+ if (self.multiple === 'true') {
1103
+ self.multiple = true;
1104
+ }
1105
+
1106
+ if (self.insert === 'true') {
1107
+ self.insert = true;
1108
+ }
1109
+
1007
1110
  // Autocomplete will be forced to be true when insert action is active
1008
- if ((self.insert || self.type === 'searchbar') && ! self.autocomplete) {
1111
+ if ((self.insert === true || self.type === 'searchbar' || self.remote === true) && ! self.autocomplete) {
1009
1112
  self.autocomplete = true;
1010
1113
  }
1011
1114
 
@@ -1018,6 +1121,8 @@ if (!Modal && typeof (require) === 'function') {
1018
1121
  self.input.remove();
1019
1122
  // New input
1020
1123
  self.input = input;
1124
+ } else {
1125
+ self.el.children[0].style.position = 'relative';
1021
1126
  }
1022
1127
 
1023
1128
  // Default width
@@ -1043,14 +1148,20 @@ if (!Modal && typeof (require) === 'function') {
1043
1148
 
1044
1149
  // Load remote data
1045
1150
  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);
1151
+ if (self.remote === true) {
1152
+ loadData();
1153
+ } else {
1154
+ // Loading spin
1155
+ self.input.classList.add('lm-dropdown-loading');
1156
+ // Load remote data
1157
+ fetch(self.url, {
1158
+ headers: {
1159
+ 'Content-Type': 'text/json',
1160
+ }
1161
+ }).then(r => r.json()).then(loadData).catch(() => {
1162
+ loadData();
1163
+ });
1164
+ }
1054
1165
  } else {
1055
1166
  loadData();
1056
1167
  }
@@ -1069,7 +1180,7 @@ if (!Modal && typeof (require) === 'function') {
1069
1180
  }
1070
1181
  });
1071
1182
 
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">
1183
+ 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
1184
  <div class="lm-dropdown-header">
1074
1185
  <div class="lm-dropdown-input" placeholder="{{self.placeholder}}" :ref="self.input" tabindex="0"></div>
1075
1186
  <button class="lm-dropdown-add" onclick="${add}" tabindex="0"></button>
@@ -1081,7 +1192,7 @@ if (!Modal && typeof (require) === 'function') {
1081
1192
  <div class="lm-dropdown-content">
1082
1193
  <div>
1083
1194
  <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}}">
1195
+ <div class="lm-dropdown-item" onclick="${select}" data-cursor="{{self.cursor}}" data-disabled="{{self.disabled}}" data-selected="{{self.selected}}" data-group="{{self.header}}">
1085
1196
  <div><img :src="self.image" /> <div>{{self.text}}</div></div>
1086
1197
  </div>
1087
1198
  </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,29 @@
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
- }
58
-
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
- }
61
+ line-height: normal;
62
+ display: flex;
63
+ align-items: center;
64
+ font: inherit;
65
65
  }
66
66
 
67
67
  .lm-dropdown-input > br {
68
68
  display: none;
69
69
  }
70
70
 
71
- .lm-dropdown-input:focus {
72
- outline: 2px solid var(--lm-border-outline, #000);
73
- outline-offset: -1px;
74
- }
75
-
76
71
  .lm-dropdown-input::after {
77
72
  content: '';
78
73
  position: absolute;
79
74
  width: 24px;
80
- height: 100%;
81
- top: 0;
82
- right: 4px;
75
+ height: 24px;
76
+ top: calc(50% - 12px);
77
+ right: 0;
83
78
  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");
79
+ 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
80
  background-position: center;
86
81
  transition: transform .1s ease-in-out;
87
82
  }
@@ -89,17 +84,22 @@
89
84
  .lm-application .lm-dropdown-add,
90
85
  .lm-dropdown-add {
91
86
  position: absolute;
92
- padding: 6px 6px;
93
- right: 30px;
94
- display: none;
87
+ padding: 0;
95
88
  margin: 0;
96
89
  border: 0;
97
90
  background: transparent;
98
91
  cursor: pointer;
92
+ right: 30px;
93
+ top: calc(50% - 8px);
94
+ display: none;
99
95
  }
100
96
 
101
97
  .lm-dropdown-add::after {
102
98
  content: '+';
99
+ width: 16px;
100
+ height: auto;
101
+ display: block;
102
+ color: var(--lm-font-color);
103
103
  }
104
104
 
105
105
  .lm-dropdown[data-insert="true"] .lm-dropdown-input {
@@ -131,14 +131,19 @@
131
131
  box-sizing: border-box;
132
132
  }
133
133
 
134
+ .lm-dropdown-item[data-disabled="true"] {
135
+ opacity: 0.5;
136
+ pointer-events: none;
137
+ }
138
+
134
139
  .lm-dropdown-item[data-group]::before {
135
140
  content: attr(data-group);
136
141
  display: block;
137
142
  padding: 8px;
138
143
  font-weight: bold;
139
144
  width: 100%;
140
- background-color: #FFF;
141
- color: #000;
145
+ background-color: var(--lm-background-color-input, #fff);
146
+ color: var(--lm-font-color, #000);
142
147
  box-sizing: border-box;
143
148
  }
144
149
 
@@ -147,7 +152,7 @@
147
152
  text-align: left;
148
153
  text-overflow: ellipsis;
149
154
  overflow: hidden;
150
- color: #000;
155
+ color: var(--lm-font-color, #000);
151
156
  display: flex;
152
157
  align-items: center;
153
158
  padding: 4px 40px 4px 10px;
@@ -175,20 +180,12 @@
175
180
  }
176
181
 
177
182
  .lm-dropdown-item > div:hover {
178
- background-color: dodgerblue !important;
183
+ background-color: #1e90ff;
179
184
  color: white;
180
185
  }
181
186
 
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
187
  .lm-dropdown-item[data-selected="true"] > div {
191
- background-color: dodgerblue;
188
+ background-color: #1e90ff;
192
189
  color: white;
193
190
  }
194
191
 
@@ -199,6 +196,18 @@
199
196
  line-height: 24px;
200
197
  }
201
198
 
199
+ .lm-dropdown-item[data-cursor="true"] > div {
200
+ background-color: var(--lm-background-color-highlight);
201
+ }
202
+
203
+ .lm-dropdown-item[data-cursor="true"]:hover > div {
204
+ background-color: var(--lm-background-color-active);
205
+ }
206
+
207
+ .lm-dropdown-item[data-cursor="true"].lm-dropdown-item[data-selected="true"] > div {
208
+ background-color: #1d86ed;
209
+ }
210
+
202
211
  /** Picker */
203
212
 
204
213
  .lm-dropdown[data-type="picker"][data-state="true"] .lm-dropdown-content .lm-modal {
@@ -358,18 +367,18 @@
358
367
  width: 12px;
359
368
  }
360
369
 
361
- .lm-dropdown.lm-dropdown-loading .lm-dropdown-add::after {
370
+ .lm-dropdown .lm-dropdown-loading::after {
362
371
  position: absolute;
363
372
  content: '';
364
373
  width: 12px;
365
374
  height: 12px;
366
- margin-top: -7px;
367
- margin-left: -12px;
375
+ margin: 5px;
368
376
  border-style: solid;
369
377
  border-color: #888;
370
378
  border-top-color: transparent;
371
379
  border-width: 1px;
372
380
  border-radius: 50%;
381
+ background-image: none;
373
382
  animation: lm-dropdown-spin .8s linear infinite;
374
383
  }
375
384
 
@@ -382,10 +391,6 @@
382
391
  }
383
392
  }
384
393
 
385
- .lm-dark-mode .lm-dropdown-item > div {
386
- color: white;
387
- }
388
-
389
394
  /** Lazyloading */
390
395
 
391
396
  .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.6",
18
+ "@lemonadejs/modal": "^5.8.0"
19
+ },
20
+ "main": "dist/index.js",
21
+ "types": "dist/index.d.ts",
22
+ "version": "5.8.1"
23
+ }