@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 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's width */
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?: (el: HTMLElement, obj: object, oldValue: string, newValue: string) => void;
47
+ onchange?: (obj: object, newValue: string|number) => void;
46
48
  /** Event handler for when the dropdown is ready */
47
- onload?: (el: HTMLElement, obj: object, data: any, val: any) => void;
49
+ onload?: (obj: object) => void;
48
50
  /** Event handler for when the dropdown opens */
49
- onopen?: (el: HTMLElement) => void;
51
+ onopen?: (obj: object) => void;
50
52
  /** Event handler for when the dropdown closes */
51
- onclose?: (el: HTMLElement) => void;
52
- /** Event handler for when a new option is added to the dropdown */
53
- oninsert?: (obj: object, item: Item, newItem: Item) => void;
54
- /** Event handler for just before a new option is added to the dropdown */
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 before a search on autocomplete is performed */
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 = y + (el.offsetHeight || self.height);
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 += adjust;
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) < 24) {
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
- return `<div class="lm-dropdown" data-type="{{self.type}}" data-state="{{self.state}}">
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: 24px;
56
- right: 2px;
57
- top: 2px;
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
@@ -15,9 +15,9 @@
15
15
  ],
16
16
  "dependencies": {
17
17
  "lemonadejs": "^4.0.7",
18
- "@lemonadejs/modal": "^2.4.6"
18
+ "@lemonadejs/modal": "^2.6.1"
19
19
  },
20
20
  "main": "dist/index.js",
21
21
  "types": "dist/index.d.ts",
22
- "version": "3.0.12"
22
+ "version": "3.1.0"
23
23
  }