@aquera/nile-elements 2.0.8 → 2.0.9
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/README.md +4 -0
- package/dist/index.js +619 -252
- package/dist/nile-select/index.cjs.js +1 -1
- package/dist/nile-select/index.esm.js +1 -1
- package/dist/nile-select/nile-select.cjs.js +2 -1
- package/dist/nile-select/nile-select.cjs.js.map +1 -1
- package/dist/nile-select/nile-select.css.cjs.js +1 -1
- package/dist/nile-select/nile-select.css.cjs.js.map +1 -1
- package/dist/nile-select/nile-select.css.esm.js +126 -6
- package/dist/nile-select/nile-select.esm.js +77 -11
- package/dist/nile-select/virtual-scroll-helper.cjs.js +1 -1
- package/dist/nile-select/virtual-scroll-helper.cjs.js.map +1 -1
- package/dist/nile-select/virtual-scroll-helper.esm.js +3 -1
- package/dist/nile-virtual-select/index.cjs.js +1 -1
- package/dist/nile-virtual-select/index.esm.js +1 -1
- package/dist/nile-virtual-select/nile-virtual-select.cjs.js +4 -3
- package/dist/nile-virtual-select/nile-virtual-select.cjs.js.map +1 -1
- package/dist/nile-virtual-select/nile-virtual-select.css.cjs.js +1 -1
- package/dist/nile-virtual-select/nile-virtual-select.css.cjs.js.map +1 -1
- package/dist/nile-virtual-select/nile-virtual-select.css.esm.js +126 -6
- package/dist/nile-virtual-select/nile-virtual-select.esm.js +71 -12
- package/dist/src/nile-select/nile-select.css.js +126 -6
- package/dist/src/nile-select/nile-select.css.js.map +1 -1
- package/dist/src/nile-select/nile-select.d.ts +43 -5
- package/dist/src/nile-select/nile-select.js +445 -106
- package/dist/src/nile-select/nile-select.js.map +1 -1
- package/dist/src/nile-select/nile-select.test.d.ts +1 -0
- package/dist/src/nile-select/nile-select.test.js +342 -1
- package/dist/src/nile-select/nile-select.test.js.map +1 -1
- package/dist/src/nile-select/virtual-scroll-helper.js +3 -1
- package/dist/src/nile-select/virtual-scroll-helper.js.map +1 -1
- package/dist/src/nile-virtual-select/nile-virtual-select.css.js +126 -6
- package/dist/src/nile-virtual-select/nile-virtual-select.css.js.map +1 -1
- package/dist/src/nile-virtual-select/nile-virtual-select.d.ts +45 -4
- package/dist/src/nile-virtual-select/nile-virtual-select.js +385 -59
- package/dist/src/nile-virtual-select/nile-virtual-select.js.map +1 -1
- package/dist/src/nile-virtual-select/nile-virtual-select.test.js +253 -1
- package/dist/src/nile-virtual-select/nile-virtual-select.test.js.map +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/version.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/nile-select/nile-select.css.ts +126 -6
- package/src/nile-select/nile-select.test.ts +478 -1
- package/src/nile-select/nile-select.ts +499 -91
- package/src/nile-select/virtual-scroll-helper.ts +3 -1
- package/src/nile-virtual-select/nile-virtual-select.css.ts +126 -6
- package/src/nile-virtual-select/nile-virtual-select.test.ts +352 -1
- package/src/nile-virtual-select/nile-virtual-select.ts +460 -55
- package/vscode-html-custom-data.json +40 -6
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import '../nile-icon';
|
|
8
8
|
import '../nile-popup/nile-popup';
|
|
9
9
|
import '../nile-tag/nile-tag';
|
|
10
|
+
import '../nile-lite-tooltip/nile-lite-tooltip';
|
|
10
11
|
import '../nile-checkbox/nile-checkbox';
|
|
11
12
|
import '../nile-loader/nile-loader';
|
|
12
13
|
import NileElement from '../internal/nile-element';
|
|
@@ -61,6 +62,10 @@ import type { VirtualOption, RenderItemConfig } from './types.js';
|
|
|
61
62
|
* @csspart listbox - The listbox container where options are slotted.
|
|
62
63
|
* @csspart tags - The container that houses option tags when `multiple` is used.
|
|
63
64
|
* @csspart tag - The individual tags that represent each multiselect option.
|
|
65
|
+
* @csspart tag__base - The tag's base part.
|
|
66
|
+
* @csspart tag__content - The tag's content part.
|
|
67
|
+
* @csspart tag__remove-button - The tag's remove button.
|
|
68
|
+
* @csspart tag__remove-button__base - The tag's remove button base part.
|
|
64
69
|
* @csspart clear-button - The clear button.
|
|
65
70
|
* @csspart expand-icon - The container that wraps the expand icon.
|
|
66
71
|
* @csspart footer - The footer container with show selected and clear all options.
|
|
@@ -98,7 +103,7 @@ export declare class NileVirtualSelect extends NileElement implements NileFormCo
|
|
|
98
103
|
size: 'small' | 'medium' | 'large';
|
|
99
104
|
/** Placeholder text to show as a hint when the select is empty. */
|
|
100
105
|
placeholder: string;
|
|
101
|
-
/**
|
|
106
|
+
/** Re-runs the fitted-chip layout when the control's own width changes. */
|
|
102
107
|
autoResize: boolean;
|
|
103
108
|
/** Current search value */
|
|
104
109
|
searchValue: string;
|
|
@@ -176,8 +181,27 @@ export declare class NileVirtualSelect extends NileElement implements NileFormCo
|
|
|
176
181
|
* The maximum number of selected options to show when `multiple` is true. After the maximum, "+n" will be shown to
|
|
177
182
|
* indicate the number of additional items that are selected. Set to 0 to remove the limit.
|
|
178
183
|
*/
|
|
179
|
-
|
|
180
|
-
|
|
184
|
+
private _maxOptionsVisible;
|
|
185
|
+
private maxOptionsVisibleExplicit;
|
|
186
|
+
get maxOptionsVisible(): number;
|
|
187
|
+
set maxOptionsVisible(value: number);
|
|
188
|
+
private get chipFittingEnabled();
|
|
189
|
+
/** Minimum width, in px, of a squeezed chip. Needs `showtooltip`. */
|
|
190
|
+
chipMinWidth: number;
|
|
191
|
+
/** Maximum width, in px, of a squeezed chip. Needs `showtooltip`. */
|
|
192
|
+
chipMaxWidth: number;
|
|
193
|
+
/**
|
|
194
|
+
* Shows a chip's full value in a tooltip when its text is clipped, and is
|
|
195
|
+
* what allows a chip to be truncated at all.
|
|
196
|
+
*/
|
|
197
|
+
showTooltip: boolean;
|
|
198
|
+
private hiddenChipCount;
|
|
199
|
+
/** Last measured width of the overflow button, in px. */
|
|
200
|
+
private lastOverflowWidth;
|
|
201
|
+
private chipFitFrame;
|
|
202
|
+
private lastChipLayout;
|
|
203
|
+
private clippedChips;
|
|
204
|
+
private clipFrame;
|
|
181
205
|
enableTabClose: boolean;
|
|
182
206
|
descriptionSearchEnabled: boolean;
|
|
183
207
|
allowHtmlLabel: boolean;
|
|
@@ -247,6 +271,7 @@ export declare class NileVirtualSelect extends NileElement implements NileFormCo
|
|
|
247
271
|
private unSelectAll;
|
|
248
272
|
private handleLabelClick;
|
|
249
273
|
private handleComboboxMouseDown;
|
|
274
|
+
private observedTagsContainer?;
|
|
250
275
|
private resizeController;
|
|
251
276
|
private shouldIgnoreComboboxClick;
|
|
252
277
|
private handleComboboxKeyDown;
|
|
@@ -295,6 +320,7 @@ export declare class NileVirtualSelect extends NileElement implements NileFormCo
|
|
|
295
320
|
private renderCombobox;
|
|
296
321
|
private renderPrefix;
|
|
297
322
|
private renderDisplayInput;
|
|
323
|
+
private renderFittedTags;
|
|
298
324
|
private renderTags;
|
|
299
325
|
private renderValueInput;
|
|
300
326
|
private renderClearButton;
|
|
@@ -309,8 +335,23 @@ export declare class NileVirtualSelect extends NileElement implements NileFormCo
|
|
|
309
335
|
nileInput(value: any): void;
|
|
310
336
|
nileChange(value: any): void;
|
|
311
337
|
private updateValidity;
|
|
312
|
-
private
|
|
338
|
+
private setupResizeObserver;
|
|
313
339
|
private calculateTotalWidthOfTags;
|
|
340
|
+
private allocateChipWidths;
|
|
341
|
+
private computeChipFit;
|
|
342
|
+
private layoutChips;
|
|
343
|
+
private isChipClipped;
|
|
344
|
+
private refreshClippedChips;
|
|
345
|
+
private scheduleClipCheck;
|
|
346
|
+
/** A chip gets a tooltip only while its text is measured as clipped. */
|
|
347
|
+
private chipTooltipDisabled;
|
|
348
|
+
private destroyChipFit;
|
|
349
|
+
private getChipLabel;
|
|
350
|
+
private handleChipKeyDown;
|
|
351
|
+
/** Moves focus to the next chip, the last chip, or the field after a removal. */
|
|
352
|
+
private restoreFocusAfterChipRemoval;
|
|
353
|
+
private handleOverflowActivate;
|
|
354
|
+
private handleOverflowKeyDown;
|
|
314
355
|
private resetScrollPosition;
|
|
315
356
|
}
|
|
316
357
|
export default NileVirtualSelect;
|
|
@@ -11,12 +11,14 @@ import { styles } from './nile-virtual-select.css';
|
|
|
11
11
|
import '../nile-icon';
|
|
12
12
|
import '../nile-popup/nile-popup';
|
|
13
13
|
import '../nile-tag/nile-tag';
|
|
14
|
+
import '../nile-lite-tooltip/nile-lite-tooltip';
|
|
14
15
|
import '../nile-checkbox/nile-checkbox';
|
|
15
16
|
import '../nile-loader/nile-loader';
|
|
16
17
|
import { animateTo, stopAnimations } from '../internal/animate';
|
|
17
18
|
import { classMap } from 'lit/directives/class-map.js';
|
|
18
19
|
import { query, state } from 'lit/decorators.js';
|
|
19
20
|
import { defaultValue } from '../internal/default-value';
|
|
21
|
+
import { KeyCode } from '../internal/enum';
|
|
20
22
|
import { FormControlController } from '../internal/form';
|
|
21
23
|
import { getAnimation, setDefaultAnimation, } from '../utilities/animation-registry';
|
|
22
24
|
import { HasSlotController } from '../internal/slot';
|
|
@@ -77,6 +79,10 @@ import { VisibilityManager } from '../utilities/visibility-manager.js';
|
|
|
77
79
|
* @csspart listbox - The listbox container where options are slotted.
|
|
78
80
|
* @csspart tags - The container that houses option tags when `multiple` is used.
|
|
79
81
|
* @csspart tag - The individual tags that represent each multiselect option.
|
|
82
|
+
* @csspart tag__base - The tag's base part.
|
|
83
|
+
* @csspart tag__content - The tag's content part.
|
|
84
|
+
* @csspart tag__remove-button - The tag's remove button.
|
|
85
|
+
* @csspart tag__remove-button__base - The tag's remove button base part.
|
|
80
86
|
* @csspart clear-button - The clear button.
|
|
81
87
|
* @csspart expand-icon - The container that wraps the expand icon.
|
|
82
88
|
* @csspart footer - The footer container with show selected and clear all options.
|
|
@@ -110,7 +116,7 @@ let NileVirtualSelect = class NileVirtualSelect extends NileElement {
|
|
|
110
116
|
this.size = 'medium';
|
|
111
117
|
/** Placeholder text to show as a hint when the select is empty. */
|
|
112
118
|
this.placeholder = 'Select...';
|
|
113
|
-
/**
|
|
119
|
+
/** Re-runs the fitted-chip layout when the control's own width changes. */
|
|
114
120
|
this.autoResize = false;
|
|
115
121
|
/** Current search value */
|
|
116
122
|
this.searchValue = '';
|
|
@@ -186,8 +192,24 @@ let NileVirtualSelect = class NileVirtualSelect extends NileElement {
|
|
|
186
192
|
* The maximum number of selected options to show when `multiple` is true. After the maximum, "+n" will be shown to
|
|
187
193
|
* indicate the number of additional items that are selected. Set to 0 to remove the limit.
|
|
188
194
|
*/
|
|
189
|
-
this.
|
|
190
|
-
this.
|
|
195
|
+
this._maxOptionsVisible = 3;
|
|
196
|
+
this.maxOptionsVisibleExplicit = false;
|
|
197
|
+
/** Minimum width, in px, of a squeezed chip. Needs `showtooltip`. */
|
|
198
|
+
this.chipMinWidth = 110;
|
|
199
|
+
/** Maximum width, in px, of a squeezed chip. Needs `showtooltip`. */
|
|
200
|
+
this.chipMaxWidth = 180;
|
|
201
|
+
/**
|
|
202
|
+
* Shows a chip's full value in a tooltip when its text is clipped, and is
|
|
203
|
+
* what allows a chip to be truncated at all.
|
|
204
|
+
*/
|
|
205
|
+
this.showTooltip = false;
|
|
206
|
+
this.hiddenChipCount = 0;
|
|
207
|
+
/** Last measured width of the overflow button, in px. */
|
|
208
|
+
this.lastOverflowWidth = 0;
|
|
209
|
+
this.chipFitFrame = 0;
|
|
210
|
+
this.lastChipLayout = { hidden: -1, visible: -1 };
|
|
211
|
+
this.clippedChips = [];
|
|
212
|
+
this.clipFrame = 0;
|
|
191
213
|
this.enableTabClose = false;
|
|
192
214
|
this.descriptionSearchEnabled = false;
|
|
193
215
|
this.allowHtmlLabel = true;
|
|
@@ -237,6 +259,19 @@ let NileVirtualSelect = class NileVirtualSelect extends NileElement {
|
|
|
237
259
|
}
|
|
238
260
|
});
|
|
239
261
|
}
|
|
262
|
+
get maxOptionsVisible() {
|
|
263
|
+
return this._maxOptionsVisible;
|
|
264
|
+
}
|
|
265
|
+
set maxOptionsVisible(value) {
|
|
266
|
+
const previous = this._maxOptionsVisible;
|
|
267
|
+
this._maxOptionsVisible = value;
|
|
268
|
+
// Any assignment opts this instance out of automatic chip fitting.
|
|
269
|
+
this.maxOptionsVisibleExplicit = true;
|
|
270
|
+
this.requestUpdate('maxOptionsVisible', previous);
|
|
271
|
+
}
|
|
272
|
+
get chipFittingEnabled() {
|
|
273
|
+
return !!this.multiple && !this.maxOptionsVisibleExplicit;
|
|
274
|
+
}
|
|
240
275
|
/** Gets the validity state object */
|
|
241
276
|
get validity() {
|
|
242
277
|
return this.valueInput?.validity;
|
|
@@ -265,19 +300,26 @@ let NileVirtualSelect = class NileVirtualSelect extends NileElement {
|
|
|
265
300
|
}
|
|
266
301
|
// Clean up body append elements
|
|
267
302
|
this.portalManager.cleanupPortalAppend();
|
|
303
|
+
this.destroyChipFit();
|
|
268
304
|
}
|
|
269
305
|
updated(changedProperties) {
|
|
270
306
|
if (changedProperties.has('value')) {
|
|
271
307
|
this.selectionChanged();
|
|
272
308
|
}
|
|
273
|
-
if (changedProperties.has('autoResize')
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
309
|
+
if (changedProperties.has('autoResize') ||
|
|
310
|
+
changedProperties.has('multiple') ||
|
|
311
|
+
changedProperties.has('maxOptionsVisible')) {
|
|
312
|
+
this.setupResizeObserver();
|
|
313
|
+
}
|
|
314
|
+
if (changedProperties.has('chipMinWidth') ||
|
|
315
|
+
changedProperties.has('chipMaxWidth')) {
|
|
316
|
+
this.destroyChipFit();
|
|
317
|
+
}
|
|
318
|
+
if (this.chipFittingEnabled) {
|
|
319
|
+
this.calculateTotalWidthOfTags();
|
|
320
|
+
}
|
|
321
|
+
if (this.multiple && this.showTooltip) {
|
|
322
|
+
this.scheduleClipCheck();
|
|
281
323
|
}
|
|
282
324
|
}
|
|
283
325
|
initializeComponent() {
|
|
@@ -502,7 +544,9 @@ let NileVirtualSelect = class NileVirtualSelect extends NileElement {
|
|
|
502
544
|
shouldIgnoreComboboxClick(event) {
|
|
503
545
|
const path = event.composedPath();
|
|
504
546
|
const isIconButton = path.some(el => el instanceof Element && el.tagName.toLowerCase() === 'nile-icon-button');
|
|
505
|
-
|
|
547
|
+
const isOverflowButton = path.some(el => el instanceof HTMLButtonElement &&
|
|
548
|
+
el.classList.contains('select__tags-count'));
|
|
549
|
+
return this.disabled || isIconButton || isOverflowButton;
|
|
506
550
|
}
|
|
507
551
|
handleComboboxKeyDown(event) {
|
|
508
552
|
if (this.isEnterOrSpaceKey(event)) {
|
|
@@ -1048,20 +1092,74 @@ let NileVirtualSelect = class NileVirtualSelect extends NileElement {
|
|
|
1048
1092
|
/>
|
|
1049
1093
|
`;
|
|
1050
1094
|
}
|
|
1095
|
+
renderFittedTags() {
|
|
1096
|
+
return html `
|
|
1097
|
+
<div part="tags" class="select__tags select__tags--fitted">
|
|
1098
|
+
${this.selectedOptions.map((option, index) => {
|
|
1099
|
+
const label = this.getChipLabel(option);
|
|
1100
|
+
return html `
|
|
1101
|
+
<nile-lite-tooltip
|
|
1102
|
+
class="select__tag-tip"
|
|
1103
|
+
.content=${label}
|
|
1104
|
+
?disabled=${this.chipTooltipDisabled(index)}
|
|
1105
|
+
>
|
|
1106
|
+
<nile-tag
|
|
1107
|
+
part="tag"
|
|
1108
|
+
exportparts="
|
|
1109
|
+
base:tag__base,
|
|
1110
|
+
content:tag__content,
|
|
1111
|
+
remove-button:tag__remove-button,
|
|
1112
|
+
remove-button__base:tag__remove-button__base,
|
|
1113
|
+
prefix:tag__prefix,
|
|
1114
|
+
prefix_content_wrapper:tag__prefix_content_wrapper
|
|
1115
|
+
"
|
|
1116
|
+
?pill=${this.pill}
|
|
1117
|
+
size=${this.size}
|
|
1118
|
+
removable
|
|
1119
|
+
tabindex="0"
|
|
1120
|
+
aria-label=${`${label}. Press Backspace or Delete to remove.`}
|
|
1121
|
+
@nile-remove=${(event) => this.handleTagRemove(event, option)}
|
|
1122
|
+
@keydown=${(event) => this.handleChipKeyDown(event, option)}
|
|
1123
|
+
>
|
|
1124
|
+
${label}
|
|
1125
|
+
</nile-tag>
|
|
1126
|
+
</nile-lite-tooltip>
|
|
1127
|
+
`;
|
|
1128
|
+
})}
|
|
1129
|
+
${this.hiddenChipCount > 0
|
|
1130
|
+
? html `<button
|
|
1131
|
+
type="button"
|
|
1132
|
+
class="select__tags-count"
|
|
1133
|
+
?disabled=${this.disabled}
|
|
1134
|
+
aria-controls="listbox"
|
|
1135
|
+
aria-expanded=${this.open ? 'true' : 'false'}
|
|
1136
|
+
aria-haspopup="listbox"
|
|
1137
|
+
aria-label=${`${this.hiddenChipCount} more selected`}
|
|
1138
|
+
@click=${this.handleOverflowActivate}
|
|
1139
|
+
@keydown=${this.handleOverflowKeyDown}
|
|
1140
|
+
>+${this.hiddenChipCount}</button
|
|
1141
|
+
>`
|
|
1142
|
+
: ''}
|
|
1143
|
+
</div>
|
|
1144
|
+
`;
|
|
1145
|
+
}
|
|
1051
1146
|
renderTags() {
|
|
1147
|
+
if (this.chipFittingEnabled) {
|
|
1148
|
+
return this.renderFittedTags();
|
|
1149
|
+
}
|
|
1052
1150
|
return this.multiple
|
|
1053
1151
|
? html `
|
|
1054
1152
|
<div part="tags" class="select__tags">
|
|
1055
1153
|
${this.selectedOptions.map((option, index) => {
|
|
1056
1154
|
if (index < this.maxOptionsVisible ||
|
|
1057
1155
|
this.maxOptionsVisible <= 0) {
|
|
1058
|
-
const classes = {
|
|
1059
|
-
select__invisible: index + 1 > this.oldMaxOptionsVisible &&
|
|
1060
|
-
this.maxOptionsVisible === Infinity,
|
|
1061
|
-
};
|
|
1062
1156
|
return html `
|
|
1157
|
+
<nile-lite-tooltip
|
|
1158
|
+
class="select__tag-tip"
|
|
1159
|
+
.content=${option.getTextLabel()}
|
|
1160
|
+
?disabled=${this.chipTooltipDisabled(index)}
|
|
1161
|
+
>
|
|
1063
1162
|
<nile-tag
|
|
1064
|
-
class=${classMap(classes)}
|
|
1065
1163
|
part="tag"
|
|
1066
1164
|
exportparts="
|
|
1067
1165
|
base:tag__base,
|
|
@@ -1069,6 +1167,7 @@ let NileVirtualSelect = class NileVirtualSelect extends NileElement {
|
|
|
1069
1167
|
remove-button:tag__remove-button,
|
|
1070
1168
|
remove-button__base:tag__remove-button__base
|
|
1071
1169
|
"
|
|
1170
|
+
style="max-width:${this.chipMaxWidth}px"
|
|
1072
1171
|
?pill=${this.pill}
|
|
1073
1172
|
size=${this.size}
|
|
1074
1173
|
removable
|
|
@@ -1076,6 +1175,7 @@ let NileVirtualSelect = class NileVirtualSelect extends NileElement {
|
|
|
1076
1175
|
>
|
|
1077
1176
|
${option.getTextLabel()}
|
|
1078
1177
|
</nile-tag>
|
|
1178
|
+
</nile-lite-tooltip>
|
|
1079
1179
|
`;
|
|
1080
1180
|
}
|
|
1081
1181
|
else if (index === this.maxOptionsVisible) {
|
|
@@ -1222,14 +1322,16 @@ let NileVirtualSelect = class NileVirtualSelect extends NileElement {
|
|
|
1222
1322
|
@click="${this.handleFooterClick}"
|
|
1223
1323
|
>
|
|
1224
1324
|
<span
|
|
1325
|
+
class="select__footer-action"
|
|
1225
1326
|
@click="${this.toggleShowSelected}"
|
|
1226
1327
|
style="cursor: pointer;"
|
|
1328
|
+
title="Show selected"
|
|
1227
1329
|
>
|
|
1228
1330
|
<nile-checkbox
|
|
1229
1331
|
?disabled="${this.selectedOptions.length === 0}"
|
|
1230
1332
|
?checked="${this.showSelected}"
|
|
1231
1333
|
>
|
|
1232
|
-
|
|
1334
|
+
<span class="select__footer-label">Selected</span></nile-checkbox
|
|
1233
1335
|
>
|
|
1234
1336
|
</span>
|
|
1235
1337
|
${this.selectedOptions.length !== 0
|
|
@@ -1237,8 +1339,16 @@ let NileVirtualSelect = class NileVirtualSelect extends NileElement {
|
|
|
1237
1339
|
<span
|
|
1238
1340
|
class="select__clear"
|
|
1239
1341
|
@click="${this.unSelectAll}"
|
|
1342
|
+
title="Clear"
|
|
1240
1343
|
>
|
|
1241
|
-
|
|
1344
|
+
<nile-glyph
|
|
1345
|
+
class="select__footer-glyph"
|
|
1346
|
+
name="ng-eraser"
|
|
1347
|
+
size="16"
|
|
1348
|
+
method="var(--nile-svg-method-stroke, var(--ng-svg-method-stroke))"
|
|
1349
|
+
color="var(--nile-colors-primary-600, var(--ng-colors-text-brand-secondary-700))"
|
|
1350
|
+
></nile-glyph
|
|
1351
|
+
><span class="select__footer-label">Clear</span>
|
|
1242
1352
|
</span>
|
|
1243
1353
|
`
|
|
1244
1354
|
: ``}
|
|
@@ -1274,50 +1384,254 @@ let NileVirtualSelect = class NileVirtualSelect extends NileElement {
|
|
|
1274
1384
|
this.formControlController.updateValidity();
|
|
1275
1385
|
});
|
|
1276
1386
|
}
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1387
|
+
setupResizeObserver() {
|
|
1388
|
+
const wanted = this.autoResize && this.chipFittingEnabled
|
|
1389
|
+
? this.shadowRoot?.querySelector('.select__tags') ??
|
|
1390
|
+
undefined
|
|
1391
|
+
: undefined;
|
|
1392
|
+
if (wanted === this.observedTagsContainer) {
|
|
1393
|
+
if (!this.chipFittingEnabled)
|
|
1394
|
+
this.destroyChipFit();
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
if (this.observedTagsContainer) {
|
|
1398
|
+
this.resizeController.unobserve(this.observedTagsContainer);
|
|
1399
|
+
this.observedTagsContainer = undefined;
|
|
1400
|
+
}
|
|
1401
|
+
if (wanted) {
|
|
1402
|
+
this.resizeController.observe(wanted);
|
|
1403
|
+
this.observedTagsContainer = wanted;
|
|
1404
|
+
return;
|
|
1405
|
+
}
|
|
1406
|
+
if (!this.chipFittingEnabled) {
|
|
1407
|
+
this.destroyChipFit();
|
|
1284
1408
|
}
|
|
1285
|
-
return null;
|
|
1286
1409
|
}
|
|
1287
1410
|
calculateTotalWidthOfTags() {
|
|
1288
|
-
if (this.
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1411
|
+
if (!this.chipFittingEnabled)
|
|
1412
|
+
return;
|
|
1413
|
+
if (this.chipFitFrame)
|
|
1414
|
+
cancelAnimationFrame(this.chipFitFrame);
|
|
1415
|
+
this.chipFitFrame = requestAnimationFrame(() => {
|
|
1416
|
+
this.chipFitFrame = 0;
|
|
1417
|
+
this.layoutChips();
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
allocateChipWidths(natural, available) {
|
|
1421
|
+
const min = this.chipMinWidth;
|
|
1422
|
+
const max = this.chipMaxWidth;
|
|
1423
|
+
const total = natural.reduce((sum, w) => sum + w, 0);
|
|
1424
|
+
if (total <= available) {
|
|
1425
|
+
return natural.slice();
|
|
1426
|
+
}
|
|
1427
|
+
// Nothing reveals a truncated value without a tooltip, so chips keep their
|
|
1428
|
+
// natural width and the surplus moves into the +N button instead.
|
|
1429
|
+
if (!this.showTooltip)
|
|
1430
|
+
return null;
|
|
1431
|
+
const capped = natural.map(w => Math.min(w, max));
|
|
1432
|
+
const cappedTotal = capped.reduce((sum, w) => sum + w, 0);
|
|
1433
|
+
if (cappedTotal <= available) {
|
|
1434
|
+
return capped;
|
|
1435
|
+
}
|
|
1436
|
+
const widths = new Array(natural.length).fill(0);
|
|
1437
|
+
let pool = capped.map((_, i) => i);
|
|
1438
|
+
let remaining = available;
|
|
1439
|
+
for (;;) {
|
|
1440
|
+
if (pool.length === 0)
|
|
1441
|
+
break;
|
|
1442
|
+
const share = remaining / pool.length;
|
|
1443
|
+
const settled = pool.filter(i => capped[i] <= share);
|
|
1444
|
+
if (settled.length === 0)
|
|
1445
|
+
break;
|
|
1446
|
+
for (const i of settled) {
|
|
1447
|
+
widths[i] = capped[i];
|
|
1448
|
+
remaining -= capped[i];
|
|
1301
1449
|
}
|
|
1302
|
-
|
|
1303
|
-
|
|
1450
|
+
pool = pool.filter(i => capped[i] > share);
|
|
1451
|
+
}
|
|
1452
|
+
if (pool.length > 0) {
|
|
1453
|
+
const share = remaining / pool.length;
|
|
1454
|
+
if (share < min) {
|
|
1455
|
+
return null;
|
|
1304
1456
|
}
|
|
1305
|
-
|
|
1306
|
-
const
|
|
1307
|
-
|
|
1308
|
-
return;
|
|
1457
|
+
const width = Math.floor(share);
|
|
1458
|
+
for (const i of pool) {
|
|
1459
|
+
widths[i] = width;
|
|
1309
1460
|
}
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1461
|
+
}
|
|
1462
|
+
return widths;
|
|
1463
|
+
}
|
|
1464
|
+
computeChipFit(natural, available, overflowWidth, gap) {
|
|
1465
|
+
const total = natural.length;
|
|
1466
|
+
if (total === 0) {
|
|
1467
|
+
return { visibleCount: 0, widths: [], hiddenCount: 0 };
|
|
1468
|
+
}
|
|
1469
|
+
for (let count = total; count >= 1; count--) {
|
|
1470
|
+
const hidden = total - count;
|
|
1471
|
+
const reserved = hidden > 0 ? overflowWidth + gap : 0;
|
|
1472
|
+
const row = available - reserved - gap * (count - 1);
|
|
1473
|
+
if (row <= 0)
|
|
1474
|
+
continue;
|
|
1475
|
+
const widths = this.allocateChipWidths(natural.slice(0, count), row);
|
|
1476
|
+
if (widths) {
|
|
1477
|
+
return { visibleCount: count, widths, hiddenCount: hidden };
|
|
1318
1478
|
}
|
|
1319
|
-
|
|
1320
|
-
|
|
1479
|
+
}
|
|
1480
|
+
const room = available - overflowWidth - gap;
|
|
1481
|
+
const fallback = Math.max(0,
|
|
1482
|
+
// A single chip too wide for the row has to be clipped either way; only
|
|
1483
|
+
// the tooltip variant squeezes it to chipMinWidth.
|
|
1484
|
+
Math.min(natural[0], this.showTooltip ? Math.max(this.chipMinWidth, room) : room));
|
|
1485
|
+
return { visibleCount: 1, widths: [fallback], hiddenCount: total - 1 };
|
|
1486
|
+
}
|
|
1487
|
+
layoutChips() {
|
|
1488
|
+
const root = this.shadowRoot;
|
|
1489
|
+
if (!root)
|
|
1490
|
+
return;
|
|
1491
|
+
const container = root.querySelector('.select__tags');
|
|
1492
|
+
if (!container)
|
|
1493
|
+
return;
|
|
1494
|
+
const chips = Array.from(container.querySelectorAll('nile-tag'));
|
|
1495
|
+
if (chips.length === 0) {
|
|
1496
|
+
this.hiddenChipCount = 0;
|
|
1497
|
+
return;
|
|
1498
|
+
}
|
|
1499
|
+
const overflowEl = container.querySelector('.select__tags-count');
|
|
1500
|
+
for (const chip of chips) {
|
|
1501
|
+
chip.style.display = '';
|
|
1502
|
+
chip.style.width = 'auto';
|
|
1503
|
+
chip.style.maxWidth = 'none';
|
|
1504
|
+
chip.style.minWidth = '0';
|
|
1505
|
+
chip.style.flex = '0 0 auto';
|
|
1506
|
+
}
|
|
1507
|
+
const natural = chips.map(chip => Math.ceil(chip.getBoundingClientRect().width));
|
|
1508
|
+
// Not rendered yet on the pass that first finds an overflow; reuse the last width.
|
|
1509
|
+
const overflowWidth = overflowEl
|
|
1510
|
+
? Math.ceil(overflowEl.getBoundingClientRect().width)
|
|
1511
|
+
: this.lastOverflowWidth;
|
|
1512
|
+
if (overflowEl)
|
|
1513
|
+
this.lastOverflowWidth = overflowWidth;
|
|
1514
|
+
const containerStyles = getComputedStyle(container);
|
|
1515
|
+
const gap = parseFloat(containerStyles.columnGap || containerStyles.gap || '0') || 0;
|
|
1516
|
+
const fit = this.computeChipFit(natural, container.clientWidth, overflowWidth, gap);
|
|
1517
|
+
chips.forEach((chip, index) => {
|
|
1518
|
+
if (index < fit.visibleCount) {
|
|
1519
|
+
// Never squeeze a chip below chipMinWidth: narrower than that it is
|
|
1520
|
+
// also narrower than the tag's own box - the label is already at zero
|
|
1521
|
+
// while the remove button is fixed - so the box overflows the chip and
|
|
1522
|
+
// paints the X over the next chip or the +N button. A chip whose
|
|
1523
|
+
// natural width is already smaller is left alone, not padded out.
|
|
1524
|
+
const width = Math.min(natural[index], Math.max(fit.widths[index], this.chipMinWidth));
|
|
1525
|
+
chip.style.display = '';
|
|
1526
|
+
chip.style.width = `${width}px`;
|
|
1527
|
+
chip.style.maxWidth = `${width}px`;
|
|
1528
|
+
chip.style.flex = '0 0 auto';
|
|
1529
|
+
}
|
|
1530
|
+
else {
|
|
1531
|
+
chip.style.display = 'none';
|
|
1532
|
+
}
|
|
1533
|
+
});
|
|
1534
|
+
if (this.lastChipLayout.hidden !== fit.hiddenCount ||
|
|
1535
|
+
this.lastChipLayout.visible !== fit.visibleCount) {
|
|
1536
|
+
this.lastChipLayout = {
|
|
1537
|
+
hidden: fit.hiddenCount,
|
|
1538
|
+
visible: fit.visibleCount
|
|
1539
|
+
};
|
|
1540
|
+
this.hiddenChipCount = fit.hiddenCount;
|
|
1541
|
+
}
|
|
1542
|
+
this.refreshClippedChips();
|
|
1543
|
+
}
|
|
1544
|
+
isChipClipped(el) {
|
|
1545
|
+
if (!el)
|
|
1546
|
+
return false;
|
|
1547
|
+
const root = el.shadowRoot;
|
|
1548
|
+
if (root) {
|
|
1549
|
+
const content = root.querySelector('[part="content"]');
|
|
1550
|
+
if (content && content.scrollWidth > content.clientWidth + 1)
|
|
1551
|
+
return true;
|
|
1552
|
+
const inner = root.querySelector('[part="prefix_content_wrapper"]');
|
|
1553
|
+
if (inner && inner.scrollWidth > inner.clientWidth + 1)
|
|
1554
|
+
return true;
|
|
1555
|
+
}
|
|
1556
|
+
return el.scrollWidth > el.clientWidth + 1;
|
|
1557
|
+
}
|
|
1558
|
+
refreshClippedChips() {
|
|
1559
|
+
if (!this.multiple)
|
|
1560
|
+
return;
|
|
1561
|
+
const root = this.shadowRoot;
|
|
1562
|
+
if (!root)
|
|
1563
|
+
return;
|
|
1564
|
+
const chips = Array.from(root.querySelectorAll('.select__tags nile-tag'));
|
|
1565
|
+
const next = chips.map(chip => this.isChipClipped(chip));
|
|
1566
|
+
const same = next.length === this.clippedChips.length &&
|
|
1567
|
+
next.every((v, i) => v === this.clippedChips[i]);
|
|
1568
|
+
if (!same) {
|
|
1569
|
+
this.clippedChips = next;
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
scheduleClipCheck() {
|
|
1573
|
+
if (this.clipFrame)
|
|
1574
|
+
cancelAnimationFrame(this.clipFrame);
|
|
1575
|
+
this.clipFrame = requestAnimationFrame(() => {
|
|
1576
|
+
this.clipFrame = 0;
|
|
1577
|
+
this.refreshClippedChips();
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1580
|
+
/** A chip gets a tooltip only while its text is measured as clipped. */
|
|
1581
|
+
chipTooltipDisabled(index) {
|
|
1582
|
+
return !this.showTooltip || !(this.clippedChips[index] ?? false);
|
|
1583
|
+
}
|
|
1584
|
+
destroyChipFit() {
|
|
1585
|
+
if (this.chipFitFrame)
|
|
1586
|
+
cancelAnimationFrame(this.chipFitFrame);
|
|
1587
|
+
this.chipFitFrame = 0;
|
|
1588
|
+
if (this.clipFrame)
|
|
1589
|
+
cancelAnimationFrame(this.clipFrame);
|
|
1590
|
+
this.clipFrame = 0;
|
|
1591
|
+
this.lastChipLayout = { hidden: -1, visible: -1 };
|
|
1592
|
+
}
|
|
1593
|
+
getChipLabel(option) {
|
|
1594
|
+
return (option.getTextLabel?.() ?? '').trim();
|
|
1595
|
+
}
|
|
1596
|
+
handleChipKeyDown(event, option) {
|
|
1597
|
+
if (event.key !== KeyCode.BACKSPACE && event.key !== KeyCode.DELETE)
|
|
1598
|
+
return;
|
|
1599
|
+
if (this.disabled)
|
|
1600
|
+
return;
|
|
1601
|
+
event.preventDefault();
|
|
1602
|
+
event.stopPropagation();
|
|
1603
|
+
const removedIndex = this.selectedOptions.indexOf(option);
|
|
1604
|
+
this.handleTagRemove(new CustomEvent('nile-remove'), option);
|
|
1605
|
+
this.restoreFocusAfterChipRemoval(removedIndex);
|
|
1606
|
+
}
|
|
1607
|
+
/** Moves focus to the next chip, the last chip, or the field after a removal. */
|
|
1608
|
+
async restoreFocusAfterChipRemoval(removedIndex) {
|
|
1609
|
+
if (removedIndex < 0)
|
|
1610
|
+
return;
|
|
1611
|
+
await this.updateComplete;
|
|
1612
|
+
// Wait for the fit pass so hidden chips are not focused.
|
|
1613
|
+
await new Promise(resolve => requestAnimationFrame(() => resolve()));
|
|
1614
|
+
const visibleChips = Array.from(this.shadowRoot?.querySelectorAll('.select__tags nile-tag') ??
|
|
1615
|
+
[]).filter(chip => chip.style.display !== 'none');
|
|
1616
|
+
const target = visibleChips[removedIndex] ?? visibleChips[visibleChips.length - 1];
|
|
1617
|
+
if (target) {
|
|
1618
|
+
target.focus({ preventScroll: true });
|
|
1619
|
+
}
|
|
1620
|
+
else {
|
|
1621
|
+
this.displayInput?.focus({ preventScroll: true });
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
handleOverflowActivate(event) {
|
|
1625
|
+
event.preventDefault();
|
|
1626
|
+
event.stopPropagation();
|
|
1627
|
+
if (this.disabled)
|
|
1628
|
+
return;
|
|
1629
|
+
this.open = !this.open;
|
|
1630
|
+
}
|
|
1631
|
+
handleOverflowKeyDown(event) {
|
|
1632
|
+
if (event.key !== KeyCode.ENTER && event.key !== KeyCode.SPACE)
|
|
1633
|
+
return;
|
|
1634
|
+
this.handleOverflowActivate(event);
|
|
1321
1635
|
}
|
|
1322
1636
|
async resetScrollPosition() {
|
|
1323
1637
|
await this.portalManager.resetScrollPosition();
|
|
@@ -1378,7 +1692,7 @@ __decorate([
|
|
|
1378
1692
|
property()
|
|
1379
1693
|
], NileVirtualSelect.prototype, "placeholder", void 0);
|
|
1380
1694
|
__decorate([
|
|
1381
|
-
property({ type: Boolean })
|
|
1695
|
+
property({ type: Boolean, attribute: true, reflect: true })
|
|
1382
1696
|
], NileVirtualSelect.prototype, "autoResize", void 0);
|
|
1383
1697
|
__decorate([
|
|
1384
1698
|
state()
|
|
@@ -1472,10 +1786,22 @@ __decorate([
|
|
|
1472
1786
|
], NileVirtualSelect.prototype, "portal", void 0);
|
|
1473
1787
|
__decorate([
|
|
1474
1788
|
property({ attribute: 'max-options-visible', type: Number })
|
|
1475
|
-
], NileVirtualSelect.prototype, "maxOptionsVisible",
|
|
1789
|
+
], NileVirtualSelect.prototype, "maxOptionsVisible", null);
|
|
1790
|
+
__decorate([
|
|
1791
|
+
property({ type: Number, reflect: true, attribute: true })
|
|
1792
|
+
], NileVirtualSelect.prototype, "chipMinWidth", void 0);
|
|
1793
|
+
__decorate([
|
|
1794
|
+
property({ type: Number, reflect: true, attribute: true })
|
|
1795
|
+
], NileVirtualSelect.prototype, "chipMaxWidth", void 0);
|
|
1796
|
+
__decorate([
|
|
1797
|
+
property({ type: Boolean, attribute: true, reflect: true })
|
|
1798
|
+
], NileVirtualSelect.prototype, "showTooltip", void 0);
|
|
1799
|
+
__decorate([
|
|
1800
|
+
state()
|
|
1801
|
+
], NileVirtualSelect.prototype, "hiddenChipCount", void 0);
|
|
1476
1802
|
__decorate([
|
|
1477
1803
|
state()
|
|
1478
|
-
], NileVirtualSelect.prototype, "
|
|
1804
|
+
], NileVirtualSelect.prototype, "clippedChips", void 0);
|
|
1479
1805
|
__decorate([
|
|
1480
1806
|
property({ type: Boolean, reflect: true, attribute: true })
|
|
1481
1807
|
], NileVirtualSelect.prototype, "enableTabClose", void 0);
|