@ekzo-dev/bootstrap-addons 5.2.24 → 5.2.26
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.
|
|
4
|
+
"version": "5.2.26",
|
|
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.
|
|
12
|
+
"@ekzo-dev/bootstrap": "^5.2.20",
|
|
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",
|
|
@@ -21,7 +21,9 @@
|
|
|
21
21
|
"immutable-json-patch": "^5.1.3"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
|
-
"
|
|
24
|
+
"aurelia": "^2.0.0-beta.8",
|
|
25
|
+
"bootstrap": "^5.2.3",
|
|
26
|
+
"@popperjs/core": "^2.11.8"
|
|
25
27
|
},
|
|
26
28
|
"main": "src/index.ts",
|
|
27
29
|
"files": [
|
|
@@ -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:
|
|
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="${
|
|
31
|
+
value="${valueText}"
|
|
32
|
+
placeholder="${emptyOption?.text ?? ''}"
|
|
32
33
|
disabled.bind="disabled"
|
|
33
34
|
required.bind="required"
|
|
34
35
|
form.bind="form & attr"
|
|
@@ -38,26 +39,33 @@
|
|
|
38
39
|
keydown.trigger="$event.preventDefault()"
|
|
39
40
|
ref="control"
|
|
40
41
|
/>
|
|
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>
|
|
48
|
+
<bs-dropdown-menu popper-config.bind="popperConfig">
|
|
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" />
|
|
44
51
|
</div>
|
|
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
|
-
|
|
55
|
+
repeat.for="option of ungroupedOptions | filter:filter:emptyOption"
|
|
56
|
+
class="dropdown-item ${option.value === selectedOption?.value ? 'active' : ''}"
|
|
57
|
+
disabled.bind="option.disabled"
|
|
50
58
|
click.trigger="selectOption(option)"
|
|
51
59
|
>
|
|
52
60
|
${option.text || ' '}
|
|
53
61
|
</button>
|
|
54
|
-
<template repeat.for="[k, v] of groupedOptions | filter:filter">
|
|
62
|
+
<template repeat.for="[k, v] of groupedOptions | filter:filter:emptyOption">
|
|
55
63
|
<h6 bs-dropdown-item="header">${k}</h6>
|
|
56
64
|
<button
|
|
57
|
-
class="ps-4"
|
|
58
65
|
type="button"
|
|
59
66
|
repeat.for="option of v"
|
|
60
|
-
|
|
67
|
+
class="ps-4 dropdown-item ${option.value === selectedOption?.value ? 'active' : ''}"
|
|
68
|
+
disabled.bind="option.disabled"
|
|
61
69
|
click.trigger="selectOption(option)"
|
|
62
70
|
>
|
|
63
71
|
${option.text || ' '}
|
|
@@ -1,17 +1,40 @@
|
|
|
1
|
-
@import '
|
|
2
|
-
@import '
|
|
3
|
-
@import '
|
|
4
|
-
@import '
|
|
5
|
-
@import '
|
|
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
|
-
|
|
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 */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BsButton, BsOffcanvas } from '@ekzo-dev/bootstrap';
|
|
1
2
|
import { extractArgTypes, Meta, Story } from '@storybook/aurelia';
|
|
2
3
|
|
|
3
4
|
import { selectControl } from '../../../../../.storybook/helpers';
|
|
@@ -15,12 +16,14 @@ export default {
|
|
|
15
16
|
args: {
|
|
16
17
|
label: 'Label',
|
|
17
18
|
options: [
|
|
18
|
-
{ value: undefined, text: '' },
|
|
19
|
+
{ value: undefined, text: 'Select option' },
|
|
19
20
|
{ value: '1', text: 'One', disabled: true },
|
|
20
21
|
{ value: '2', text: 'Two' },
|
|
21
22
|
{ value: '3', text: 'Three', group: 'Group' },
|
|
22
23
|
],
|
|
23
24
|
value: '2',
|
|
25
|
+
floatingLabel: false,
|
|
26
|
+
valid: null,
|
|
24
27
|
},
|
|
25
28
|
argTypes: {
|
|
26
29
|
bsSize: {
|
|
@@ -55,5 +58,23 @@ LargeOptions.args = {
|
|
|
55
58
|
})),
|
|
56
59
|
};
|
|
57
60
|
|
|
61
|
+
const InModal: Story = (args) => ({
|
|
62
|
+
props: args,
|
|
63
|
+
template: `
|
|
64
|
+
<button bs-button click.trigger="offcanvas.toggle()">Open modal</button>
|
|
65
|
+
<bs-offcanvas component.ref="offcanvas">
|
|
66
|
+
<bs-select value.bind="value" options.bind="options" label.bind="label" style="width: 100%"></bs-select>
|
|
67
|
+
<div style="height: 2000px"></div>
|
|
68
|
+
</bs-offcanvas>`,
|
|
69
|
+
components: [BsOffcanvas, BsButton],
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
InModal.args = {
|
|
73
|
+
options: Array.from({ length: 1000 }).map((v, i) => ({
|
|
74
|
+
value: i.toString(),
|
|
75
|
+
text: `Option ${i} has long content which forces dropdown menu to scale larger that select box`,
|
|
76
|
+
})),
|
|
77
|
+
};
|
|
78
|
+
|
|
58
79
|
// eslint-disable-next-line
|
|
59
|
-
export { Overview, Multiple, LargeOptions };
|
|
80
|
+
export { Overview, Multiple, LargeOptions, InModal };
|
|
@@ -2,7 +2,11 @@ import template from './select.html';
|
|
|
2
2
|
|
|
3
3
|
import './select.scss';
|
|
4
4
|
|
|
5
|
+
import type { Options } from '@popperjs/core';
|
|
6
|
+
import type { Tooltip } from 'bootstrap';
|
|
7
|
+
|
|
5
8
|
import {
|
|
9
|
+
BsCloseButton,
|
|
6
10
|
BsDropdown,
|
|
7
11
|
BsDropdownItem,
|
|
8
12
|
BsDropdownMenu,
|
|
@@ -10,7 +14,7 @@ import {
|
|
|
10
14
|
BsSelect as BaseBsSelect,
|
|
11
15
|
ISelectOption,
|
|
12
16
|
} from '@ekzo-dev/bootstrap';
|
|
13
|
-
import { customElement, ICustomElementViewModel } from 'aurelia';
|
|
17
|
+
import { bindable, customElement, ICustomElementViewModel, resolve } from 'aurelia';
|
|
14
18
|
|
|
15
19
|
import { Filter } from './filter';
|
|
16
20
|
|
|
@@ -22,9 +26,14 @@ const BS_SIZE_MULTIPLIER = {
|
|
|
22
26
|
@customElement({
|
|
23
27
|
name: 'bs-select',
|
|
24
28
|
template,
|
|
25
|
-
dependencies: [BsDropdown, BsDropdownMenu, BsDropdownToggle, BsDropdownItem, Filter],
|
|
29
|
+
dependencies: [BsDropdown, BsDropdownMenu, BsDropdownToggle, BsDropdownItem, Filter, BsCloseButton],
|
|
26
30
|
})
|
|
27
31
|
export class BsSelect extends BaseBsSelect implements ICustomElementViewModel {
|
|
32
|
+
@bindable()
|
|
33
|
+
emptyValue?: unknown = null;
|
|
34
|
+
|
|
35
|
+
host = resolve(HTMLElement);
|
|
36
|
+
|
|
28
37
|
control!: HTMLFieldSetElement;
|
|
29
38
|
|
|
30
39
|
filter: string = '';
|
|
@@ -33,6 +42,10 @@ export class BsSelect extends BaseBsSelect implements ICustomElementViewModel {
|
|
|
33
42
|
|
|
34
43
|
deactivating: boolean = false;
|
|
35
44
|
|
|
45
|
+
emptyOption?: ISelectOption;
|
|
46
|
+
|
|
47
|
+
popperConfig: Partial<Options> | Tooltip.PopperConfigFunction | null = null;
|
|
48
|
+
|
|
36
49
|
binding() {
|
|
37
50
|
super.binding();
|
|
38
51
|
this.deactivating = false;
|
|
@@ -47,6 +60,8 @@ export class BsSelect extends BaseBsSelect implements ICustomElementViewModel {
|
|
|
47
60
|
this.#setHeight();
|
|
48
61
|
this.#scrollToSelected();
|
|
49
62
|
}
|
|
63
|
+
|
|
64
|
+
this.setPopperConfig();
|
|
50
65
|
}
|
|
51
66
|
|
|
52
67
|
propertyChanged(name: keyof this) {
|
|
@@ -62,11 +77,37 @@ export class BsSelect extends BaseBsSelect implements ICustomElementViewModel {
|
|
|
62
77
|
}
|
|
63
78
|
}
|
|
64
79
|
|
|
80
|
+
setPopperConfig() {
|
|
81
|
+
if (this.multiple) return;
|
|
82
|
+
|
|
83
|
+
const { host } = this;
|
|
84
|
+
const parentModal = host.closest('.modal-body,.popover-body,.offcanvas-body');
|
|
85
|
+
const dropdownMenu: HTMLElement = host.querySelector('.dropdown-menu');
|
|
86
|
+
|
|
87
|
+
if (parentModal != null) {
|
|
88
|
+
this.popperConfig = {
|
|
89
|
+
strategy: 'fixed',
|
|
90
|
+
};
|
|
91
|
+
dropdownMenu.style.minWidth = `${host.offsetWidth}px`;
|
|
92
|
+
} else {
|
|
93
|
+
this.popperConfig = null;
|
|
94
|
+
dropdownMenu.style.minWidth = undefined;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
65
98
|
selectOption(option: ISelectOption) {
|
|
66
99
|
this.value = option.value;
|
|
67
100
|
this.#dispatchEvents();
|
|
68
101
|
}
|
|
69
102
|
|
|
103
|
+
get valueText(): string {
|
|
104
|
+
const { selectedOption, emptyOption } = this;
|
|
105
|
+
|
|
106
|
+
return emptyOption && emptyOption.value === selectedOption?.value
|
|
107
|
+
? ''
|
|
108
|
+
: `${selectedOption?.group ? selectedOption.group + ' / ' : ''}${selectedOption?.text ?? ''}`;
|
|
109
|
+
}
|
|
110
|
+
|
|
70
111
|
#dispatchEvents() {
|
|
71
112
|
const change = new Event('change', { bubbles: true });
|
|
72
113
|
const input = new Event('input', { bubbles: true });
|
|
@@ -102,8 +143,9 @@ export class BsSelect extends BaseBsSelect implements ICustomElementViewModel {
|
|
|
102
143
|
get selectedOption(): ISelectOption | undefined {
|
|
103
144
|
if (this['__raw__'].deactivating) return;
|
|
104
145
|
|
|
105
|
-
const { matcher, value } = this;
|
|
146
|
+
const { matcher, value, emptyValue } = this;
|
|
106
147
|
let { options } = this;
|
|
148
|
+
let emptyOption: ISelectOption;
|
|
107
149
|
|
|
108
150
|
if (options instanceof Object && options.constructor === Object) {
|
|
109
151
|
options = Object.entries(options);
|
|
@@ -115,12 +157,19 @@ export class BsSelect extends BaseBsSelect implements ICustomElementViewModel {
|
|
|
115
157
|
let option = (options as Array<ISelectOption | readonly [unknown, string]>).find((option) => {
|
|
116
158
|
const currentValue: unknown = isEntries ? option[0] : (option as ISelectOption).value;
|
|
117
159
|
|
|
160
|
+
if (currentValue == emptyValue) {
|
|
161
|
+
emptyOption = {
|
|
162
|
+
value: currentValue,
|
|
163
|
+
text: isEntries ? option[1] : (option as ISelectOption).text,
|
|
164
|
+
} as ISelectOption;
|
|
165
|
+
}
|
|
166
|
+
|
|
118
167
|
return matcher ? matcher(value, currentValue) : value === currentValue;
|
|
119
168
|
});
|
|
120
169
|
|
|
121
170
|
option = isEntries && option !== undefined ? { value: option[0], text: option[1] } : (option as ISelectOption);
|
|
122
171
|
|
|
123
|
-
// update value next tick
|
|
172
|
+
// update value next tick
|
|
124
173
|
const foundValue = option?.value;
|
|
125
174
|
|
|
126
175
|
if (foundValue !== value) {
|
|
@@ -128,6 +177,9 @@ export class BsSelect extends BaseBsSelect implements ICustomElementViewModel {
|
|
|
128
177
|
void Promise.resolve().then(() => (this.value = foundValue));
|
|
129
178
|
}
|
|
130
179
|
|
|
180
|
+
// update empty option next tick
|
|
181
|
+
void Promise.resolve().then(() => (this.emptyOption = emptyOption));
|
|
182
|
+
|
|
131
183
|
return option;
|
|
132
184
|
}
|
|
133
185
|
}
|