@danielgindi/selectbox 2.0.26 → 2.0.28

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/lib/DropList.js CHANGED
@@ -114,7 +114,7 @@ const hasOwnProperty = Object.prototype.hasOwnProperty;
114
114
  /** */
115
115
 
116
116
  /** @type {DropList.Options} */
117
- let defaultOptions = {
117
+ export const DefaultOptions = {
118
118
  baseClassName: 'droplist',
119
119
 
120
120
  autoItemBlur: true,
@@ -173,7 +173,7 @@ class DropList {
173
173
  * @param {DropList.Options} options
174
174
  */
175
175
  constructor(options) {
176
- const o = { ...defaultOptions };
176
+ const o = { ...DefaultOptions };
177
177
 
178
178
  for (let [key, value] of Object.entries(/**@type Object*/options))
179
179
  if (value !== undefined)
package/lib/SelectBox.js CHANGED
@@ -78,6 +78,7 @@ const inputBackbufferCssProps = [
78
78
  * @property {string|string[]} [additionalClasses]
79
79
  * @property {'ltr'|'rtl'|'auto'} [direction='auto']
80
80
  * @property {boolean} [disabled=false] Should start as disabled?
81
+ * @property {boolean} [readonly=false] Should start as readonly?
81
82
  * @property {boolean} [clearable=true] Has clear button?
82
83
  * @property {boolean} [hasOpenIndicator=true] has open/close indicator?
83
84
  * @property {string} [placeholder=''] Placeholder text
@@ -96,6 +97,7 @@ const inputBackbufferCssProps = [
96
97
  * @property {string} [labelProp='label']
97
98
  * @property {string} [valueProp='value']
98
99
  * @property {string} [multiItemLabelProp='short_label']
100
+ * @property {'after'|'before'|'none'} [multiItemRemovePosition='after']
99
101
  * @property {number} [maxMultiItems] maximum number of multi items. The rest will get a single item to represent.
100
102
  * @property {function(count: number, items: DropList.ItemBase[]):string} [multiItemsRestLabelProvider] label for the item representing the rest of the items.
101
103
  * @property {DropList.ItemBase[]|null} [items] initial items
@@ -118,10 +120,11 @@ const inputBackbufferCssProps = [
118
120
  * @property {boolean} [closeListWhenLoading] whether we should close the list automatically when loading
119
121
  * @property {string[]} [clearInputWhen=['single_close','multi_select_single']] clear input box when closing the droplist or selecting <code>['single_close', 'multi_close', 'multi_select_single']</code>
120
122
  * */
121
- const defaultOptions = {
123
+ export const DefaultOptions = {
122
124
  el: null,
123
125
  baseClassName: 'selectbox',
124
126
  disabled: false,
127
+ readonly: false,
125
128
  clearable: true,
126
129
  hasOpenIndicator: true,
127
130
  placeholder: '',
@@ -145,6 +148,7 @@ const defaultOptions = {
145
148
  labelProp: 'label',
146
149
  valueProp: 'value',
147
150
  multiItemLabelProp: 'short_label',
151
+ multiItemRemovePosition: 'after',
148
152
  maxMultiItems: null,
149
153
  multiItemsRestLabelProvider: null,
150
154
  items: [],
@@ -203,7 +207,7 @@ class SelectBox {
203
207
  * @param {SelectBox.Options} options
204
208
  */
205
209
  constructor(options) {
206
- const o = { ...defaultOptions };
210
+ const o = { ...DefaultOptions };
207
211
 
208
212
  for (let [key, value] of Object.entries(/**@type Object*/options))
209
213
  if (value !== undefined)
@@ -219,6 +223,7 @@ class SelectBox {
219
223
  listOptions: o.listOptions,
220
224
 
221
225
  disabled: !!o.disabled,
226
+ readonly: !!o.readonly,
222
227
  clearable: !!o.clearable,
223
228
  hasOpenIndicator: !!o.hasOpenIndicator,
224
229
  placeholder: o.placeholder,
@@ -241,6 +246,7 @@ class SelectBox {
241
246
  labelProp: o.labelProp,
242
247
  valueProp: o.valueProp,
243
248
  multiItemLabelProp: o.multiItemLabelProp,
249
+ multiItemRemovePosition: o.multiItemRemovePosition,
244
250
 
245
251
  maxMultiItems: o.maxMultiItems,
246
252
  multiItemsRestLabelProvider: o.multiItemsRestLabelProvider,
@@ -300,6 +306,7 @@ class SelectBox {
300
306
  p.multiItemEls = [];
301
307
 
302
308
  this.enable(!p.disabled);
309
+ this.setReadOnly(!p.readonly);
303
310
 
304
311
  this._setupDropdownMenu();
305
312
 
@@ -526,6 +533,32 @@ class SelectBox {
526
533
  return this._p.disabled;
527
534
  }
528
535
 
536
+ /**
537
+ * Sets read only mode
538
+ * @param {boolean=true} readonly Should the control be read only?
539
+ * @returns {SelectBox}
540
+ */
541
+ setReadOnly(readonly) {
542
+ const p = this._p;
543
+
544
+ if (readonly === undefined) {
545
+ readonly = true;
546
+ }
547
+ p.readonly = !readonly;
548
+ p.el.setAttribute('aria-readonly', p.readonly.toString());
549
+ p.input.readonly = !!p.readonly;
550
+
551
+ return this;
552
+ }
553
+
554
+ /**
555
+ * Is the control enabled?
556
+ * @returns {boolean}
557
+ */
558
+ isReadOnly() {
559
+ return !this._p.readonly;
560
+ }
561
+
529
562
  /**
530
563
  * @param {string|string[]} classes
531
564
  * @returns {SelectBox}
@@ -1153,6 +1186,18 @@ class SelectBox {
1153
1186
  setMultiItemLabelProp(prop) {
1154
1187
  const p = this._p;
1155
1188
  p.multiItemLabelProp = prop;
1189
+ this._scheduleSync('render_items');
1190
+ return this;
1191
+ }
1192
+
1193
+ /**
1194
+ * @param {'before'|'after'|'none'} position
1195
+ * @returns {SelectBox}
1196
+ */
1197
+ setMultiItemRemovePosition(position) {
1198
+ const p = this._p;
1199
+ p.multiItemRemovePosition = position;
1200
+ this._scheduleSync('render_items');
1156
1201
  return this;
1157
1202
  }
1158
1203
 
@@ -1675,7 +1720,7 @@ class SelectBox {
1675
1720
  if (!closestUntil(evt.target, `.${p.baseClassName}__item_remove`, evt.currentTarget))
1676
1721
  return;
1677
1722
 
1678
- if (p.disabled) return;
1723
+ if (p.disabled || p.readonly) return;
1679
1724
 
1680
1725
  this._removeMultiItemFromEvent(
1681
1726
  /**@type Element*/
@@ -1728,6 +1773,7 @@ class SelectBox {
1728
1773
  if (p.hasOpenIndicator) {
1729
1774
  p.openIndicator = createElement('span', { class: `${p.baseClassName}__open_indicator` });
1730
1775
  p.el.appendChild(p.openIndicator);
1776
+ p.el.classList.add(`${p.baseClassName}__has_open_indicator`);
1731
1777
  } else {
1732
1778
  remove(p.openIndicator);
1733
1779
  delete p.openIndicator;
@@ -2042,7 +2088,7 @@ class SelectBox {
2042
2088
 
2043
2089
  setTimeout(() => {
2044
2090
  if (this[DestroyedSymbol]) return; // destroyed by event handler
2045
- if (p.disabled) return;
2091
+ if (p.disabled || p.readonly) return;
2046
2092
 
2047
2093
  this._trigger('search:blur');
2048
2094
 
@@ -2185,7 +2231,8 @@ class SelectBox {
2185
2231
  p.lastKeyAllowsNonTypeKeys &&
2186
2232
  !p.multi &&
2187
2233
  !dropList.hasFocusedItem() &&
2188
- !p.disabled
2234
+ !p.disabled &&
2235
+ !p.readonly
2189
2236
  )) {
2190
2237
  this.toggleList();
2191
2238
  evt.preventDefault();
@@ -2197,20 +2244,20 @@ class SelectBox {
2197
2244
  if (p.input) {
2198
2245
  p.sink
2199
2246
  .add(p.input, 'input.dropdown', () => {
2200
- if (p.disabled) return;
2247
+ if (p.disabled || p.readonly) return;
2201
2248
 
2202
2249
  p.filterTerm = p.input.value.trim();
2203
2250
  p.dropList?.setSearchTerm(p.filterTerm, true);
2204
2251
  })
2205
2252
  .add(p.input, 'click.dropdown', () => {
2206
- if (p.disabled) return;
2253
+ if (p.disabled || p.readonly) return;
2207
2254
 
2208
2255
  if (!p.multi && p.searchable) {
2209
2256
  this.openList();
2210
2257
  }
2211
2258
  })
2212
2259
  .add(p.input, 'focus.dropdown', () => {
2213
- if (p.disabled) return;
2260
+ if (p.disabled || p.readonly) return;
2214
2261
 
2215
2262
  this._trigger('search:focus');
2216
2263
 
@@ -2227,7 +2274,7 @@ class SelectBox {
2227
2274
 
2228
2275
  p.sink
2229
2276
  .add(p.el, 'mousedown.dropdown', () => {
2230
- if (!p.multi && !p.searchable && !avoidToggleFromClick && !p.disabled) {
2277
+ if (!p.multi && !p.searchable && !avoidToggleFromClick && !p.disabled && !p.readonly) {
2231
2278
  this.toggleList();
2232
2279
  }
2233
2280
  avoidToggleFromClick = false;
@@ -2236,7 +2283,7 @@ class SelectBox {
2236
2283
  if (currentTouchId) return;
2237
2284
  currentTouchId = evt.changedTouches[0].identifier;
2238
2285
 
2239
- if (this.isDisabled())
2286
+ if (this.isDisabled() || this.isReadOnly())
2240
2287
  return;
2241
2288
 
2242
2289
  if (closestUntil(evt.target, `.${p.baseClassName}__item,.${p.baseClassName}__clear`, p.el))
@@ -2676,7 +2723,7 @@ class SelectBox {
2676
2723
  p.el.classList.add(`${p.baseClassName}__has_clear`);
2677
2724
 
2678
2725
  p.sink.add(p.clearButton, 'click', () => {
2679
- if (this.isDisabled()) return;
2726
+ if (this.isDisabled() || this.isReadOnly()) return;
2680
2727
  this.clear();
2681
2728
  });
2682
2729
  }
@@ -2873,22 +2920,29 @@ class SelectBox {
2873
2920
  if (label === false)
2874
2921
  return null;
2875
2922
 
2923
+ const elRemove = createElement('span', {
2924
+ class: `${p.baseClassName}__item_remove`,
2925
+ role: 'presentation',
2926
+ });
2927
+
2876
2928
  const itemEl = createElement('li',
2877
2929
  {
2878
2930
  class: `${p.baseClassName}__item`,
2879
2931
  tabindex: '0',
2880
2932
  title: label,
2881
2933
  },
2882
- [
2883
- createElement('span', {
2884
- class: `${p.baseClassName}__item_remove`,
2885
- role: 'presentation',
2886
- }),
2887
- ],
2888
2934
  );
2889
2935
 
2936
+ if (p.multiItemRemovePosition === 'before') {
2937
+ itemEl.appendChild(elRemove);
2938
+ }
2939
+
2890
2940
  this._renderMultiItemContent(item, itemEl);
2891
2941
 
2942
+ if (p.multiItemRemovePosition === 'after') {
2943
+ itemEl.appendChild(elRemove);
2944
+ }
2945
+
2892
2946
  itemEl[ItemSymbol] = item;
2893
2947
 
2894
2948
  return itemEl;
@@ -3205,7 +3259,7 @@ class SelectBox {
3205
3259
  _handleMultiKeydown(event) {
3206
3260
  const p = this._p;
3207
3261
 
3208
- if (p.disabled) return;
3262
+ if (p.disabled || p.readonly) return;
3209
3263
 
3210
3264
  const isRtl = getComputedStyle(p.el).direction === 'rtl';
3211
3265
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danielgindi/selectbox",
3
- "version": "2.0.26",
3
+ "version": "2.0.28",
4
4
  "description": "A collection of dom utilities. So you can work natively with the dom without dom frameworks.",
5
5
  "main": "dist/lib.cjs.min.js",
6
6
  "module": "lib/index.js",
@@ -70,6 +70,14 @@ $spinner-transition-duration: .15s;
70
70
  }
71
71
  }
72
72
 
73
+ &[aria-readonly=true] {
74
+ cursor: default;
75
+
76
+ * {
77
+ cursor: default;
78
+ }
79
+ }
80
+
73
81
  &.has_droplist_above {
74
82
  border-top-left-radius: 0;
75
83
  border-top-right-radius: 0;
@@ -272,7 +280,7 @@ $spinner-transition-duration: .15s;
272
280
  left: calc(var(--selectbox-clear-spacing) + var(--selectbox-open-indicator-width));
273
281
  }
274
282
 
275
- .selectbox[aria-disabled=true] > & {
283
+ .selectbox[aria-disabled=true] > &, .selectbox[aria-readonly=true] > & {
276
284
  display: none;
277
285
  }
278
286
  }
@@ -375,16 +383,30 @@ $spinner-transition-duration: .15s;
375
383
  display: inline-block;
376
384
  font-weight: bold;
377
385
  margin-top: 0.2em;
378
- margin-right: var(--selectbox-multi-item-remove-spacing);
379
386
  font-size: 13px;
380
387
 
381
388
  &:before {
382
389
  content: '×';
383
390
  }
384
391
 
385
- @include insideRtlSelectBox {
392
+ &:first-child {
393
+ margin-right: var(--selectbox-multi-item-remove-spacing);
394
+ }
395
+
396
+ &:last-child {
386
397
  margin-left: var(--selectbox-multi-item-remove-spacing);
387
- margin-right: 0;
398
+ }
399
+
400
+ @include insideRtlSelectBox {
401
+ &:first-child {
402
+ margin-left: var(--selectbox-multi-item-remove-spacing);
403
+ margin-right: 0;
404
+ }
405
+
406
+ &:last-child {
407
+ margin-right: var(--selectbox-multi-item-remove-spacing);
408
+ margin-left: 0;
409
+ }
388
410
  }
389
411
  }
390
412
 
package/vue/DropList.vue CHANGED
@@ -19,136 +19,138 @@ const AllListEvents = [
19
19
  'subitems:select', 'subitems:blur',
20
20
  ];
21
21
 
22
+ export const PropTypes = {
23
+ baseClassName: {
24
+ type: String,
25
+ },
26
+ additionalClasses: {
27
+ type: [Object, Array, String],
28
+ },
29
+ direction: {
30
+ type: String,
31
+ default: undefined,
32
+ },
33
+ autoFocus: {
34
+ type: Boolean,
35
+ default: true,
36
+ },
37
+ autoItemBlur: {
38
+ type: Boolean,
39
+ default: true,
40
+ },
41
+ autoItemBlurDelay: {
42
+ type: Number,
43
+ default: 300,
44
+ },
45
+ capturesFocus: {
46
+ type: Boolean,
47
+ default: true,
48
+ },
49
+ multi: {
50
+ type: Boolean,
51
+ default: false,
52
+ },
53
+ isHeaderVisible: {
54
+ type: Boolean,
55
+ default: false,
56
+ },
57
+ searchable: {
58
+ type: Boolean,
59
+ default: false,
60
+ },
61
+ noResultsText: {
62
+ type: String,
63
+ default: 'No matching results',
64
+ },
65
+ filterThrottleWindow: {
66
+ type: Number,
67
+ default: 300,
68
+ },
69
+ filterOnEmptyTerm: {
70
+ type: Boolean,
71
+ default: false,
72
+ },
73
+ filterGroups: {
74
+ type: Boolean,
75
+ default: false,
76
+ },
77
+ filterEmptyGroups: {
78
+ type: Boolean,
79
+ default: false,
80
+ },
81
+ filterFn: {
82
+ type: Function,
83
+ default: undefined,
84
+ },
85
+ filterDependencies: {
86
+ type: [Array, String, Number, Boolean, Object],
87
+ default: undefined,
88
+ },
89
+ keyDownHandler: {
90
+ type: Function,
91
+ },
92
+ autoCheckGroupChildren: {
93
+ type: Boolean,
94
+ default: true,
95
+ },
96
+ useExactTargetWidth: {
97
+ type: Boolean,
98
+ default: false,
99
+ },
100
+ constrainToWindow: {
101
+ type: Boolean,
102
+ default: true,
103
+ },
104
+ autoFlipDirection: {
105
+ type: Boolean,
106
+ default: true,
107
+ },
108
+ estimatedItemHeight: {
109
+ type: Number,
110
+ default: 20,
111
+ },
112
+ estimateWidth: {
113
+ type: Boolean,
114
+ default: false,
115
+ },
116
+ virtualMinItems: {
117
+ type: Number,
118
+ default: 10,
119
+ },
120
+ labelProp: {
121
+ type: String,
122
+ default: 'label',
123
+ },
124
+ valueProp: {
125
+ type: String,
126
+ default: 'value',
127
+ },
128
+ items: {
129
+ type: Array,
130
+ default: () => [],
131
+ },
132
+ [isVue3 ? 'modelValue' : 'value']: { // Vue 2
133
+ type: [Number, String, Object, Array],
134
+ },
135
+ renderItem: {
136
+ type: Function,
137
+ },
138
+ unrenderItem: {
139
+ type: Function,
140
+ },
141
+ positionOptions: {
142
+ type: Object,
143
+ },
144
+ autoRelayoutOnItemsChange: {
145
+ type: Boolean,
146
+ default: true,
147
+ },
148
+ };
149
+
22
150
  export default {
23
151
  inheritAttrs: false,
24
152
 
25
- props: {
26
- baseClassName: {
27
- type: String,
28
- },
29
- additionalClasses: {
30
- type: [Object, Array, String],
31
- },
32
- direction: {
33
- type: String,
34
- default: undefined,
35
- },
36
- autoFocus: {
37
- type: Boolean,
38
- default: true,
39
- },
40
- autoItemBlur: {
41
- type: Boolean,
42
- default: true,
43
- },
44
- autoItemBlurDelay: {
45
- type: Number,
46
- default: 300,
47
- },
48
- capturesFocus: {
49
- type: Boolean,
50
- default: true,
51
- },
52
- multi: {
53
- type: Boolean,
54
- default: false,
55
- },
56
- isHeaderVisible: {
57
- type: Boolean,
58
- default: false,
59
- },
60
- searchable: {
61
- type: Boolean,
62
- default: false,
63
- },
64
- noResultsText: {
65
- type: String,
66
- default: 'No matching results',
67
- },
68
- filterThrottleWindow: {
69
- type: Number,
70
- default: 300,
71
- },
72
- filterOnEmptyTerm: {
73
- type: Boolean,
74
- default: false,
75
- },
76
- filterGroups: {
77
- type: Boolean,
78
- default: false,
79
- },
80
- filterEmptyGroups: {
81
- type: Boolean,
82
- default: false,
83
- },
84
- filterFn: {
85
- type: Function,
86
- default: undefined,
87
- },
88
- // eslint-disable-next-line vue/require-prop-types
89
- filterDependencies: {
90
- default: undefined,
91
- },
92
- keyDownHandler: {
93
- type: Function,
94
- },
95
- autoCheckGroupChildren: {
96
- type: Boolean,
97
- default: true,
98
- },
99
- useExactTargetWidth: {
100
- type: Boolean,
101
- default: false,
102
- },
103
- constrainToWindow: {
104
- type: Boolean,
105
- default: true,
106
- },
107
- autoFlipDirection: {
108
- type: Boolean,
109
- default: true,
110
- },
111
- estimatedItemHeight: {
112
- type: Number,
113
- default: 20,
114
- },
115
- estimateWidth: {
116
- type: Boolean,
117
- default: false,
118
- },
119
- virtualMinItems: {
120
- type: Number,
121
- default: 10,
122
- },
123
- labelProp: {
124
- type: String,
125
- default: 'label',
126
- },
127
- valueProp: {
128
- type: String,
129
- default: 'value',
130
- },
131
- items: {
132
- type: Array,
133
- default: () => [],
134
- },
135
- [isVue3 ? 'modelValue' : 'value']: { // Vue 2
136
- type: [Number, String, Object, Array],
137
- },
138
- renderItem: {
139
- type: Function,
140
- },
141
- unrenderItem: {
142
- type: Function,
143
- },
144
- positionOptions: {
145
- type: Object,
146
- },
147
- autoRelayoutOnItemsChange: {
148
- type: Boolean,
149
- default: true,
150
- },
151
- },
153
+ props: PropTypes,
152
154
 
153
155
  emits: [
154
156
  // vue events