@ekzo-dev/bootstrap-addons 5.2.24 → 5.2.25

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ekzo-dev/bootstrap-addons",
3
3
  "description": "Aurelia Bootstrap additional component",
4
- "version": "5.2.24",
4
+ "version": "5.2.25",
5
5
  "homepage": "https://github.com/ekzo-dev/aurelia-components/tree/main/packages/bootstrap-addons",
6
6
  "repository": {
7
7
  "type": "git",
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
- "@ekzo-dev/bootstrap": "^5.2.18",
12
+ "@ekzo-dev/bootstrap": "^5.2.19",
13
13
  "@ekzo-dev/vanilla-jsoneditor": "^0.23.7",
14
14
  "@ekzo-dev/toolkit": "^1.2.4",
15
15
  "@fortawesome/free-solid-svg-icons": "^6.5.2",
@@ -5,11 +5,13 @@ import { valueConverter } from 'aurelia';
5
5
  export class Filter {
6
6
  toView(
7
7
  list: Map<string, ISelectOption[]> | ISelectOption[],
8
- search: string
8
+ search: string,
9
+ emptyOption?: ISelectOption
9
10
  ): Map<string, ISelectOption[]> | ISelectOption[] {
10
- if (search === '') return list;
11
+ if (search === '' && emptyOption === undefined) return list;
11
12
 
12
- const cb = (option: { text: string }) => option.text.toLowerCase().includes(search.toLowerCase());
13
+ const cb = (option: ISelectOption) =>
14
+ option.value !== emptyOption?.value && option.text.toLowerCase().includes(search.toLowerCase());
13
15
 
14
16
  if (list instanceof Map) {
15
17
  const result = new Map<string, ISelectOption[]>();
@@ -28,7 +28,8 @@
28
28
  id.bind="id"
29
29
  class="form-select ${bsSize ? `form-select-${bsSize}` : ''} ${valid ? 'is-valid' : valid === false ? 'is-invalid' : ''}"
30
30
  bs-dropdown-toggle="arrow.bind: false"
31
- value="${selectedOption?.group ? selectedOption.group + ' / ' : ''}${selectedOption?.text ?? ''}"
31
+ value="${valueText}"
32
+ placeholder="${emptyOption?.text ?? ''}"
32
33
  disabled.bind="disabled"
33
34
  required.bind="required"
34
35
  form.bind="form & attr"
@@ -38,6 +39,12 @@
38
39
  keydown.trigger="$event.preventDefault()"
39
40
  ref="control"
40
41
  />
42
+ <button
43
+ if.bind="emptyOption && selectedOption?.value !== emptyOption?.value"
44
+ bs-close-button
45
+ type="button"
46
+ click.trigger="selectOption(emptyOption)"
47
+ ></button>
41
48
  <bs-dropdown-menu>
42
49
  <div bs-dropdown-item="text" if.bind="optionsCount > 10">
43
50
  <input class="form-control" placeholder="Filter options" type="search" value.bind="filter & debounce:250" />
@@ -45,19 +52,19 @@
45
52
  <hr bs-dropdown-item="divider" if.bind="optionsCount > 10" />
46
53
  <button
47
54
  type="button"
48
- repeat.for="option of ungroupedOptions | filter:filter"
49
- bs-dropdown-item="active.bind: option.value === selectedOption.value; disabled.bind: option.disabled"
55
+ repeat.for="option of ungroupedOptions | filter:filter:emptyOption"
56
+ bs-dropdown-item="active.bind: option.value === selectedOption?.value; disabled.bind: option.disabled"
50
57
  click.trigger="selectOption(option)"
51
58
  >
52
59
  ${option.text || '&nbsp;'}
53
60
  </button>
54
- <template repeat.for="[k, v] of groupedOptions | filter:filter">
61
+ <template repeat.for="[k, v] of groupedOptions | filter:filter:emptyOption">
55
62
  <h6 bs-dropdown-item="header">${k}</h6>
56
63
  <button
57
64
  class="ps-4"
58
65
  type="button"
59
66
  repeat.for="option of v"
60
- bs-dropdown-item="active.bind: option.value === selectedOption.value; disabled.bind: option.disabled"
67
+ bs-dropdown-item="active.bind: option.value === selectedOption?.value; disabled.bind: option.disabled"
61
68
  click.trigger="selectOption(option)"
62
69
  >
63
70
  ${option.text || '&nbsp;'}
@@ -1,17 +1,40 @@
1
- @import '~bootstrap/scss/functions';
2
- @import '~bootstrap/scss/variables';
3
- @import '~bootstrap/scss/maps';
4
- @import '~bootstrap/scss/mixins';
5
- @import '~bootstrap/scss/forms/form-select';
6
- @import '~bootstrap/scss/forms/form-check';
1
+ @import 'bootstrap/scss/functions';
2
+ @import 'bootstrap/scss/variables';
3
+ @import 'bootstrap/scss/maps';
4
+ @import 'bootstrap/scss/mixins';
5
+ @import 'bootstrap/scss/forms/form-check';
7
6
 
8
7
  bs-select {
9
- display: block;
10
-
11
- > .form-select {
8
+ .form-select {
12
9
  cursor: default;
13
10
  }
14
11
 
12
+ &:has(.btn-close) .form-select {
13
+ padding-right: 4rem;
14
+ }
15
+
16
+ .btn-close {
17
+ display: block;
18
+ position: relative;
19
+ box-sizing: border-box;
20
+ width: 0.8rem;
21
+ height: 0.8rem;
22
+ top: calc(-1px - 1.5rem);
23
+ left: calc(100% - 3.25rem);
24
+ }
25
+
26
+ .form-select-lg + .btn-close {
27
+ top: calc(-1px - 1.8rem);
28
+ }
29
+
30
+ .form-select-sm + .btn-close {
31
+ top: calc(-1px - 1.3rem);
32
+ }
33
+
34
+ &.form-floating .btn-close {
35
+ top: calc(-1px - 2.15rem) !important;
36
+ }
37
+
15
38
  .dropdown-menu {
16
39
  width: max-content;
17
40
  min-width: 100%;
@@ -76,3 +99,16 @@ bs-select {
76
99
  }
77
100
  }
78
101
  }
102
+
103
+ /* stylelint-disable */
104
+ .was-validated bs-select:has(.btn-close) .form-select:invalid,
105
+ .was-validated bs-select:has(.btn-close) .form-select:valid,
106
+ bs-select:has(.btn-close) .form-select.is-invalid,
107
+ bs-select:has(.btn-close) .form-select.is-valid {
108
+ padding-right: 5.5rem !important;
109
+
110
+ + .btn-close {
111
+ left: calc(100% - 4.75rem);
112
+ }
113
+ }
114
+ /* stylelint-enable */
@@ -15,12 +15,14 @@ export default {
15
15
  args: {
16
16
  label: 'Label',
17
17
  options: [
18
- { value: undefined, text: '' },
18
+ { value: undefined, text: 'Select option' },
19
19
  { value: '1', text: 'One', disabled: true },
20
20
  { value: '2', text: 'Two' },
21
21
  { value: '3', text: 'Three', group: 'Group' },
22
22
  ],
23
23
  value: '2',
24
+ floatingLabel: false,
25
+ valid: null,
24
26
  },
25
27
  argTypes: {
26
28
  bsSize: {
@@ -3,6 +3,7 @@ import template from './select.html';
3
3
  import './select.scss';
4
4
 
5
5
  import {
6
+ BsCloseButton,
6
7
  BsDropdown,
7
8
  BsDropdownItem,
8
9
  BsDropdownMenu,
@@ -10,7 +11,7 @@ import {
10
11
  BsSelect as BaseBsSelect,
11
12
  ISelectOption,
12
13
  } from '@ekzo-dev/bootstrap';
13
- import { customElement, ICustomElementViewModel } from 'aurelia';
14
+ import { bindable, customElement, ICustomElementViewModel } from 'aurelia';
14
15
 
15
16
  import { Filter } from './filter';
16
17
 
@@ -22,9 +23,12 @@ const BS_SIZE_MULTIPLIER = {
22
23
  @customElement({
23
24
  name: 'bs-select',
24
25
  template,
25
- dependencies: [BsDropdown, BsDropdownMenu, BsDropdownToggle, BsDropdownItem, Filter],
26
+ dependencies: [BsDropdown, BsDropdownMenu, BsDropdownToggle, BsDropdownItem, Filter, BsCloseButton],
26
27
  })
27
28
  export class BsSelect extends BaseBsSelect implements ICustomElementViewModel {
29
+ @bindable()
30
+ emptyValue?: unknown = null;
31
+
28
32
  control!: HTMLFieldSetElement;
29
33
 
30
34
  filter: string = '';
@@ -33,6 +37,8 @@ export class BsSelect extends BaseBsSelect implements ICustomElementViewModel {
33
37
 
34
38
  deactivating: boolean = false;
35
39
 
40
+ emptyOption?: ISelectOption;
41
+
36
42
  binding() {
37
43
  super.binding();
38
44
  this.deactivating = false;
@@ -67,6 +73,14 @@ export class BsSelect extends BaseBsSelect implements ICustomElementViewModel {
67
73
  this.#dispatchEvents();
68
74
  }
69
75
 
76
+ get valueText(): string {
77
+ const { selectedOption, emptyOption } = this;
78
+
79
+ return emptyOption && emptyOption.value === selectedOption?.value
80
+ ? ''
81
+ : `${selectedOption?.group ? selectedOption.group + ' / ' : ''}${selectedOption?.text ?? ''}`;
82
+ }
83
+
70
84
  #dispatchEvents() {
71
85
  const change = new Event('change', { bubbles: true });
72
86
  const input = new Event('input', { bubbles: true });
@@ -102,8 +116,9 @@ export class BsSelect extends BaseBsSelect implements ICustomElementViewModel {
102
116
  get selectedOption(): ISelectOption | undefined {
103
117
  if (this['__raw__'].deactivating) return;
104
118
 
105
- const { matcher, value } = this;
119
+ const { matcher, value, emptyValue } = this;
106
120
  let { options } = this;
121
+ let emptyOption: ISelectOption;
107
122
 
108
123
  if (options instanceof Object && options.constructor === Object) {
109
124
  options = Object.entries(options);
@@ -115,12 +130,19 @@ export class BsSelect extends BaseBsSelect implements ICustomElementViewModel {
115
130
  let option = (options as Array<ISelectOption | readonly [unknown, string]>).find((option) => {
116
131
  const currentValue: unknown = isEntries ? option[0] : (option as ISelectOption).value;
117
132
 
133
+ if (currentValue == emptyValue) {
134
+ emptyOption = {
135
+ value: currentValue,
136
+ text: isEntries ? option[1] : (option as ISelectOption).text,
137
+ } as ISelectOption;
138
+ }
139
+
118
140
  return matcher ? matcher(value, currentValue) : value === currentValue;
119
141
  });
120
142
 
121
143
  option = isEntries && option !== undefined ? { value: option[0], text: option[1] } : (option as ISelectOption);
122
144
 
123
- // update value next tick if it differs from current
145
+ // update value next tick
124
146
  const foundValue = option?.value;
125
147
 
126
148
  if (foundValue !== value) {
@@ -128,6 +150,9 @@ export class BsSelect extends BaseBsSelect implements ICustomElementViewModel {
128
150
  void Promise.resolve().then(() => (this.value = foundValue));
129
151
  }
130
152
 
153
+ // update empty option next tick
154
+ void Promise.resolve().then(() => (this.emptyOption = emptyOption));
155
+
131
156
  return option;
132
157
  }
133
158
  }