@lemonadejs/dropdown 3.0.13 → 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 CHANGED
@@ -29,6 +29,8 @@ To use Dropdown via a CDN, include the following script tags in your HTML file:
29
29
 
30
30
  ```html
31
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" />
32
34
  <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@lemonadejs/dropdown/dist/index.min.js"></script>
33
35
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lemonadejs/dropdown/dist/style.min.css" />
34
36
  ```
@@ -69,6 +71,7 @@ export default function App() {
69
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. |
70
72
  | value?: string | Represents the current value or selected option in the Dropdown. |
71
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. |
72
75
 
73
76
  ### Item Details
74
77
 
@@ -79,6 +82,14 @@ export default function App() {
79
82
  | text?: string | Displays the label or text associated with the option. |
80
83
  | image?: string | Specifies the image URL to be displayed alongside the option. |
81
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
+
82
93
  ## License
83
94
 
84
95
  The [LemonadeJS](https://lemonadejs.net) LemonadeJS Dropdown is released under the MIT.
package/dist/index.d.ts CHANGED
@@ -35,12 +35,14 @@ 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
47
  onchange?: (obj: object, newValue: string|number) => void;
46
48
  /** Event handler for when the dropdown is ready */
@@ -49,11 +51,22 @@ declare namespace Dropdown {
49
51
  onopen?: (obj: object) => void;
50
52
  /** Event handler for when the dropdown closes */
51
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
+ * */
66
+ onbeforeinsert?: (obj: object, item: Item) => void;
52
67
  /** Event handler for when a new option is added to the dropdown */
53
68
  oninsert?: (obj: object, item: Item) => void;
54
- /** Event handler for just before a new option is added to the dropdown */
55
- onbeforeinsert?: (obj: object, item: Item) => void;
56
- /** Event handler for before a search on autocomplete is performed */
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
  }
@@ -470,7 +479,7 @@ if (!Modal && typeof (require) === 'function') {
470
479
  }
471
480
  }
472
481
 
473
- self.search = function() {
482
+ self.search = function(e) {
474
483
  if (self.state && self.autocomplete) {
475
484
  // Filter options
476
485
  let data;
@@ -529,7 +538,7 @@ if (!Modal && typeof (require) === 'function') {
529
538
  } else {
530
539
  x = e.clientX;
531
540
  }
532
- if (e.target.offsetWidth - (x - e.target.offsetLeft) < 24) {
541
+ if (e.target.offsetWidth - (x - e.target.offsetLeft) < 20) {
533
542
  self.toggle();
534
543
  } else {
535
544
  self.open();
@@ -584,6 +593,8 @@ if (!Modal && typeof (require) === 'function') {
584
593
  width: self.width,
585
594
  onopen: onopen,
586
595
  onclose: onclose,
596
+ position: 'absolute',
597
+ 'auto-adjust': true,
587
598
  'auto-close': false,
588
599
  };
589
600
  // Generate modal
@@ -653,9 +664,65 @@ if (!Modal && typeof (require) === 'function') {
653
664
  }
654
665
  }
655
666
 
656
- return `<div class="lm-dropdown" data-type="{{self.type}}" data-state="{{self.state}}" :value="self.value">
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">
657
723
  <div class="lm-dropdown-header">
658
- <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>
659
726
  <button onclick="self.close" class="lm-dropdown-done">Done</button>
660
727
  </div>
661
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.9"
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.13"
22
+ "version": "3.1.0"
23
23
  }