@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 +1 -1
- package/src/forms/duration-input/duration-input.scss +1 -4
- package/src/forms/select/filter.ts +1 -1
- package/src/forms/select/select.html +59 -49
- package/src/forms/select/select.scss +10 -56
- package/src/forms/select/select.stories.ts +15 -7
- package/src/forms/select/select.ts +30 -51
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.28",
|
|
5
5
|
"homepage": "https://github.com/ekzo-dev/aurelia-components/tree/main/packages/bootstrap-addons",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -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' : ''}
|
|
1
|
+
<template class="addon ${floatingLabel ? 'form-floating' : ''}" bs-dropdown>
|
|
2
2
|
<label for="${id}" if.bind="label && !floatingLabel" class="form-label">${label}</label>
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
class="form-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
12
|
-
>
|
|
13
|
-
|
|
14
|
-
<
|
|
15
|
-
<
|
|
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"
|
|
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 || ' '}</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 || ' '}</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 || ' '}
|
|
72
82
|
</button>
|
|
73
83
|
</template>
|
|
74
|
-
</
|
|
75
|
-
</
|
|
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/
|
|
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
|
-
|
|
53
|
-
|
|
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
|
-
|
|
98
|
-
|
|
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
|
-
|
|
119
|
+
const thisRaw = this['__raw__'] as this;
|
|
120
|
+
|
|
121
|
+
if (thisRaw.deactivating || this.multiple) return;
|
|
145
122
|
|
|
146
|
-
const {
|
|
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
|
|