@lemonadejs/dropdown 3.0.12 → 3.1.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 +100 -0
- package/dist/index.d.ts +22 -9
- package/dist/index.js +93 -6
- package/dist/style.css +57 -6
- package/dist/vue.d.ts +15 -0
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# LemonadeJS Dropdown
|
|
2
|
+
|
|
3
|
+
[Official website and documentation is here](https://lemonadejs.net/components/dropdown)
|
|
4
|
+
|
|
5
|
+
Compatible with Vanilla JavaScript, LemonadeJS, React, Vue 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
|
+
### Settings
|
|
66
|
+
|
|
67
|
+
| Attribute | Description |
|
|
68
|
+
|-----------|-------------|
|
|
69
|
+
| data: Item[] | An array of options to be displayed. Each item should follow the structure defined in the 'Item Details' section. |
|
|
70
|
+
| multiple?: boolean | If provided, enables the multiple selection mode, allowing users to select more than one option simultaneously. |
|
|
71
|
+
| autocomplete?: boolean | If provided, enables the autocomplete feature, displaying an input field that allows users to filter and quickly find options in the Dropdown. |
|
|
72
|
+
| value?: string | Represents the current value or selected option in the Dropdown. |
|
|
73
|
+
| 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. |
|
|
74
|
+
| 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. |
|
|
75
|
+
|
|
76
|
+
### Item Details
|
|
77
|
+
|
|
78
|
+
| Attribute | Description |
|
|
79
|
+
| --------- | ----------- |
|
|
80
|
+
| value?: number or string | Represents the identifier or unique value associated with the option. |
|
|
81
|
+
| group?: string | Indicates the group to which the option belongs, allowing for segregation and organization. |
|
|
82
|
+
| text?: string | Displays the label or text associated with the option. |
|
|
83
|
+
| image?: string | Specifies the image URL to be displayed alongside the option. |
|
|
84
|
+
|
|
85
|
+
### Events
|
|
86
|
+
|
|
87
|
+
| Event | Trigger |
|
|
88
|
+
| ----- | ------- |
|
|
89
|
+
| onclose?: () => void | Invoked when the dropdown modal is closed. |
|
|
90
|
+
| onbeforeinsert?: (instance, Item) => void | Invoked before an item is added to the options through the insert feature. |
|
|
91
|
+
| oninsert?: (instance, Item) => void | Invoked after an item is added to the options through the insert feature. |
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
The [LemonadeJS](https://lemonadejs.net) LemonadeJS Dropdown is released under the MIT.
|
|
96
|
+
|
|
97
|
+
## Other Tools
|
|
98
|
+
|
|
99
|
+
- [jSuites](https://jsuites.net/v4/)
|
|
100
|
+
- [Jspreadsheet Data Grid](https://jspreadsheet.com)
|
package/dist/index.d.ts
CHANGED
|
@@ -35,25 +35,38 @@ declare namespace Dropdown {
|
|
|
35
35
|
autocomplete?: boolean;
|
|
36
36
|
/** Rendering style of the dropdown: 'default', 'picker', 'searchbar' or inline */
|
|
37
37
|
type?: 'default' | 'picker' | 'searchbar' | 'inline',
|
|
38
|
-
/** Defines the dropdown
|
|
38
|
+
/** Defines the dropdown width */
|
|
39
39
|
width?: number;
|
|
40
40
|
/** The initial value of the dropdown */
|
|
41
41
|
value?: string | string[] | number | number[];
|
|
42
42
|
/** Placeholder text for the dropdown */
|
|
43
43
|
placeholder?: string;
|
|
44
|
+
/** Allow insert new items */
|
|
45
|
+
insert?: boolean;
|
|
44
46
|
/** Event handler for value changes */
|
|
45
|
-
onchange?: (
|
|
47
|
+
onchange?: (obj: object, newValue: string|number) => void;
|
|
46
48
|
/** Event handler for when the dropdown is ready */
|
|
47
|
-
onload?: (
|
|
49
|
+
onload?: (obj: object) => void;
|
|
48
50
|
/** Event handler for when the dropdown opens */
|
|
49
|
-
onopen?: (
|
|
51
|
+
onopen?: (obj: object) => void;
|
|
50
52
|
/** Event handler for when the dropdown closes */
|
|
51
|
-
onclose?: (
|
|
52
|
-
/**
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
onclose?: (obj: object) => void;
|
|
54
|
+
/**
|
|
55
|
+
* Event handler for just before a new option is added to the dropdown. This is an async function to handle ajax requests.
|
|
56
|
+
* Example:
|
|
57
|
+
* self.beforeInsert = async function(s, element) {
|
|
58
|
+
* let newId = await getTheNewItemIdFromDatabase(element.text);
|
|
59
|
+
* return {
|
|
60
|
+
* text: element.text,
|
|
61
|
+
* value: newId,
|
|
62
|
+
* };
|
|
63
|
+
* }
|
|
64
|
+
*
|
|
65
|
+
* */
|
|
55
66
|
onbeforeinsert?: (obj: object, item: Item) => void;
|
|
56
|
-
/** Event handler for
|
|
67
|
+
/** Event handler for when a new option is added to the dropdown */
|
|
68
|
+
oninsert?: (obj: object, item: Item) => void;
|
|
69
|
+
/** Before the search happens */
|
|
57
70
|
onbeforesearch?: (obj: object, ajaxRequest: object) => boolean | null;
|
|
58
71
|
/** Event handler for processing search results */
|
|
59
72
|
onsearch?: (obj: object, result: object) => void;
|
package/dist/index.js
CHANGED
|
@@ -106,7 +106,14 @@ if (!Modal && typeof (require) === 'function') {
|
|
|
106
106
|
// Get the position from top
|
|
107
107
|
let y = el.scrollTop;
|
|
108
108
|
// Get the height
|
|
109
|
-
let h =
|
|
109
|
+
let h = null;
|
|
110
|
+
if (self.type === 'searchbar' || self.type === 'picker') {
|
|
111
|
+
// Priority should be the size used on the viewport
|
|
112
|
+
h = y + (el.offsetHeight || self.height);
|
|
113
|
+
} else {
|
|
114
|
+
// Priority is the height define during initialization
|
|
115
|
+
h = y + (self.height || el.offsetHeight);
|
|
116
|
+
}
|
|
110
117
|
// Go through the items
|
|
111
118
|
let rows = [];
|
|
112
119
|
// Height
|
|
@@ -171,6 +178,8 @@ if (!Modal && typeof (require) === 'function') {
|
|
|
171
178
|
const render = function (reset) {
|
|
172
179
|
// Move scroll to the top
|
|
173
180
|
el.scrollTop = 0;
|
|
181
|
+
// Reset scroll
|
|
182
|
+
updateScroll();
|
|
174
183
|
// Append first batch
|
|
175
184
|
getVisibleRows(reset);
|
|
176
185
|
}
|
|
@@ -213,7 +222,7 @@ if (!Modal && typeof (require) === 'function') {
|
|
|
213
222
|
el.scrollTop = getRowPosition(item);
|
|
214
223
|
let adjust = getVisibleRows(false);
|
|
215
224
|
if (adjust) {
|
|
216
|
-
el.scrollTop
|
|
225
|
+
el.scrollTop = adjust;
|
|
217
226
|
// Last adjust on the visible rows
|
|
218
227
|
getVisibleRows(false);
|
|
219
228
|
}
|
|
@@ -238,6 +247,9 @@ if (!Modal && typeof (require) === 'function') {
|
|
|
238
247
|
// Lazy loading global instance
|
|
239
248
|
let lazyloading = null;
|
|
240
249
|
|
|
250
|
+
let onload = self.load;
|
|
251
|
+
let onchange = self.onchange;
|
|
252
|
+
|
|
241
253
|
const setData = function() {
|
|
242
254
|
// Estimate width
|
|
243
255
|
let width = self.width;
|
|
@@ -392,6 +404,11 @@ if (!Modal && typeof (require) === 'function') {
|
|
|
392
404
|
|
|
393
405
|
// Update label
|
|
394
406
|
updateLabel();
|
|
407
|
+
|
|
408
|
+
// Component onchange
|
|
409
|
+
if (typeof(onchange) === 'function') {
|
|
410
|
+
onchange(self, self.value);
|
|
411
|
+
}
|
|
395
412
|
}
|
|
396
413
|
|
|
397
414
|
const getValue = function() {
|
|
@@ -427,6 +444,10 @@ if (!Modal && typeof (require) === 'function') {
|
|
|
427
444
|
self.value = getValue();
|
|
428
445
|
// Identify the new state of the dropdown
|
|
429
446
|
self.state = false;
|
|
447
|
+
|
|
448
|
+
if (typeof(self.onclose) === 'function') {
|
|
449
|
+
self.onclose(self);
|
|
450
|
+
}
|
|
430
451
|
}
|
|
431
452
|
|
|
432
453
|
const onopen = function() {
|
|
@@ -452,9 +473,13 @@ if (!Modal && typeof (require) === 'function') {
|
|
|
452
473
|
// Focus on the item
|
|
453
474
|
self.input.focus();
|
|
454
475
|
}
|
|
476
|
+
|
|
477
|
+
if (typeof(self.onopen) === 'function') {
|
|
478
|
+
self.onopen(self);
|
|
479
|
+
}
|
|
455
480
|
}
|
|
456
481
|
|
|
457
|
-
self.search = function() {
|
|
482
|
+
self.search = function(e) {
|
|
458
483
|
if (self.state && self.autocomplete) {
|
|
459
484
|
// Filter options
|
|
460
485
|
let data;
|
|
@@ -513,7 +538,7 @@ if (!Modal && typeof (require) === 'function') {
|
|
|
513
538
|
} else {
|
|
514
539
|
x = e.clientX;
|
|
515
540
|
}
|
|
516
|
-
if (e.target.offsetWidth - (x - e.target.offsetLeft) <
|
|
541
|
+
if (e.target.offsetWidth - (x - e.target.offsetLeft) < 20) {
|
|
517
542
|
self.toggle();
|
|
518
543
|
} else {
|
|
519
544
|
self.open();
|
|
@@ -568,6 +593,8 @@ if (!Modal && typeof (require) === 'function') {
|
|
|
568
593
|
width: self.width,
|
|
569
594
|
onopen: onopen,
|
|
570
595
|
onclose: onclose,
|
|
596
|
+
position: 'absolute',
|
|
597
|
+
'auto-adjust': true,
|
|
571
598
|
'auto-close': false,
|
|
572
599
|
};
|
|
573
600
|
// Generate modal
|
|
@@ -619,6 +646,10 @@ if (!Modal && typeof (require) === 'function') {
|
|
|
619
646
|
e.stopImmediatePropagation();
|
|
620
647
|
}
|
|
621
648
|
});
|
|
649
|
+
|
|
650
|
+
if (typeof(onload) === 'function') {
|
|
651
|
+
onload(self);
|
|
652
|
+
}
|
|
622
653
|
}
|
|
623
654
|
|
|
624
655
|
self.onchange = function(prop) {
|
|
@@ -633,9 +664,65 @@ if (!Modal && typeof (require) === 'function') {
|
|
|
633
664
|
}
|
|
634
665
|
}
|
|
635
666
|
|
|
636
|
-
|
|
667
|
+
/**
|
|
668
|
+
* Sanitize any HTML from be paste on the search
|
|
669
|
+
* @param e
|
|
670
|
+
*/
|
|
671
|
+
self.onpaste = function(e) {
|
|
672
|
+
let text;
|
|
673
|
+
if (e.clipboardData || e.originalEvent.clipboardData) {
|
|
674
|
+
text = (e.originalEvent || e).clipboardData.getData('text/plain');
|
|
675
|
+
} else if (window.clipboardData) {
|
|
676
|
+
text = window.clipboardData.getData('Text');
|
|
677
|
+
}
|
|
678
|
+
text = text.replace(/(\r\n|\n|\r)/gm, "");
|
|
679
|
+
document.execCommand('insertText', false, text)
|
|
680
|
+
e.preventDefault();
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
self.add = async function(e) {
|
|
684
|
+
if (! self.input.textContent) {
|
|
685
|
+
return false;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
e.preventDefault();
|
|
689
|
+
|
|
690
|
+
// New item
|
|
691
|
+
let s = {
|
|
692
|
+
text: self.input.textContent,
|
|
693
|
+
value: self.input.textContent,
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// Event
|
|
697
|
+
if (typeof(self.onbeforeinsert) === 'function') {
|
|
698
|
+
let elClass = self.el.classList;
|
|
699
|
+
elClass.add('lm-dropdown-loading');
|
|
700
|
+
let ret = await self.onbeforeinsert(self, s);
|
|
701
|
+
elClass.remove('lm-dropdown-loading');
|
|
702
|
+
if (ret === false) {
|
|
703
|
+
return;
|
|
704
|
+
} else if (ret) {
|
|
705
|
+
s = ret;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// Process the data
|
|
710
|
+
self.data.push(s);
|
|
711
|
+
// Select the new item
|
|
712
|
+
self.select(e, s);
|
|
713
|
+
// Close dropdown
|
|
714
|
+
self.close();
|
|
715
|
+
|
|
716
|
+
// Event
|
|
717
|
+
if (typeof(self.oninsert) === 'function') {
|
|
718
|
+
self.oninsert(self, s);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
return `<div class="lm-dropdown" data-insert="{{self.insert}}" data-type="{{self.type}}" data-state="{{self.state}}" :value="self.value">
|
|
637
723
|
<div class="lm-dropdown-header">
|
|
638
|
-
<div class="lm-dropdown-input" oninput="self.search" onfocus="self.open" onclick="self.click" placeholder="{{self.placeholder}}" :ref="self.input" tabindex="0"></div>
|
|
724
|
+
<div class="lm-dropdown-input" onpaste="self.onpaste" oninput="self.search" onfocus="self.open" onclick="self.click" placeholder="{{self.placeholder}}" :ref="self.input" tabindex="0"></div>
|
|
725
|
+
<div class="lm-dropdown-add" onmousedown="self.add"></div>
|
|
639
726
|
<button onclick="self.close" class="lm-dropdown-done">Done</button>
|
|
640
727
|
</div>
|
|
641
728
|
<div class="lm-dropdown-content">
|
package/dist/style.css
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
.lm-dropdown-header {
|
|
10
10
|
display: flex;
|
|
11
|
+
position: relative;
|
|
12
|
+
align-items: center;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
.lm-dropdown-header button {
|
|
@@ -35,8 +37,6 @@
|
|
|
35
37
|
|
|
36
38
|
.lm-dropdown-input {
|
|
37
39
|
padding: 5px 24px 5px 10px;
|
|
38
|
-
align-items: center;
|
|
39
|
-
position: relative;
|
|
40
40
|
white-space: nowrap;
|
|
41
41
|
overflow: hidden;
|
|
42
42
|
text-overflow: ellipsis;
|
|
@@ -52,20 +52,38 @@
|
|
|
52
52
|
content: '';
|
|
53
53
|
position: absolute;
|
|
54
54
|
width: 24px;
|
|
55
|
-
height:
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
height: 100%;
|
|
56
|
+
top: 0;
|
|
57
|
+
right: 4px;
|
|
58
58
|
background-repeat: no-repeat;
|
|
59
59
|
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");
|
|
60
|
+
background-position: center;
|
|
60
61
|
transition: transform .1s ease-in-out;
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
.lm-dropdown-add {
|
|
65
|
+
position: absolute;
|
|
66
|
+
padding: 10px;
|
|
67
|
+
right: 20px;
|
|
68
|
+
display: none;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.lm-dropdown-add::after {
|
|
72
|
+
content: '+';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.lm-dropdown[data-insert="true"] .lm-dropdown-input {
|
|
76
|
+
padding-right: 35px;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.lm-dropdown[data-state="true"][data-insert="true"] .lm-dropdown-add {
|
|
80
|
+
display: initial;
|
|
81
|
+
}
|
|
63
82
|
|
|
64
83
|
.lm-dropdown[data-state="true"] .lm-dropdown-input::after {
|
|
65
84
|
transform: rotate(-180deg);
|
|
66
85
|
}
|
|
67
86
|
|
|
68
|
-
|
|
69
87
|
.lm-dropdown-input:empty::before {
|
|
70
88
|
content: "\00a0";
|
|
71
89
|
}
|
|
@@ -193,6 +211,7 @@
|
|
|
193
211
|
display: initial;
|
|
194
212
|
border-left: 1px solid #ccc;
|
|
195
213
|
border-bottom: 1px solid #eee;
|
|
214
|
+
height: 100%;
|
|
196
215
|
}
|
|
197
216
|
|
|
198
217
|
.lm-dropdown[data-type="searchbar"][data-state="true"] .lm-dropdown-input {
|
|
@@ -208,6 +227,14 @@
|
|
|
208
227
|
height: 60px;
|
|
209
228
|
}
|
|
210
229
|
|
|
230
|
+
.lm-dropdown[data-type="searchbar"][data-state="true"] .lm-dropdown-input::after {
|
|
231
|
+
background: initial;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.lm-dropdown[data-type="searchbar"][data-state="true"] .lm-dropdown-add {
|
|
235
|
+
right: 100px;
|
|
236
|
+
}
|
|
237
|
+
|
|
211
238
|
.lm-dropdown[data-type="searchbar"] .lm-modal {
|
|
212
239
|
max-height: initial;
|
|
213
240
|
bottom: 0;
|
|
@@ -255,3 +282,27 @@
|
|
|
255
282
|
box-sizing: border-box;
|
|
256
283
|
width: 100%;
|
|
257
284
|
}
|
|
285
|
+
|
|
286
|
+
.lm-dropdown-loading .lm-dropdown-add::after {
|
|
287
|
+
content: '';
|
|
288
|
+
position: absolute;
|
|
289
|
+
width: 12px;
|
|
290
|
+
height: 12px;
|
|
291
|
+
margin-top: -7px;
|
|
292
|
+
margin-left: -12px;
|
|
293
|
+
border-style: solid;
|
|
294
|
+
border-color: #888;
|
|
295
|
+
border-top-color: transparent;
|
|
296
|
+
border-width: 1px;
|
|
297
|
+
border-radius: 50%;
|
|
298
|
+
animation: spin .8s linear infinite;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
@keyframes spin {
|
|
302
|
+
from {
|
|
303
|
+
transform:rotate(0deg);
|
|
304
|
+
}
|
|
305
|
+
to {
|
|
306
|
+
transform:rotate(360deg);
|
|
307
|
+
}
|
|
308
|
+
}
|
package/dist/vue.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Official Type definitions for the LemonadeJS plugins
|
|
3
|
+
* https://lemonadejs.net
|
|
4
|
+
* Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
|
5
|
+
*/
|
|
6
|
+
import Component from './index';
|
|
7
|
+
|
|
8
|
+
interface Dropdown {
|
|
9
|
+
(): any
|
|
10
|
+
[key: string]: any
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
declare function Dropdown<Dropdown>(props: Component.Options): any;
|
|
14
|
+
|
|
15
|
+
export default Dropdown;
|
package/package.json
CHANGED