@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
|
@@ -11,10 +11,12 @@ import { styles } from './nile-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 { animateTo, stopAnimations } from '../internal/animate';
|
|
15
16
|
import { classMap } from 'lit/directives/class-map.js';
|
|
16
17
|
import { query, state } from 'lit/decorators.js';
|
|
17
18
|
import { defaultValue } from '../internal/default-value';
|
|
19
|
+
import { KeyCode } from '../internal/enum';
|
|
18
20
|
import { FormControlController } from '../internal/form';
|
|
19
21
|
import { getAnimation, setDefaultAnimation, } from '../utilities/animation-registry';
|
|
20
22
|
import { HasSlotController } from '../internal/slot';
|
|
@@ -22,7 +24,6 @@ import { scrollIntoView } from '../internal/scroll';
|
|
|
22
24
|
import { waitForEvent } from '../internal/event';
|
|
23
25
|
import { watch } from '../internal/watch';
|
|
24
26
|
import NileElement from '../internal/nile-element';
|
|
25
|
-
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
|
26
27
|
import { ifDefined } from 'lit/directives/if-defined.js';
|
|
27
28
|
import '../nile-virtual-select/nile-virtual-select';
|
|
28
29
|
import { VirtualScrollHelper } from './virtual-scroll-helper';
|
|
@@ -88,6 +89,19 @@ import { VisibilityManager } from '../utilities/visibility-manager';
|
|
|
88
89
|
* @csspart footer-area - The sticky wrapper around the custom footer and the built-in multi-select footer.
|
|
89
90
|
*/
|
|
90
91
|
let NileSelect = class NileSelect extends NileElement {
|
|
92
|
+
get maxOptionsVisible() {
|
|
93
|
+
return this._maxOptionsVisible;
|
|
94
|
+
}
|
|
95
|
+
set maxOptionsVisible(value) {
|
|
96
|
+
const previous = this._maxOptionsVisible;
|
|
97
|
+
this._maxOptionsVisible = value;
|
|
98
|
+
// Any assignment opts this instance out of automatic chip fitting.
|
|
99
|
+
this.maxOptionsVisibleExplicit = true;
|
|
100
|
+
this.requestUpdate('maxOptionsVisible', previous);
|
|
101
|
+
}
|
|
102
|
+
get chipFittingEnabled() {
|
|
103
|
+
return !!this.multiple && !this.maxOptionsVisibleExplicit;
|
|
104
|
+
}
|
|
91
105
|
/** Gets the validity state object */
|
|
92
106
|
get validity() {
|
|
93
107
|
return this.valueInput?.validity;
|
|
@@ -103,10 +117,26 @@ let NileSelect = class NileSelect extends NileElement {
|
|
|
103
117
|
this.typeToSelectString = '';
|
|
104
118
|
this.scrolling = false;
|
|
105
119
|
this.options = [];
|
|
120
|
+
this.resizeController = new ResizeController(this, {
|
|
121
|
+
callback: (entries) => {
|
|
122
|
+
for (const entry of entries) {
|
|
123
|
+
if (entry.target.classList.contains('select__tags')) {
|
|
124
|
+
this.calculateTotalWidthOfTags();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
this.chipFitFrame = 0;
|
|
130
|
+
this.lastChipLayout = { hidden: -1, visible: -1 };
|
|
131
|
+
this.clippedChips = [];
|
|
132
|
+
this.clipFrame = 0;
|
|
106
133
|
this.hasFocus = false;
|
|
107
134
|
this.displayLabel = '';
|
|
108
135
|
this.selectedOptions = [];
|
|
109
136
|
this.oldValue = '';
|
|
137
|
+
this.hiddenChipCount = 0;
|
|
138
|
+
/** Last measured width of the overflow button, in px. */
|
|
139
|
+
this.lastOverflowWidth = 0;
|
|
110
140
|
/** The name of the select, submitted as a name/value pair with form data. */
|
|
111
141
|
this.name = '';
|
|
112
142
|
/**
|
|
@@ -141,6 +171,7 @@ let NileSelect = class NileSelect extends NileElement {
|
|
|
141
171
|
this.multiple = false;
|
|
142
172
|
this.helpText = '';
|
|
143
173
|
this.help_text = '';
|
|
174
|
+
/** Re-runs the fitted-chip layout when the control's own width changes. */
|
|
144
175
|
this.autoResize = false;
|
|
145
176
|
this.errorMessage = '';
|
|
146
177
|
/** Sets the input to a warning state, changing its visual appearance. */
|
|
@@ -153,7 +184,17 @@ let NileSelect = class NileSelect extends NileElement {
|
|
|
153
184
|
* The maximum number of selected options to show when `multiple` is true. After the maximum, "+n" will be shown to
|
|
154
185
|
* indicate the number of additional items that are selected. Set to 0 to remove the limit.
|
|
155
186
|
*/
|
|
156
|
-
this.
|
|
187
|
+
this._maxOptionsVisible = 3;
|
|
188
|
+
this.maxOptionsVisibleExplicit = false;
|
|
189
|
+
/** Minimum width, in px, of a squeezed chip. Needs `showtooltip`. */
|
|
190
|
+
this.chipMinWidth = 110;
|
|
191
|
+
/** Maximum width, in px, of a squeezed chip. Needs `showtooltip`. */
|
|
192
|
+
this.chipMaxWidth = 180;
|
|
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
|
+
this.showTooltip = false;
|
|
157
198
|
/** Disables the select control. */
|
|
158
199
|
this.disabled = false;
|
|
159
200
|
/** Adds a clear button when the select is not empty. */
|
|
@@ -188,7 +229,6 @@ let NileSelect = class NileSelect extends NileElement {
|
|
|
188
229
|
/** The select's required attribute. */
|
|
189
230
|
this.required = false;
|
|
190
231
|
this.showSelected = false;
|
|
191
|
-
this.oldMaxOptionsVisible = 1;
|
|
192
232
|
this.enableTabClose = false;
|
|
193
233
|
this.showNoResults = false;
|
|
194
234
|
this.noResultsMessage = 'No results found';
|
|
@@ -238,21 +278,38 @@ let NileSelect = class NileSelect extends NileElement {
|
|
|
238
278
|
this.visibilityManager?.cleanup();
|
|
239
279
|
// Clean up body append elements
|
|
240
280
|
this.portalManager.cleanupPortalAppend();
|
|
281
|
+
this.destroyChipFit();
|
|
241
282
|
this.emit('nile-destroy');
|
|
242
283
|
}
|
|
243
284
|
setupResizeObserver() {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
this.
|
|
251
|
-
|
|
285
|
+
const wanted = this.autoResize && this.chipFittingEnabled
|
|
286
|
+
? this.shadowRoot?.querySelector('.select__tags') ??
|
|
287
|
+
undefined
|
|
288
|
+
: undefined;
|
|
289
|
+
if (wanted === this.observedTagsContainer) {
|
|
290
|
+
if (!this.chipFittingEnabled)
|
|
291
|
+
this.destroyChipFit();
|
|
292
|
+
return;
|
|
252
293
|
}
|
|
253
|
-
|
|
254
|
-
this.resizeController
|
|
255
|
-
this.
|
|
294
|
+
if (this.observedTagsContainer) {
|
|
295
|
+
this.resizeController.unobserve(this.observedTagsContainer);
|
|
296
|
+
this.observedTagsContainer = undefined;
|
|
297
|
+
}
|
|
298
|
+
if (wanted) {
|
|
299
|
+
this.resizeController.observe(wanted);
|
|
300
|
+
this.observedTagsContainer = wanted;
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (!this.chipFittingEnabled) {
|
|
304
|
+
this.destroyChipFit();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
syncVirtualSelectMaxOptionsVisible() {
|
|
308
|
+
if (!this.enableVirtualScroll || !this.maxOptionsVisibleExplicit)
|
|
309
|
+
return;
|
|
310
|
+
const inner = this.shadowRoot?.querySelector('nile-virtual-select');
|
|
311
|
+
if (inner && inner.maxOptionsVisible !== this._maxOptionsVisible) {
|
|
312
|
+
inner.maxOptionsVisible = this._maxOptionsVisible;
|
|
256
313
|
}
|
|
257
314
|
}
|
|
258
315
|
updated(_changedProperties) {
|
|
@@ -268,12 +325,25 @@ let NileSelect = class NileSelect extends NileElement {
|
|
|
268
325
|
if (_changedProperties.has('tickOnSelect')) {
|
|
269
326
|
this.setTickOnSelectInOptions(this.tickOnSelect);
|
|
270
327
|
}
|
|
271
|
-
if (_changedProperties.has('autoResize')
|
|
328
|
+
if (_changedProperties.has('autoResize') ||
|
|
329
|
+
_changedProperties.has('multiple') ||
|
|
330
|
+
_changedProperties.has('maxOptionsVisible')) {
|
|
272
331
|
this.setupResizeObserver();
|
|
273
332
|
}
|
|
333
|
+
this.syncVirtualSelectMaxOptionsVisible();
|
|
274
334
|
if (_changedProperties.has('enableDescription')) {
|
|
275
335
|
this.setEnableDescriptionInOptions();
|
|
276
336
|
}
|
|
337
|
+
if (_changedProperties.has('chipMinWidth') ||
|
|
338
|
+
_changedProperties.has('chipMaxWidth')) {
|
|
339
|
+
this.destroyChipFit();
|
|
340
|
+
}
|
|
341
|
+
if (this.chipFittingEnabled) {
|
|
342
|
+
this.calculateTotalWidthOfTags();
|
|
343
|
+
}
|
|
344
|
+
if (this.multiple && this.showTooltip) {
|
|
345
|
+
this.scheduleClipCheck();
|
|
346
|
+
}
|
|
277
347
|
}
|
|
278
348
|
firstUpdated(_changedProperties) {
|
|
279
349
|
if (this.enableGroupHeader) {
|
|
@@ -560,8 +630,10 @@ let NileSelect = class NileSelect extends NileElement {
|
|
|
560
630
|
handleComboboxMouseDown(event) {
|
|
561
631
|
const path = event.composedPath();
|
|
562
632
|
const isIconButton = path.some(el => el instanceof Element && el.tagName.toLowerCase() === 'nile-icon-button');
|
|
633
|
+
const isOverflowButton = path.some(el => el instanceof HTMLButtonElement &&
|
|
634
|
+
el.classList.contains('select__tags-count'));
|
|
563
635
|
// Ignore disabled controls and clicks on tags (remove buttons)
|
|
564
|
-
if (this.disabled || isIconButton) {
|
|
636
|
+
if (this.disabled || isIconButton || isOverflowButton) {
|
|
565
637
|
return;
|
|
566
638
|
}
|
|
567
639
|
event.preventDefault();
|
|
@@ -727,22 +799,11 @@ let NileSelect = class NileSelect extends NileElement {
|
|
|
727
799
|
}
|
|
728
800
|
return options;
|
|
729
801
|
}
|
|
730
|
-
|
|
802
|
+
getOptionPrefixNodes(option) {
|
|
731
803
|
const prefixSlot = option.shadowRoot?.querySelector('slot[name="prefix"]');
|
|
732
804
|
if (!prefixSlot)
|
|
733
|
-
return
|
|
734
|
-
|
|
735
|
-
const htmlStrings = [];
|
|
736
|
-
assignedNodes.forEach(node => {
|
|
737
|
-
if (node instanceof HTMLElement) {
|
|
738
|
-
htmlStrings.push(node.outerHTML);
|
|
739
|
-
}
|
|
740
|
-
else if (node.nodeType === Node.TEXT_NODE) {
|
|
741
|
-
// Text node
|
|
742
|
-
htmlStrings.push(node.textContent || '');
|
|
743
|
-
}
|
|
744
|
-
});
|
|
745
|
-
return htmlStrings.join('');
|
|
805
|
+
return [];
|
|
806
|
+
return prefixSlot.assignedElements().map(el => el.cloneNode(true));
|
|
746
807
|
}
|
|
747
808
|
// Gets the first <nile-option> element
|
|
748
809
|
getFirstOption() {
|
|
@@ -799,13 +860,16 @@ let NileSelect = class NileSelect extends NileElement {
|
|
|
799
860
|
this.selectedOptions = this.getAllOptions().filter(el => el.selected);
|
|
800
861
|
// Update the value and display label
|
|
801
862
|
if (this.multiple) {
|
|
802
|
-
|
|
803
|
-
if (
|
|
804
|
-
|
|
805
|
-
this.
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
863
|
+
const hasOptions = this.getAllOptions().length > 0;
|
|
864
|
+
if (hasOptions) {
|
|
865
|
+
this.value = this.selectedOptions.map(el => el.value);
|
|
866
|
+
if (this.placeholder && this.value.length === 0) {
|
|
867
|
+
// When no items are selected, keep the value empty so the placeholder shows
|
|
868
|
+
this.displayLabel = '';
|
|
869
|
+
}
|
|
870
|
+
else {
|
|
871
|
+
this.displayLabel = this.selectedOptions.length + ' selected';
|
|
872
|
+
}
|
|
809
873
|
}
|
|
810
874
|
if (this.selectedOptions.length === 0) {
|
|
811
875
|
this.showSelected = false;
|
|
@@ -1070,49 +1134,293 @@ let NileSelect = class NileSelect extends NileElement {
|
|
|
1070
1134
|
// This will stop the event from bubbling up to the parent `nile-select` component
|
|
1071
1135
|
event.stopPropagation();
|
|
1072
1136
|
}
|
|
1073
|
-
|
|
1074
|
-
if (this.
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1137
|
+
calculateTotalWidthOfTags() {
|
|
1138
|
+
if (!this.chipFittingEnabled)
|
|
1139
|
+
return;
|
|
1140
|
+
if (this.chipFitFrame)
|
|
1141
|
+
cancelAnimationFrame(this.chipFitFrame);
|
|
1142
|
+
this.chipFitFrame = requestAnimationFrame(() => {
|
|
1143
|
+
this.chipFitFrame = 0;
|
|
1144
|
+
this.layoutChips();
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
allocateChipWidths(natural, available) {
|
|
1148
|
+
const min = this.chipMinWidth;
|
|
1149
|
+
const max = this.chipMaxWidth;
|
|
1150
|
+
const total = natural.reduce((sum, w) => sum + w, 0);
|
|
1151
|
+
if (total <= available) {
|
|
1152
|
+
return natural.slice();
|
|
1153
|
+
}
|
|
1154
|
+
// Nothing reveals a truncated value without a tooltip, so chips keep their
|
|
1155
|
+
// natural width and the surplus moves into the +N button instead.
|
|
1156
|
+
if (!this.showTooltip)
|
|
1157
|
+
return null;
|
|
1158
|
+
const capped = natural.map(w => Math.min(w, max));
|
|
1159
|
+
const cappedTotal = capped.reduce((sum, w) => sum + w, 0);
|
|
1160
|
+
if (cappedTotal <= available) {
|
|
1161
|
+
return capped;
|
|
1162
|
+
}
|
|
1163
|
+
const widths = new Array(natural.length).fill(0);
|
|
1164
|
+
let pool = capped.map((_, i) => i);
|
|
1165
|
+
let remaining = available;
|
|
1166
|
+
for (;;) {
|
|
1167
|
+
if (pool.length === 0)
|
|
1168
|
+
break;
|
|
1169
|
+
const share = remaining / pool.length;
|
|
1170
|
+
const settled = pool.filter(i => capped[i] <= share);
|
|
1171
|
+
if (settled.length === 0)
|
|
1172
|
+
break;
|
|
1173
|
+
for (const i of settled) {
|
|
1174
|
+
widths[i] = capped[i];
|
|
1175
|
+
remaining -= capped[i];
|
|
1079
1176
|
}
|
|
1177
|
+
pool = pool.filter(i => capped[i] > share);
|
|
1080
1178
|
}
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
}
|
|
1086
|
-
this.maxOptionsVisible = Infinity;
|
|
1087
|
-
setTimeout(() => {
|
|
1088
|
-
let widths = [];
|
|
1089
|
-
if (this.shadowRoot) {
|
|
1090
|
-
const tags = this.shadowRoot.querySelectorAll('nile-tag');
|
|
1091
|
-
tags.forEach(tag => {
|
|
1092
|
-
if (tag instanceof HTMLElement) {
|
|
1093
|
-
widths.push(tag.offsetWidth);
|
|
1094
|
-
}
|
|
1095
|
-
});
|
|
1179
|
+
if (pool.length > 0) {
|
|
1180
|
+
const share = remaining / pool.length;
|
|
1181
|
+
if (share < min) {
|
|
1182
|
+
return null;
|
|
1096
1183
|
}
|
|
1097
|
-
|
|
1098
|
-
|
|
1184
|
+
const width = Math.floor(share);
|
|
1185
|
+
for (const i of pool) {
|
|
1186
|
+
widths[i] = width;
|
|
1099
1187
|
}
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1188
|
+
}
|
|
1189
|
+
return widths;
|
|
1190
|
+
}
|
|
1191
|
+
computeChipFit(natural, available, overflowWidth, gap) {
|
|
1192
|
+
const total = natural.length;
|
|
1193
|
+
if (total === 0) {
|
|
1194
|
+
return { visibleCount: 0, widths: [], hiddenCount: 0 };
|
|
1195
|
+
}
|
|
1196
|
+
for (let count = total; count >= 1; count--) {
|
|
1197
|
+
const hidden = total - count;
|
|
1198
|
+
const reserved = hidden > 0 ? overflowWidth + gap : 0;
|
|
1199
|
+
const row = available - reserved - gap * (count - 1);
|
|
1200
|
+
if (row <= 0)
|
|
1201
|
+
continue;
|
|
1202
|
+
const widths = this.allocateChipWidths(natural.slice(0, count), row);
|
|
1203
|
+
if (widths) {
|
|
1204
|
+
return { visibleCount: count, widths, hiddenCount: hidden };
|
|
1104
1205
|
}
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1206
|
+
}
|
|
1207
|
+
const room = available - overflowWidth - gap;
|
|
1208
|
+
const fallback = Math.max(0,
|
|
1209
|
+
// A single chip too wide for the row has to be clipped either way; only
|
|
1210
|
+
// the tooltip variant squeezes it to chipMinWidth.
|
|
1211
|
+
Math.min(natural[0], this.showTooltip ? Math.max(this.chipMinWidth, room) : room));
|
|
1212
|
+
return { visibleCount: 1, widths: [fallback], hiddenCount: total - 1 };
|
|
1213
|
+
}
|
|
1214
|
+
layoutChips() {
|
|
1215
|
+
const root = this.shadowRoot;
|
|
1216
|
+
if (!root)
|
|
1217
|
+
return;
|
|
1218
|
+
const container = root.querySelector('.select__tags');
|
|
1219
|
+
if (!container)
|
|
1220
|
+
return;
|
|
1221
|
+
const chips = Array.from(container.querySelectorAll('nile-tag'));
|
|
1222
|
+
if (chips.length === 0) {
|
|
1223
|
+
this.hiddenChipCount = 0;
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
const overflowEl = container.querySelector('.select__tags-count');
|
|
1227
|
+
for (const chip of chips) {
|
|
1228
|
+
chip.style.display = '';
|
|
1229
|
+
chip.style.width = 'auto';
|
|
1230
|
+
chip.style.maxWidth = 'none';
|
|
1231
|
+
chip.style.minWidth = '0';
|
|
1232
|
+
chip.style.flex = '0 0 auto';
|
|
1233
|
+
}
|
|
1234
|
+
const natural = chips.map(chip => Math.ceil(chip.getBoundingClientRect().width));
|
|
1235
|
+
// Not rendered yet on the pass that first finds an overflow; reuse the last width.
|
|
1236
|
+
const overflowWidth = overflowEl
|
|
1237
|
+
? Math.ceil(overflowEl.getBoundingClientRect().width)
|
|
1238
|
+
: this.lastOverflowWidth;
|
|
1239
|
+
if (overflowEl)
|
|
1240
|
+
this.lastOverflowWidth = overflowWidth;
|
|
1241
|
+
const containerStyles = getComputedStyle(container);
|
|
1242
|
+
const gap = parseFloat(containerStyles.columnGap || containerStyles.gap || '0') || 0;
|
|
1243
|
+
const fit = this.computeChipFit(natural, container.clientWidth, overflowWidth, gap);
|
|
1244
|
+
chips.forEach((chip, index) => {
|
|
1245
|
+
if (index < fit.visibleCount) {
|
|
1246
|
+
// Never squeeze a chip below chipMinWidth: narrower than that it is
|
|
1247
|
+
// also narrower than the tag's own box - the label is already at zero
|
|
1248
|
+
// while the remove button is fixed - so the box overflows the chip and
|
|
1249
|
+
// paints the X over the next chip or the +N button. A chip whose
|
|
1250
|
+
// natural width is already smaller is left alone, not padded out.
|
|
1251
|
+
const width = Math.min(natural[index], Math.max(fit.widths[index], this.chipMinWidth));
|
|
1252
|
+
chip.style.display = '';
|
|
1253
|
+
chip.style.width = `${width}px`;
|
|
1254
|
+
chip.style.maxWidth = `${width}px`;
|
|
1255
|
+
chip.style.flex = '0 0 auto';
|
|
1256
|
+
}
|
|
1257
|
+
else {
|
|
1258
|
+
chip.style.display = 'none';
|
|
1113
1259
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1260
|
+
});
|
|
1261
|
+
if (this.lastChipLayout.hidden !== fit.hiddenCount ||
|
|
1262
|
+
this.lastChipLayout.visible !== fit.visibleCount) {
|
|
1263
|
+
this.lastChipLayout = {
|
|
1264
|
+
hidden: fit.hiddenCount,
|
|
1265
|
+
visible: fit.visibleCount
|
|
1266
|
+
};
|
|
1267
|
+
this.hiddenChipCount = fit.hiddenCount;
|
|
1268
|
+
}
|
|
1269
|
+
this.refreshClippedChips();
|
|
1270
|
+
}
|
|
1271
|
+
isChipClipped(el) {
|
|
1272
|
+
if (!el)
|
|
1273
|
+
return false;
|
|
1274
|
+
const root = el.shadowRoot;
|
|
1275
|
+
if (root) {
|
|
1276
|
+
const content = root.querySelector('[part="content"]');
|
|
1277
|
+
if (content && content.scrollWidth > content.clientWidth + 1)
|
|
1278
|
+
return true;
|
|
1279
|
+
const inner = root.querySelector('[part="prefix_content_wrapper"]');
|
|
1280
|
+
if (inner && inner.scrollWidth > inner.clientWidth + 1)
|
|
1281
|
+
return true;
|
|
1282
|
+
}
|
|
1283
|
+
return el.scrollWidth > el.clientWidth + 1;
|
|
1284
|
+
}
|
|
1285
|
+
refreshClippedChips() {
|
|
1286
|
+
if (!this.multiple)
|
|
1287
|
+
return;
|
|
1288
|
+
const root = this.shadowRoot;
|
|
1289
|
+
if (!root)
|
|
1290
|
+
return;
|
|
1291
|
+
const chips = Array.from(root.querySelectorAll('.select__tags nile-tag'));
|
|
1292
|
+
const next = chips.map(chip => this.isChipClipped(chip));
|
|
1293
|
+
const same = next.length === this.clippedChips.length &&
|
|
1294
|
+
next.every((v, i) => v === this.clippedChips[i]);
|
|
1295
|
+
if (!same) {
|
|
1296
|
+
this.clippedChips = next;
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
scheduleClipCheck() {
|
|
1300
|
+
if (this.clipFrame)
|
|
1301
|
+
cancelAnimationFrame(this.clipFrame);
|
|
1302
|
+
this.clipFrame = requestAnimationFrame(() => {
|
|
1303
|
+
this.clipFrame = 0;
|
|
1304
|
+
this.refreshClippedChips();
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
/** A chip gets a tooltip only while its text is measured as clipped. */
|
|
1308
|
+
chipTooltipDisabled(index) {
|
|
1309
|
+
return !this.showTooltip || !(this.clippedChips[index] ?? false);
|
|
1310
|
+
}
|
|
1311
|
+
destroyChipFit() {
|
|
1312
|
+
if (this.chipFitFrame)
|
|
1313
|
+
cancelAnimationFrame(this.chipFitFrame);
|
|
1314
|
+
this.chipFitFrame = 0;
|
|
1315
|
+
if (this.clipFrame)
|
|
1316
|
+
cancelAnimationFrame(this.clipFrame);
|
|
1317
|
+
this.clipFrame = 0;
|
|
1318
|
+
this.lastChipLayout = { hidden: -1, visible: -1 };
|
|
1319
|
+
}
|
|
1320
|
+
getChipLabel(option) {
|
|
1321
|
+
return (option.getTextLabel?.() ?? '').trim();
|
|
1322
|
+
}
|
|
1323
|
+
handleChipKeyDown(event, option) {
|
|
1324
|
+
if (event.key !== KeyCode.BACKSPACE && event.key !== KeyCode.DELETE)
|
|
1325
|
+
return;
|
|
1326
|
+
if (this.disabled)
|
|
1327
|
+
return;
|
|
1328
|
+
event.preventDefault();
|
|
1329
|
+
event.stopPropagation();
|
|
1330
|
+
const removedIndex = this.selectedOptions.indexOf(option);
|
|
1331
|
+
this.handleTagRemove(new CustomEvent('nile-remove'), option);
|
|
1332
|
+
this.restoreFocusAfterChipRemoval(removedIndex);
|
|
1333
|
+
}
|
|
1334
|
+
/** Moves focus to the next chip, the last chip, or the field after a removal. */
|
|
1335
|
+
async restoreFocusAfterChipRemoval(removedIndex) {
|
|
1336
|
+
if (removedIndex < 0)
|
|
1337
|
+
return;
|
|
1338
|
+
await this.updateComplete;
|
|
1339
|
+
// Wait for the fit pass so hidden chips are not focused.
|
|
1340
|
+
await new Promise(resolve => requestAnimationFrame(() => resolve()));
|
|
1341
|
+
const visibleChips = Array.from(this.shadowRoot?.querySelectorAll('.select__tags nile-tag') ??
|
|
1342
|
+
[]).filter(chip => chip.style.display !== 'none');
|
|
1343
|
+
const target = visibleChips[removedIndex] ?? visibleChips[visibleChips.length - 1];
|
|
1344
|
+
if (target) {
|
|
1345
|
+
target.focus({ preventScroll: true });
|
|
1346
|
+
}
|
|
1347
|
+
else {
|
|
1348
|
+
this.displayInput?.focus({ preventScroll: true });
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
handleOverflowActivate(event) {
|
|
1352
|
+
event.preventDefault();
|
|
1353
|
+
event.stopPropagation();
|
|
1354
|
+
if (this.disabled)
|
|
1355
|
+
return;
|
|
1356
|
+
if (this.open) {
|
|
1357
|
+
this.hide();
|
|
1358
|
+
}
|
|
1359
|
+
else {
|
|
1360
|
+
this.show();
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
handleOverflowKeyDown(event) {
|
|
1364
|
+
if (event.key !== KeyCode.ENTER && event.key !== KeyCode.SPACE)
|
|
1365
|
+
return;
|
|
1366
|
+
this.handleOverflowActivate(event);
|
|
1367
|
+
}
|
|
1368
|
+
renderFittedTags() {
|
|
1369
|
+
return html `
|
|
1370
|
+
<div part="tags" class="select__tags select__tags--fitted">
|
|
1371
|
+
${this.selectedOptions.map((option, index) => {
|
|
1372
|
+
const label = this.getChipLabel(option);
|
|
1373
|
+
const prefixNodes = this.getOptionPrefixNodes(option);
|
|
1374
|
+
return html `
|
|
1375
|
+
<nile-lite-tooltip
|
|
1376
|
+
class="select__tag-tip"
|
|
1377
|
+
.content=${label}
|
|
1378
|
+
?disabled=${this.chipTooltipDisabled(index)}
|
|
1379
|
+
>
|
|
1380
|
+
<nile-tag
|
|
1381
|
+
part="tag"
|
|
1382
|
+
exportparts="
|
|
1383
|
+
base:tag__base,
|
|
1384
|
+
content:tag__content,
|
|
1385
|
+
remove-button:tag__remove-button,
|
|
1386
|
+
remove-button__base:tag__remove-button__base,
|
|
1387
|
+
prefix:tag__prefix,
|
|
1388
|
+
prefix_content_wrapper:tag__prefix_content_wrapper
|
|
1389
|
+
"
|
|
1390
|
+
?pill=${this.pill}
|
|
1391
|
+
size=${this.size}
|
|
1392
|
+
removable
|
|
1393
|
+
tabindex="0"
|
|
1394
|
+
aria-label=${`${label}. Press Backspace or Delete to remove.`}
|
|
1395
|
+
@nile-remove=${(event) => this.handleTagRemove(event, option)}
|
|
1396
|
+
@keydown=${(event) => this.handleChipKeyDown(event, option)}
|
|
1397
|
+
>
|
|
1398
|
+
${prefixNodes.length
|
|
1399
|
+
? html `<span class="tag__prefix" slot="prefix"
|
|
1400
|
+
>${prefixNodes}</span
|
|
1401
|
+
>`
|
|
1402
|
+
: ''}
|
|
1403
|
+
${label}
|
|
1404
|
+
</nile-tag>
|
|
1405
|
+
</nile-lite-tooltip>
|
|
1406
|
+
`;
|
|
1407
|
+
})}
|
|
1408
|
+
${this.hiddenChipCount > 0
|
|
1409
|
+
? html `<button
|
|
1410
|
+
type="button"
|
|
1411
|
+
class="select__tags-count"
|
|
1412
|
+
?disabled=${this.disabled}
|
|
1413
|
+
aria-controls="listbox"
|
|
1414
|
+
aria-expanded=${this.open ? 'true' : 'false'}
|
|
1415
|
+
aria-haspopup="listbox"
|
|
1416
|
+
aria-label=${`${this.hiddenChipCount} more selected`}
|
|
1417
|
+
@click=${this.handleOverflowActivate}
|
|
1418
|
+
@keydown=${this.handleOverflowKeyDown}
|
|
1419
|
+
>+${this.hiddenChipCount}</button
|
|
1420
|
+
>`
|
|
1421
|
+
: ''}
|
|
1422
|
+
</div>
|
|
1423
|
+
`;
|
|
1116
1424
|
}
|
|
1117
1425
|
handleInputAfterInit() {
|
|
1118
1426
|
this.shadowRoot?.querySelector('nile-input')?.inputFocus();
|
|
@@ -1130,9 +1438,9 @@ let NileSelect = class NileSelect extends NileElement {
|
|
|
1130
1438
|
const isPlaceholderVisible = this.placeholder && this.value.length === 0;
|
|
1131
1439
|
const hasHelpText = (this.helpText || this.help_text) ? true : false;
|
|
1132
1440
|
const hasErrorMessage = this.errorMessage ? true : false;
|
|
1133
|
-
const
|
|
1134
|
-
? this.
|
|
1135
|
-
:
|
|
1441
|
+
const prefixNodes = this.selectedOptions[0]
|
|
1442
|
+
? this.getOptionPrefixNodes(this.selectedOptions[0])
|
|
1443
|
+
: [];
|
|
1136
1444
|
return html `
|
|
1137
1445
|
<div
|
|
1138
1446
|
part="form-control"
|
|
@@ -1206,9 +1514,9 @@ let NileSelect = class NileSelect extends NileElement {
|
|
|
1206
1514
|
@keydown=${this.handleComboboxKeyDown}
|
|
1207
1515
|
@mousedown=${this.handleComboboxMouseDown}
|
|
1208
1516
|
>
|
|
1209
|
-
${
|
|
1517
|
+
${prefixNodes.length && !this.multiple
|
|
1210
1518
|
? html `<div class="select__prefix--from-options">
|
|
1211
|
-
${
|
|
1519
|
+
${prefixNodes}
|
|
1212
1520
|
</div>`
|
|
1213
1521
|
: html `<slot
|
|
1214
1522
|
part="prefix"
|
|
@@ -1243,18 +1551,21 @@ let NileSelect = class NileSelect extends NileElement {
|
|
|
1243
1551
|
/>
|
|
1244
1552
|
|
|
1245
1553
|
${this.multiple
|
|
1246
|
-
?
|
|
1554
|
+
? this.chipFittingEnabled
|
|
1555
|
+
? this.renderFittedTags()
|
|
1556
|
+
: html `
|
|
1247
1557
|
<div part="tags" class="select__tags">
|
|
1248
1558
|
${this.selectedOptions.map((option, index) => {
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1559
|
+
if (index < this.maxOptionsVisible ||
|
|
1560
|
+
this.maxOptionsVisible <= 0) {
|
|
1561
|
+
const prefixNodes = this.getOptionPrefixNodes(option);
|
|
1562
|
+
return html `
|
|
1563
|
+
<nile-lite-tooltip
|
|
1564
|
+
class="select__tag-tip"
|
|
1565
|
+
.content=${option.getTextLabel()}
|
|
1566
|
+
?disabled=${this.chipTooltipDisabled(index)}
|
|
1567
|
+
>
|
|
1256
1568
|
<nile-tag
|
|
1257
|
-
class=${classMap(classes)}
|
|
1258
1569
|
part="tag"
|
|
1259
1570
|
exportparts="
|
|
1260
1571
|
base:tag__base,
|
|
@@ -1264,31 +1575,33 @@ let NileSelect = class NileSelect extends NileElement {
|
|
|
1264
1575
|
prefix:tag__prefix,
|
|
1265
1576
|
prefix_content_wrapper:tag__prefix_content_wrapper
|
|
1266
1577
|
"
|
|
1578
|
+
style="max-width:${this.chipMaxWidth}px"
|
|
1267
1579
|
?pill=${this.pill}
|
|
1268
1580
|
size=${this.size}
|
|
1269
1581
|
removable
|
|
1270
1582
|
@nile-remove=${(event) => this.handleTagRemove(event, option)}
|
|
1271
1583
|
>
|
|
1272
|
-
${
|
|
1273
|
-
|
|
1274
|
-
>${
|
|
1584
|
+
${prefixNodes.length
|
|
1585
|
+
? html `<span class="tag__prefix" slot="prefix"
|
|
1586
|
+
>${prefixNodes}</span
|
|
1275
1587
|
>`
|
|
1276
|
-
|
|
1588
|
+
: ''}
|
|
1277
1589
|
${option.getTextLabel()}
|
|
1278
1590
|
</nile-tag>
|
|
1591
|
+
</nile-lite-tooltip>
|
|
1279
1592
|
`;
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1593
|
+
}
|
|
1594
|
+
else if (index === this.maxOptionsVisible) {
|
|
1595
|
+
return html `
|
|
1283
1596
|
<span class="select__tags-count"
|
|
1284
1597
|
>+${this.selectedOptions.length - index} More
|
|
1285
1598
|
</span>
|
|
1286
1599
|
`;
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1600
|
+
}
|
|
1601
|
+
else {
|
|
1602
|
+
return null;
|
|
1603
|
+
}
|
|
1604
|
+
})}
|
|
1292
1605
|
</div>
|
|
1293
1606
|
`
|
|
1294
1607
|
: ''}
|
|
@@ -1418,21 +1731,35 @@ let NileSelect = class NileSelect extends NileElement {
|
|
|
1418
1731
|
@click="${this.handleFooterClick}"
|
|
1419
1732
|
>
|
|
1420
1733
|
<span
|
|
1734
|
+
class="select__footer-action"
|
|
1421
1735
|
@click="${this.toggleShowSelected}"
|
|
1422
1736
|
style="cursor: pointer;"
|
|
1737
|
+
title="Show selected"
|
|
1423
1738
|
>
|
|
1424
1739
|
<nile-checkbox
|
|
1425
1740
|
?disabled="${this.selectedOptions.length === 0}"
|
|
1426
1741
|
?checked="${this.showSelected}"
|
|
1427
1742
|
>
|
|
1428
|
-
|
|
1743
|
+
<span class="select__footer-label"
|
|
1744
|
+
>Selected</span
|
|
1745
|
+
></nile-checkbox
|
|
1429
1746
|
>
|
|
1430
1747
|
</span>
|
|
1431
1748
|
${this.selectedOptions.length !== 0
|
|
1432
1749
|
? html `<span
|
|
1433
1750
|
class="select__clear"
|
|
1434
1751
|
@click="${this.unSlectAll}"
|
|
1435
|
-
|
|
1752
|
+
title="Clear"
|
|
1753
|
+
><nile-glyph
|
|
1754
|
+
class="select__footer-glyph"
|
|
1755
|
+
name="ng-eraser"
|
|
1756
|
+
size="16"
|
|
1757
|
+
method="var(--nile-svg-method-stroke, var(--ng-svg-method-stroke))"
|
|
1758
|
+
color="var(--nile-colors-primary-600, var(--ng-colors-text-brand-secondary-700))"
|
|
1759
|
+
></nile-glyph
|
|
1760
|
+
><span class="select__footer-label"
|
|
1761
|
+
>Clear</span
|
|
1762
|
+
></span
|
|
1436
1763
|
>`
|
|
1437
1764
|
: ``}
|
|
1438
1765
|
</div>`
|
|
@@ -1464,6 +1791,9 @@ let NileSelect = class NileSelect extends NileElement {
|
|
|
1464
1791
|
}
|
|
1465
1792
|
};
|
|
1466
1793
|
NileSelect.styles = styles;
|
|
1794
|
+
__decorate([
|
|
1795
|
+
state()
|
|
1796
|
+
], NileSelect.prototype, "clippedChips", void 0);
|
|
1467
1797
|
__decorate([
|
|
1468
1798
|
query('.select')
|
|
1469
1799
|
], NileSelect.prototype, "popup", void 0);
|
|
@@ -1494,6 +1824,9 @@ __decorate([
|
|
|
1494
1824
|
__decorate([
|
|
1495
1825
|
state()
|
|
1496
1826
|
], NileSelect.prototype, "oldValue", void 0);
|
|
1827
|
+
__decorate([
|
|
1828
|
+
state()
|
|
1829
|
+
], NileSelect.prototype, "hiddenChipCount", void 0);
|
|
1497
1830
|
__decorate([
|
|
1498
1831
|
property()
|
|
1499
1832
|
], NileSelect.prototype, "name", void 0);
|
|
@@ -1587,7 +1920,16 @@ __decorate([
|
|
|
1587
1920
|
], NileSelect.prototype, "success", void 0);
|
|
1588
1921
|
__decorate([
|
|
1589
1922
|
property({ attribute: 'max-options-visible', type: Number })
|
|
1590
|
-
], NileSelect.prototype, "maxOptionsVisible",
|
|
1923
|
+
], NileSelect.prototype, "maxOptionsVisible", null);
|
|
1924
|
+
__decorate([
|
|
1925
|
+
property({ type: Number, reflect: true, attribute: true })
|
|
1926
|
+
], NileSelect.prototype, "chipMinWidth", void 0);
|
|
1927
|
+
__decorate([
|
|
1928
|
+
property({ type: Number, reflect: true, attribute: true })
|
|
1929
|
+
], NileSelect.prototype, "chipMaxWidth", void 0);
|
|
1930
|
+
__decorate([
|
|
1931
|
+
property({ type: Boolean, attribute: true, reflect: true })
|
|
1932
|
+
], NileSelect.prototype, "showTooltip", void 0);
|
|
1591
1933
|
__decorate([
|
|
1592
1934
|
property({ type: Boolean, reflect: true })
|
|
1593
1935
|
], NileSelect.prototype, "disabled", void 0);
|
|
@@ -1621,9 +1963,6 @@ __decorate([
|
|
|
1621
1963
|
__decorate([
|
|
1622
1964
|
property({ type: Boolean })
|
|
1623
1965
|
], NileSelect.prototype, "showSelected", void 0);
|
|
1624
|
-
__decorate([
|
|
1625
|
-
state()
|
|
1626
|
-
], NileSelect.prototype, "oldMaxOptionsVisible", void 0);
|
|
1627
1966
|
__decorate([
|
|
1628
1967
|
property({ type: Boolean, reflect: true, attribute: true })
|
|
1629
1968
|
], NileSelect.prototype, "enableTabClose", void 0);
|