@ekzo-dev/bootstrap-addons 5.2.27 → 5.2.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/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.27",
4
+ "version": "5.2.28",
5
5
  "homepage": "https://github.com/ekzo-dev/aurelia-components/tree/main/packages/bootstrap-addons",
6
6
  "repository": {
7
7
  "type": "git",
@@ -1,7 +1,4 @@
1
- @import 'bootstrap/scss/functions';
2
- @import 'bootstrap/scss/variables';
3
- @import 'bootstrap/scss/maps';
4
- @import 'bootstrap/scss/mixins';
1
+ @import '@ekzo-dev/bootstrap/src/utils';
5
2
  @import 'bootstrap/scss/forms/form-control';
6
3
 
7
4
  bs-duration-input {
@@ -11,7 +11,7 @@ export class Filter {
11
11
  if (search === '' && emptyOption === undefined) return list;
12
12
 
13
13
  const cb = (option: ISelectOption) =>
14
- option.value !== emptyOption?.value && option.text.toLowerCase().includes(search.toLowerCase());
14
+ option.value !== emptyOption?.value && (option.text ?? '').toLowerCase().includes(search.toLowerCase());
15
15
 
16
16
  if (list instanceof Map) {
17
17
  const result = new Map<string, ISelectOption[]>();
@@ -1,55 +1,65 @@
1
- <template class="${floatingLabel ? 'form-floating' : ''} ${!multiple ? 'dropdown' : ''}">
1
+ <template class="addon ${floatingLabel ? 'form-floating' : ''}" bs-dropdown>
2
2
  <label for="${id}" if.bind="label && !floatingLabel" class="form-label">${label}</label>
3
- <fieldset
4
- if.bind="multiple"
5
- class="form-control ${bsSize ? `form-control-${bsSize}` : ''} ${valid ? 'is-valid' : valid === false ? 'is-invalid' : ''}"
6
- title.bind="title & attr"
7
- name.bind="name & attr"
8
- form.bind="form & attr"
3
+ <input
4
+ id="${id}"
5
+ class="form-select ${bsSize ? `form-select-${bsSize}` : ''} ${valid ? 'is-valid' : valid === false ? 'is-invalid' : ''}"
6
+ bs-dropdown-toggle="arrow.bind: false"
7
+ value="${valueText}"
8
+ placeholder="${emptyOption?.text ?? ''}"
9
9
  disabled.bind="disabled"
10
+ required.bind="required"
11
+ form.bind="form & attr"
12
+ name.bind="name & attr"
13
+ title.bind="title & attr"
14
+ autocomplete="off"
15
+ keydown.trigger="$event.preventDefault()"
10
16
  ref="control"
11
- id="${id}"
12
- >
13
- <select if.bind="required && !value?.length" multiple required></select>
14
- <div class="form-check" repeat.for="option of ungroupedOptions">
15
- <input
16
- class="form-check-input"
17
- type="checkbox"
18
- checked.bind="value"
19
- model.bind="option.value"
20
- disabled.bind="option.disabled"
21
- id="${id+$index}"
22
- />
23
- <label class="form-check-label" for="${id+$index}">${option.text}</label>
24
- </div>
25
- </fieldset>
26
- <template else>
27
- <input
28
- id.bind="id"
29
- class="form-select ${bsSize ? `form-select-${bsSize}` : ''} ${valid ? 'is-valid' : valid === false ? 'is-invalid' : ''}"
30
- bs-dropdown-toggle="arrow.bind: false"
31
- value="${valueText}"
32
- placeholder="${emptyOption?.text ?? ''}"
33
- disabled.bind="disabled"
34
- required.bind="required"
35
- form.bind="form & attr"
36
- name.bind="name & attr"
37
- title.bind="title & attr"
38
- autocomplete="off"
39
- keydown.trigger="$event.preventDefault()"
40
- ref="control"
41
- />
42
- <button
43
- if.bind="emptyOption && selectedOption?.value !== emptyOption?.value && !disabled"
44
- bs-close-button
45
- type="button"
46
- click.trigger="selectOption(emptyOption)"
47
- ></button>
48
- <bs-dropdown-menu popper-config.bind="popperConfig">
49
- <div bs-dropdown-item="text" if.bind="optionsCount > 10">
17
+ />
18
+ <button if.bind="showClear" bs-close-button type="button" click.trigger="clear()"></button>
19
+ <bs-dropdown-menu popper-config.bind="popperConfig" auto-close.bind="multiple ? 'outside' : true">
20
+ <template if.bind="optionsCount > 10">
21
+ <div bs-dropdown-item="text">
50
22
  <input class="form-control" placeholder="Filter options" type="search" value.bind="filter & debounce:250" />
51
23
  </div>
52
- <hr bs-dropdown-item="divider" if.bind="optionsCount > 10" />
24
+ <hr bs-dropdown-item="divider" />
25
+ </template>
26
+ <template if.bind="multiple">
27
+ <div
28
+ repeat.for="option of ungroupedOptions | filter:filter"
29
+ class="dropdown-item ${option.disabled ? 'disabled' : ''}"
30
+ >
31
+ <div class="form-check">
32
+ <input
33
+ class="form-check-input"
34
+ type="checkbox"
35
+ checked.bind="value"
36
+ model.bind="option.value"
37
+ disabled.bind="option.disabled"
38
+ matcher.bind="matcher"
39
+ id="${optionId($index)}"
40
+ />
41
+ <label class="form-check-label" for="${optionId($index)}">${option.text || '&nbsp;'}</label>
42
+ </div>
43
+ </div>
44
+ <template repeat.for="[k, v] of groupedOptions | filter:filter">
45
+ <h6 bs-dropdown-item="header">${k}</h6>
46
+ <div repeat.for="option of v" class="dropdown-item ps-4 ${option.disabled ? 'disabled' : ''}">
47
+ <div class="form-check">
48
+ <input
49
+ class="form-check-input"
50
+ type="checkbox"
51
+ checked.bind="value"
52
+ model.bind="option.value"
53
+ disabled.bind="option.disabled"
54
+ matcher.bind="matcher"
55
+ id="${optionId($index, $parent.$index)}"
56
+ />
57
+ <label class="form-check-label" for="${optionId($index, $parent.$index)}">${option.text || '&nbsp;'}</label>
58
+ </div>
59
+ </div>
60
+ </template>
61
+ </template>
62
+ <template else>
53
63
  <button
54
64
  type="button"
55
65
  repeat.for="option of ungroupedOptions | filter:filter:emptyOption"
@@ -71,8 +81,8 @@
71
81
  ${option.text || '&nbsp;'}
72
82
  </button>
73
83
  </template>
74
- </bs-dropdown-menu>
75
- </template>
84
+ </template>
85
+ </bs-dropdown-menu>
76
86
  <label for="${id}" if.bind="label && floatingLabel"><span>${label}</span></label>
77
87
  <div class="invalid-feedback" if.bind="invalidFeedback">${invalidFeedback}</div>
78
88
  <div class="valid-feedback" if.bind="validFeedback">${validFeedback}</div>
@@ -1,10 +1,7 @@
1
- @import 'bootstrap/scss/functions';
2
- @import 'bootstrap/scss/variables';
3
- @import 'bootstrap/scss/maps';
4
- @import 'bootstrap/scss/mixins';
1
+ @import '@ekzo-dev/bootstrap/src/utils';
5
2
  @import 'bootstrap/scss/forms/form-check';
6
3
 
7
- bs-select {
4
+ bs-select.addon {
8
5
  .form-select {
9
6
  cursor: default;
10
7
  }
@@ -49,63 +46,20 @@ bs-select {
49
46
  white-space: normal;
50
47
  }
51
48
 
52
- > .form-control {
53
- overflow: auto;
54
- position: relative;
55
-
56
- .form-check:last-of-type {
57
- margin-bottom: 0;
58
- }
59
-
60
- > select {
61
- position: absolute;
62
- height: 100%;
63
- left: 0;
64
- bottom: 0;
65
- opacity: 0;
66
- pointer-events: none;
67
- }
68
- }
69
-
70
- &.form-floating:not(.dropdown) {
71
- > label {
72
- height: auto;
73
- width: auto;
74
- transform: none !important;
75
- left: $input-border-width;
76
- top: $input-border-width;
77
- right: 20px;
78
- font-size: 85%;
79
- padding-top: 7px;
80
- padding-bottom: 5px;
81
- opacity: 1 !important;
82
- background-color: $input-bg;
83
-
84
- @include border-radius($input-border-radius, 0);
85
-
86
- span {
87
- opacity: 0.65;
88
- }
89
- }
90
-
91
- > .form-control {
92
- min-height: add($form-floating-height, 0.5rem);
93
- height: auto;
94
- padding-top: add($form-floating-input-padding-t, 0.5rem) !important;
95
- padding-bottom: $input-padding-y !important;
49
+ .form-check {
50
+ margin-bottom: 0;
96
51
 
97
- &:disabled + label {
98
- background-color: $input-disabled-bg;
99
- }
52
+ label {
53
+ display: block;
100
54
  }
101
55
  }
102
56
  }
103
57
 
104
58
  /* stylelint-disable */
105
- .was-validated bs-select:has(.btn-close) .form-select:invalid,
106
- .was-validated bs-select:has(.btn-close) .form-select:valid,
107
- bs-select:has(.btn-close) .form-select.is-invalid,
108
- bs-select:has(.btn-close) .form-select.is-valid {
59
+ .was-validated bs-select.addon:has(.btn-close) .form-select:invalid,
60
+ .was-validated bs-select.addon:has(.btn-close) .form-select:valid,
61
+ bs-select.addon:has(.btn-close) .form-select.is-invalid,
62
+ bs-select.addon:has(.btn-close) .form-select.is-valid {
109
63
  padding-right: 5.5rem !important;
110
64
 
111
65
  + .btn-close {
@@ -15,13 +15,6 @@ export default {
15
15
  },
16
16
  args: {
17
17
  label: 'Label',
18
- options: [
19
- { value: undefined, text: 'Select option' },
20
- { value: '1', text: 'One', disabled: true },
21
- { value: '2', text: 'Two' },
22
- { value: '3', text: 'Three', group: 'Group' },
23
- ],
24
- value: '2',
25
18
  floatingLabel: false,
26
19
  valid: null,
27
20
  },
@@ -37,6 +30,16 @@ const Overview: Story = (args) => ({
37
30
  props: args,
38
31
  });
39
32
 
33
+ Overview.args = {
34
+ options: [
35
+ { value: undefined, text: 'Select option' },
36
+ { value: '1', text: 'One', disabled: true },
37
+ { value: '2', text: 'Two' },
38
+ { value: '3', text: 'Three', group: 'Group' },
39
+ ],
40
+ value: '2',
41
+ };
42
+
40
43
  const Multiple: Story = (args) => ({
41
44
  props: args,
42
45
  });
@@ -44,6 +47,11 @@ const Multiple: Story = (args) => ({
44
47
  Multiple.args = {
45
48
  multiple: true,
46
49
  value: ['2', '3'],
50
+ options: [
51
+ { value: '1', text: 'One', disabled: true },
52
+ { value: '2', text: 'Two' },
53
+ { value: '3', text: 'Three', group: 'Group' },
54
+ ],
47
55
  };
48
56
 
49
57
  const LargeOptions: Story = (args) => ({
@@ -18,11 +18,6 @@ import { bindable, customElement, ICustomElementViewModel, resolve } from 'aurel
18
18
 
19
19
  import { Filter } from './filter';
20
20
 
21
- const BS_SIZE_MULTIPLIER = {
22
- lg: 1.125,
23
- sm: 0.875,
24
- };
25
-
26
21
  @customElement({
27
22
  name: 'bs-select',
28
23
  template,
@@ -49,6 +44,10 @@ export class BsSelect extends BaseBsSelect implements ICustomElementViewModel {
49
44
  binding() {
50
45
  super.binding();
51
46
  this.deactivating = false;
47
+
48
+ if (this.multiple && !Array.isArray(this.value)) {
49
+ this.value = [];
50
+ }
52
51
  }
53
52
 
54
53
  unbinding() {
@@ -56,30 +55,10 @@ export class BsSelect extends BaseBsSelect implements ICustomElementViewModel {
56
55
  }
57
56
 
58
57
  attached() {
59
- if (this.multiple) {
60
- this.#setHeight();
61
- this.#scrollToSelected();
62
- }
63
-
64
58
  this.setPopperConfig();
65
59
  }
66
60
 
67
- propertyChanged(name: keyof this) {
68
- switch (name) {
69
- case 'size':
70
-
71
- case 'bsSize':
72
-
73
- case 'floatingLabel':
74
- if (this.multiple) {
75
- setTimeout(() => this.#setHeight());
76
- }
77
- }
78
- }
79
-
80
61
  setPopperConfig() {
81
- if (this.multiple) return;
82
-
83
62
  const { host } = this;
84
63
  const parentModal = host.closest('.modal-body,.popover-body,.offcanvas-body');
85
64
  const dropdownMenu: HTMLElement = host.querySelector('.dropdown-menu');
@@ -95,12 +74,20 @@ export class BsSelect extends BaseBsSelect implements ICustomElementViewModel {
95
74
  }
96
75
  }
97
76
 
77
+ optionId(index: number, parentIndex?: number): string {
78
+ return `${this.id}${parentIndex != null ? '-g' + parentIndex.toString() : ''}-${index}`;
79
+ }
80
+
98
81
  selectOption(option: ISelectOption) {
99
82
  this.value = option.value;
100
83
  this.#dispatchEvents();
101
84
  }
102
85
 
103
86
  get valueText(): string {
87
+ // if (this.multiple) {
88
+ //
89
+ // }
90
+
104
91
  const { selectedOption, emptyOption } = this;
105
92
 
106
93
  return emptyOption && emptyOption.value === selectedOption?.value
@@ -108,6 +95,18 @@ export class BsSelect extends BaseBsSelect implements ICustomElementViewModel {
108
95
  : `${selectedOption?.group ? selectedOption.group + ' / ' : ''}${selectedOption?.text ?? ''}`;
109
96
  }
110
97
 
98
+ get showClear(): boolean {
99
+ return (
100
+ !this.disabled &&
101
+ ((this.emptyOption && this.selectedOption?.value !== this.emptyOption.value) ||
102
+ (this.multiple && (this.value as unknown[]).length > 0))
103
+ );
104
+ }
105
+
106
+ clear() {
107
+ this.selectOption(this.multiple ? { value: [], text: '' } : this.emptyOption);
108
+ }
109
+
111
110
  #dispatchEvents() {
112
111
  const change = new Event('change', { bubbles: true });
113
112
  const input = new Event('input', { bubbles: true });
@@ -116,34 +115,14 @@ export class BsSelect extends BaseBsSelect implements ICustomElementViewModel {
116
115
  this.control.dispatchEvent(change);
117
116
  }
118
117
 
119
- #setHeight(): void {
120
- const { style } = this.control;
121
-
122
- if (this.size > 0) {
123
- const { borderTopWidth, borderBottomWidth, paddingTop, paddingBottom } = getComputedStyle(this.control);
124
-
125
- style.height = `calc(${
126
- this.size * 1.625 * (this.bsSize ? BS_SIZE_MULTIPLIER[this.bsSize] : 1)
127
- }rem + ${borderTopWidth} + ${borderBottomWidth} + ${paddingTop} + ${paddingBottom} - 2px)`;
128
- } else if (style.height) {
129
- style.height = undefined;
130
- }
131
- }
132
-
133
- #scrollToSelected() {
134
- const selected = this.control.querySelector<HTMLInputElement>('input:checked');
135
-
136
- if (selected) {
137
- const { paddingTop } = getComputedStyle(this.control);
138
-
139
- this.control.scrollTo({ top: selected.parentElement.offsetTop - parseInt(paddingTop) });
140
- }
141
- }
142
-
143
118
  get selectedOption(): ISelectOption | undefined {
144
- if (this['__raw__'].deactivating) return;
119
+ const thisRaw = this['__raw__'] as this;
120
+
121
+ if (thisRaw.deactivating || this.multiple) return;
145
122
 
146
- const { matcher, value, emptyValue } = this;
123
+ const { value, emptyValue } = this;
124
+ // take matcher from unproxied object to avoid unnecessary getter call
125
+ const { matcher } = thisRaw;
147
126
  let { options } = this;
148
127
  let emptyOption: ISelectOption;
149
128