@aquera/nile-elements 2.0.8 → 2.0.10
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 +12 -0
- package/demo/nxtgen-classes.css +5 -5
- package/demo/nxtgen-dark.css +25 -25
- package/demo/nxtgen-properties.css +2 -2
- package/demo/nxtgen-utilities.css +5 -5
- package/demo/nxtgen.css +5 -5
- package/demo/utilities.css +2 -2
- package/demo/variables.css +2 -2
- package/dist/index.js +634 -264
- package/dist/nile-badge/nile-badge.css.cjs.js +1 -1
- package/dist/nile-badge/nile-badge.css.cjs.js.map +1 -1
- package/dist/nile-badge/nile-badge.css.esm.js +2 -2
- package/dist/nile-button-toggle/nile-button-toggle.css.cjs.js +1 -1
- package/dist/nile-button-toggle/nile-button-toggle.css.cjs.js.map +1 -1
- package/dist/nile-button-toggle/nile-button-toggle.css.esm.js +2 -2
- package/dist/nile-code-editor/nile-code-editor.cjs.js +1 -1
- package/dist/nile-code-editor/nile-code-editor.cjs.js.map +1 -1
- package/dist/nile-code-editor/nile-code-editor.esm.js +1 -1
- package/dist/nile-menu-item/nile-menu-item.css.cjs.js +1 -1
- package/dist/nile-menu-item/nile-menu-item.css.cjs.js.map +1 -1
- package/dist/nile-menu-item/nile-menu-item.css.esm.js +1 -1
- package/dist/nile-rich-text-editor/nile-rich-text-editor.css.cjs.js +1 -1
- package/dist/nile-rich-text-editor/nile-rich-text-editor.css.cjs.js.map +1 -1
- package/dist/nile-rich-text-editor/nile-rich-text-editor.css.esm.js +5 -2
- 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-toast/nile-toast.css.cjs.js +1 -1
- package/dist/nile-toast/nile-toast.css.cjs.js.map +1 -1
- package/dist/nile-toast/nile-toast.css.esm.js +5 -5
- 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-badge/nile-badge.css.js +2 -2
- package/dist/src/nile-badge/nile-badge.css.js.map +1 -1
- package/dist/src/nile-button-toggle/nile-button-toggle.css.js +2 -2
- package/dist/src/nile-button-toggle/nile-button-toggle.css.js.map +1 -1
- package/dist/src/nile-code-editor/nile-code-editor.d.ts +1 -1
- package/dist/src/nile-code-editor/nile-code-editor.js +2 -2
- package/dist/src/nile-code-editor/nile-code-editor.js.map +1 -1
- package/dist/src/nile-menu-item/nile-menu-item.css.js +1 -1
- package/dist/src/nile-menu-item/nile-menu-item.css.js.map +1 -1
- package/dist/src/nile-rich-text-editor/nile-rich-text-editor.css.js +5 -2
- package/dist/src/nile-rich-text-editor/nile-rich-text-editor.css.js.map +1 -1
- 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-toast/nile-toast.css.js +5 -5
- package/dist/src/nile-toast/nile-toast.css.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/dist/version.cjs.js +1 -1
- package/dist/version.cjs.js.map +1 -1
- package/dist/version.esm.js +1 -1
- package/package.json +1 -1
- package/src/nile-badge/nile-badge.css.ts +2 -2
- package/src/nile-button-toggle/nile-button-toggle.css.ts +2 -2
- package/src/nile-code-editor/nile-code-editor.ts +2 -2
- package/src/nile-menu-item/nile-menu-item.css.ts +1 -1
- package/src/nile-rich-text-editor/nile-rich-text-editor.css.ts +5 -2
- 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-toast/nile-toast.css.ts +5 -5
- 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
|
@@ -15,12 +15,14 @@ import { styles } from './nile-virtual-select.css';
|
|
|
15
15
|
import '../nile-icon';
|
|
16
16
|
import '../nile-popup/nile-popup';
|
|
17
17
|
import '../nile-tag/nile-tag';
|
|
18
|
+
import '../nile-lite-tooltip/nile-lite-tooltip';
|
|
18
19
|
import '../nile-checkbox/nile-checkbox';
|
|
19
20
|
import '../nile-loader/nile-loader';
|
|
20
21
|
import { animateTo, stopAnimations } from '../internal/animate';
|
|
21
22
|
import { classMap } from 'lit/directives/class-map.js';
|
|
22
23
|
import { query, state } from 'lit/decorators.js';
|
|
23
24
|
import { defaultValue } from '../internal/default-value';
|
|
25
|
+
import { KeyCode } from '../internal/enum';
|
|
24
26
|
import { FormControlController } from '../internal/form';
|
|
25
27
|
import {
|
|
26
28
|
getAnimation,
|
|
@@ -92,6 +94,10 @@ import { VisibilityManager } from '../utilities/visibility-manager.js';
|
|
|
92
94
|
* @csspart listbox - The listbox container where options are slotted.
|
|
93
95
|
* @csspart tags - The container that houses option tags when `multiple` is used.
|
|
94
96
|
* @csspart tag - The individual tags that represent each multiselect option.
|
|
97
|
+
* @csspart tag__base - The tag's base part.
|
|
98
|
+
* @csspart tag__content - The tag's content part.
|
|
99
|
+
* @csspart tag__remove-button - The tag's remove button.
|
|
100
|
+
* @csspart tag__remove-button__base - The tag's remove button base part.
|
|
95
101
|
* @csspart clear-button - The clear button.
|
|
96
102
|
* @csspart expand-icon - The container that wraps the expand icon.
|
|
97
103
|
* @csspart footer - The footer container with show selected and clear all options.
|
|
@@ -155,8 +161,8 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
|
|
|
155
161
|
/** Placeholder text to show as a hint when the select is empty. */
|
|
156
162
|
@property() placeholder = 'Select...';
|
|
157
163
|
|
|
158
|
-
|
|
159
|
-
|
|
164
|
+
/** Re-runs the fitted-chip layout when the control's own width changes. */
|
|
165
|
+
@property({ type: Boolean, attribute: true, reflect: true }) autoResize = false;
|
|
160
166
|
|
|
161
167
|
/** Current search value */
|
|
162
168
|
@state() searchValue: string = '';
|
|
@@ -264,10 +270,49 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
|
|
|
264
270
|
* The maximum number of selected options to show when `multiple` is true. After the maximum, "+n" will be shown to
|
|
265
271
|
* indicate the number of additional items that are selected. Set to 0 to remove the limit.
|
|
266
272
|
*/
|
|
273
|
+
private _maxOptionsVisible = 3;
|
|
274
|
+
|
|
275
|
+
private maxOptionsVisibleExplicit = false;
|
|
276
|
+
|
|
267
277
|
@property({ attribute: 'max-options-visible', type: Number })
|
|
268
|
-
maxOptionsVisible
|
|
278
|
+
get maxOptionsVisible(): number {
|
|
279
|
+
return this._maxOptionsVisible;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
set maxOptionsVisible(value: number) {
|
|
283
|
+
const previous = this._maxOptionsVisible;
|
|
284
|
+
this._maxOptionsVisible = value;
|
|
285
|
+
// Any assignment opts this instance out of automatic chip fitting.
|
|
286
|
+
this.maxOptionsVisibleExplicit = true;
|
|
287
|
+
this.requestUpdate('maxOptionsVisible', previous);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private get chipFittingEnabled(): boolean {
|
|
291
|
+
return !!this.multiple && !this.maxOptionsVisibleExplicit;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Minimum width, in px, of a squeezed chip. Needs `showtooltip`. */
|
|
295
|
+
@property({ type: Number, reflect: true, attribute: true }) chipMinWidth = 110;
|
|
269
296
|
|
|
270
|
-
|
|
297
|
+
/** Maximum width, in px, of a squeezed chip. Needs `showtooltip`. */
|
|
298
|
+
@property({ type: Number, reflect: true, attribute: true }) chipMaxWidth = 180;
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Shows a chip's full value in a tooltip when its text is clipped, and is
|
|
302
|
+
* what allows a chip to be truncated at all.
|
|
303
|
+
*/
|
|
304
|
+
@property({ type: Boolean, attribute: true, reflect: true }) showTooltip = false;
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
@state() private hiddenChipCount = 0;
|
|
308
|
+
|
|
309
|
+
/** Last measured width of the overflow button, in px. */
|
|
310
|
+
private lastOverflowWidth = 0;
|
|
311
|
+
|
|
312
|
+
private chipFitFrame = 0;
|
|
313
|
+
private lastChipLayout = { hidden: -1, visible: -1 };
|
|
314
|
+
@state() private clippedChips: boolean[] = [];
|
|
315
|
+
private clipFrame = 0;
|
|
271
316
|
|
|
272
317
|
@property({ type: Boolean, reflect: true, attribute: true }) enableTabClose = false;
|
|
273
318
|
|
|
@@ -326,19 +371,31 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
|
|
|
326
371
|
}
|
|
327
372
|
// Clean up body append elements
|
|
328
373
|
this.portalManager.cleanupPortalAppend();
|
|
374
|
+
this.destroyChipFit();
|
|
329
375
|
}
|
|
330
376
|
|
|
331
377
|
protected updated(changedProperties: PropertyValues): void {
|
|
332
378
|
if(changedProperties.has('value')) {
|
|
333
379
|
this.selectionChanged();
|
|
334
380
|
}
|
|
335
|
-
if (
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
381
|
+
if (
|
|
382
|
+
changedProperties.has('autoResize') ||
|
|
383
|
+
changedProperties.has('multiple') ||
|
|
384
|
+
changedProperties.has('maxOptionsVisible')
|
|
385
|
+
) {
|
|
386
|
+
this.setupResizeObserver();
|
|
387
|
+
}
|
|
388
|
+
if (
|
|
389
|
+
changedProperties.has('chipMinWidth') ||
|
|
390
|
+
changedProperties.has('chipMaxWidth')
|
|
391
|
+
) {
|
|
392
|
+
this.destroyChipFit();
|
|
341
393
|
}
|
|
394
|
+
if (this.chipFittingEnabled) {
|
|
395
|
+
this.calculateTotalWidthOfTags();
|
|
396
|
+
}
|
|
397
|
+
if (this.multiple && this.showTooltip) {
|
|
398
|
+
this.scheduleClipCheck();
|
|
342
399
|
}
|
|
343
400
|
}
|
|
344
401
|
|
|
@@ -632,6 +689,8 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
|
|
|
632
689
|
this.open = !this.open;
|
|
633
690
|
}
|
|
634
691
|
|
|
692
|
+
private observedTagsContainer?: Element;
|
|
693
|
+
|
|
635
694
|
private resizeController = new ResizeController(this, {
|
|
636
695
|
callback: (entries: ResizeObserverEntry[]) => {
|
|
637
696
|
for (const entry of entries) {
|
|
@@ -648,7 +707,12 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
|
|
|
648
707
|
el =>
|
|
649
708
|
el instanceof Element && el.tagName.toLowerCase() === 'nile-icon-button'
|
|
650
709
|
);
|
|
651
|
-
|
|
710
|
+
const isOverflowButton = path.some(
|
|
711
|
+
el =>
|
|
712
|
+
el instanceof HTMLButtonElement &&
|
|
713
|
+
el.classList.contains('select__tags-count')
|
|
714
|
+
);
|
|
715
|
+
return this.disabled || isIconButton || isOverflowButton;
|
|
652
716
|
}
|
|
653
717
|
|
|
654
718
|
private handleComboboxKeyDown(event: KeyboardEvent): void {
|
|
@@ -1310,7 +1374,65 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
|
|
|
1310
1374
|
`;
|
|
1311
1375
|
}
|
|
1312
1376
|
|
|
1377
|
+
private renderFittedTags(): TemplateResult {
|
|
1378
|
+
return html`
|
|
1379
|
+
<div part="tags" class="select__tags select__tags--fitted">
|
|
1380
|
+
${this.selectedOptions.map((option, index) => {
|
|
1381
|
+
const label = this.getChipLabel(option);
|
|
1382
|
+
return html`
|
|
1383
|
+
<nile-lite-tooltip
|
|
1384
|
+
class="select__tag-tip"
|
|
1385
|
+
.content=${label}
|
|
1386
|
+
?disabled=${this.chipTooltipDisabled(index)}
|
|
1387
|
+
>
|
|
1388
|
+
<nile-tag
|
|
1389
|
+
part="tag"
|
|
1390
|
+
exportparts="
|
|
1391
|
+
base:tag__base,
|
|
1392
|
+
content:tag__content,
|
|
1393
|
+
remove-button:tag__remove-button,
|
|
1394
|
+
remove-button__base:tag__remove-button__base,
|
|
1395
|
+
prefix:tag__prefix,
|
|
1396
|
+
prefix_content_wrapper:tag__prefix_content_wrapper
|
|
1397
|
+
"
|
|
1398
|
+
?pill=${this.pill}
|
|
1399
|
+
size=${this.size}
|
|
1400
|
+
removable
|
|
1401
|
+
tabindex="0"
|
|
1402
|
+
aria-label=${`${label}. Press Backspace or Delete to remove.`}
|
|
1403
|
+
@nile-remove=${(event: NileRemoveEvent) =>
|
|
1404
|
+
this.handleTagRemove(event, option)}
|
|
1405
|
+
@keydown=${(event: KeyboardEvent) =>
|
|
1406
|
+
this.handleChipKeyDown(event, option)}
|
|
1407
|
+
>
|
|
1408
|
+
${label}
|
|
1409
|
+
</nile-tag>
|
|
1410
|
+
</nile-lite-tooltip>
|
|
1411
|
+
`;
|
|
1412
|
+
})}
|
|
1413
|
+
${this.hiddenChipCount > 0
|
|
1414
|
+
? html`<button
|
|
1415
|
+
type="button"
|
|
1416
|
+
class="select__tags-count"
|
|
1417
|
+
?disabled=${this.disabled}
|
|
1418
|
+
aria-controls="listbox"
|
|
1419
|
+
aria-expanded=${this.open ? 'true' : 'false'}
|
|
1420
|
+
aria-haspopup="listbox"
|
|
1421
|
+
aria-label=${`${this.hiddenChipCount} more selected`}
|
|
1422
|
+
@click=${this.handleOverflowActivate}
|
|
1423
|
+
@keydown=${this.handleOverflowKeyDown}
|
|
1424
|
+
>+${this.hiddenChipCount}</button
|
|
1425
|
+
>`
|
|
1426
|
+
: ''}
|
|
1427
|
+
</div>
|
|
1428
|
+
`;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1313
1431
|
private renderTags(): TemplateResult {
|
|
1432
|
+
if (this.chipFittingEnabled) {
|
|
1433
|
+
return this.renderFittedTags();
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1314
1436
|
return this.multiple
|
|
1315
1437
|
? html`
|
|
1316
1438
|
<div part="tags" class="select__tags">
|
|
@@ -1319,14 +1441,13 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
|
|
|
1319
1441
|
index < this.maxOptionsVisible ||
|
|
1320
1442
|
this.maxOptionsVisible <= 0
|
|
1321
1443
|
) {
|
|
1322
|
-
const classes = {
|
|
1323
|
-
select__invisible:
|
|
1324
|
-
index + 1 > this.oldMaxOptionsVisible &&
|
|
1325
|
-
this.maxOptionsVisible === Infinity,
|
|
1326
|
-
};
|
|
1327
1444
|
return html`
|
|
1445
|
+
<nile-lite-tooltip
|
|
1446
|
+
class="select__tag-tip"
|
|
1447
|
+
.content=${option.getTextLabel()}
|
|
1448
|
+
?disabled=${this.chipTooltipDisabled(index)}
|
|
1449
|
+
>
|
|
1328
1450
|
<nile-tag
|
|
1329
|
-
class=${classMap(classes)}
|
|
1330
1451
|
part="tag"
|
|
1331
1452
|
exportparts="
|
|
1332
1453
|
base:tag__base,
|
|
@@ -1334,6 +1455,7 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
|
|
|
1334
1455
|
remove-button:tag__remove-button,
|
|
1335
1456
|
remove-button__base:tag__remove-button__base
|
|
1336
1457
|
"
|
|
1458
|
+
style="max-width:${this.chipMaxWidth}px"
|
|
1337
1459
|
?pill=${this.pill}
|
|
1338
1460
|
size=${this.size}
|
|
1339
1461
|
removable
|
|
@@ -1342,6 +1464,7 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
|
|
|
1342
1464
|
>
|
|
1343
1465
|
${option.getTextLabel()}
|
|
1344
1466
|
</nile-tag>
|
|
1467
|
+
</nile-lite-tooltip>
|
|
1345
1468
|
`;
|
|
1346
1469
|
} else if (index === this.maxOptionsVisible) {
|
|
1347
1470
|
return html`
|
|
@@ -1496,14 +1619,16 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
|
|
|
1496
1619
|
@click="${this.handleFooterClick}"
|
|
1497
1620
|
>
|
|
1498
1621
|
<span
|
|
1622
|
+
class="select__footer-action"
|
|
1499
1623
|
@click="${this.toggleShowSelected}"
|
|
1500
1624
|
style="cursor: pointer;"
|
|
1625
|
+
title="Show selected"
|
|
1501
1626
|
>
|
|
1502
1627
|
<nile-checkbox
|
|
1503
1628
|
?disabled="${this.selectedOptions.length === 0}"
|
|
1504
1629
|
?checked="${this.showSelected}"
|
|
1505
1630
|
>
|
|
1506
|
-
|
|
1631
|
+
<span class="select__footer-label">Selected</span></nile-checkbox
|
|
1507
1632
|
>
|
|
1508
1633
|
</span>
|
|
1509
1634
|
${this.selectedOptions.length !== 0
|
|
@@ -1511,8 +1636,16 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
|
|
|
1511
1636
|
<span
|
|
1512
1637
|
class="select__clear"
|
|
1513
1638
|
@click="${this.unSelectAll}"
|
|
1639
|
+
title="Clear"
|
|
1514
1640
|
>
|
|
1515
|
-
|
|
1641
|
+
<nile-glyph
|
|
1642
|
+
class="select__footer-glyph"
|
|
1643
|
+
name="ng-eraser"
|
|
1644
|
+
size="16"
|
|
1645
|
+
method="var(--nile-svg-method-stroke, var(--ng-svg-method-stroke))"
|
|
1646
|
+
color="var(--nile-colors-primary-600, var(--ng-colors-text-brand-secondary-700))"
|
|
1647
|
+
></nile-glyph
|
|
1648
|
+
><span class="select__footer-label">Clear</span>
|
|
1516
1649
|
</span>
|
|
1517
1650
|
`
|
|
1518
1651
|
: ``}
|
|
@@ -1573,59 +1706,331 @@ export class NileVirtualSelect extends NileElement implements NileFormControl {
|
|
|
1573
1706
|
});
|
|
1574
1707
|
}
|
|
1575
1708
|
|
|
1576
|
-
private
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1709
|
+
private setupResizeObserver(): void {
|
|
1710
|
+
const wanted =
|
|
1711
|
+
this.autoResize && this.chipFittingEnabled
|
|
1712
|
+
? (this.shadowRoot?.querySelector('.select__tags') as HTMLElement | null) ??
|
|
1713
|
+
undefined
|
|
1714
|
+
: undefined;
|
|
1715
|
+
|
|
1716
|
+
if (wanted === this.observedTagsContainer) {
|
|
1717
|
+
if (!this.chipFittingEnabled) this.destroyChipFit();
|
|
1718
|
+
return;
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
if (this.observedTagsContainer) {
|
|
1722
|
+
this.resizeController.unobserve(this.observedTagsContainer);
|
|
1723
|
+
this.observedTagsContainer = undefined;
|
|
1582
1724
|
}
|
|
1725
|
+
|
|
1726
|
+
if (wanted) {
|
|
1727
|
+
this.resizeController.observe(wanted);
|
|
1728
|
+
this.observedTagsContainer = wanted;
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
if (!this.chipFittingEnabled) {
|
|
1733
|
+
this.destroyChipFit();
|
|
1583
1734
|
}
|
|
1584
|
-
return null;
|
|
1585
1735
|
}
|
|
1586
1736
|
|
|
1587
1737
|
private calculateTotalWidthOfTags(): void {
|
|
1588
|
-
if (this.
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1738
|
+
if (!this.chipFittingEnabled) return;
|
|
1739
|
+
|
|
1740
|
+
if (this.chipFitFrame) cancelAnimationFrame(this.chipFitFrame);
|
|
1741
|
+
this.chipFitFrame = requestAnimationFrame(() => {
|
|
1742
|
+
this.chipFitFrame = 0;
|
|
1743
|
+
this.layoutChips();
|
|
1744
|
+
});
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
private allocateChipWidths(
|
|
1748
|
+
natural: number[],
|
|
1749
|
+
available: number
|
|
1750
|
+
): number[] | null {
|
|
1751
|
+
const min = this.chipMinWidth;
|
|
1752
|
+
const max = this.chipMaxWidth;
|
|
1753
|
+
const total = natural.reduce((sum, w) => sum + w, 0);
|
|
1754
|
+
|
|
1755
|
+
if (total <= available) {
|
|
1756
|
+
return natural.slice();
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
// Nothing reveals a truncated value without a tooltip, so chips keep their
|
|
1760
|
+
// natural width and the surplus moves into the +N button instead.
|
|
1761
|
+
if (!this.showTooltip) return null;
|
|
1762
|
+
|
|
1763
|
+
const capped = natural.map(w => Math.min(w, max));
|
|
1764
|
+
const cappedTotal = capped.reduce((sum, w) => sum + w, 0);
|
|
1765
|
+
if (cappedTotal <= available) {
|
|
1766
|
+
return capped;
|
|
1600
1767
|
}
|
|
1601
|
-
|
|
1768
|
+
|
|
1769
|
+
const widths = new Array<number>(natural.length).fill(0);
|
|
1770
|
+
let pool = capped.map((_, i) => i);
|
|
1771
|
+
let remaining = available;
|
|
1772
|
+
|
|
1773
|
+
for (;;) {
|
|
1774
|
+
if (pool.length === 0) break;
|
|
1775
|
+
const share = remaining / pool.length;
|
|
1776
|
+
const settled = pool.filter(i => capped[i] <= share);
|
|
1777
|
+
if (settled.length === 0) break;
|
|
1778
|
+
for (const i of settled) {
|
|
1779
|
+
widths[i] = capped[i];
|
|
1780
|
+
remaining -= capped[i];
|
|
1602
1781
|
}
|
|
1603
|
-
|
|
1604
|
-
|
|
1782
|
+
pool = pool.filter(i => capped[i] > share);
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
if (pool.length > 0) {
|
|
1786
|
+
const share = remaining / pool.length;
|
|
1787
|
+
|
|
1788
|
+
if (share < min) {
|
|
1789
|
+
return null;
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
const width = Math.floor(share);
|
|
1793
|
+
for (const i of pool) {
|
|
1794
|
+
widths[i] = width;
|
|
1605
1795
|
}
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
return widths;
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
private computeChipFit(
|
|
1802
|
+
natural: number[],
|
|
1803
|
+
available: number,
|
|
1804
|
+
overflowWidth: number,
|
|
1805
|
+
gap: number
|
|
1806
|
+
): { visibleCount: number; widths: number[]; hiddenCount: number } {
|
|
1807
|
+
const total = natural.length;
|
|
1808
|
+
|
|
1809
|
+
if (total === 0) {
|
|
1810
|
+
return { visibleCount: 0, widths: [], hiddenCount: 0 };
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
for (let count = total; count >= 1; count--) {
|
|
1814
|
+
const hidden = total - count;
|
|
1815
|
+
const reserved = hidden > 0 ? overflowWidth + gap : 0;
|
|
1816
|
+
const row = available - reserved - gap * (count - 1);
|
|
1817
|
+
|
|
1818
|
+
if (row <= 0) continue;
|
|
1606
1819
|
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1820
|
+
const widths = this.allocateChipWidths(natural.slice(0, count), row);
|
|
1821
|
+
if (widths) {
|
|
1822
|
+
return { visibleCount: count, widths, hiddenCount: hidden };
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
const room = available - overflowWidth - gap;
|
|
1827
|
+
const fallback = Math.max(
|
|
1828
|
+
0,
|
|
1829
|
+
// A single chip too wide for the row has to be clipped either way; only
|
|
1830
|
+
// the tooltip variant squeezes it to chipMinWidth.
|
|
1831
|
+
Math.min(natural[0], this.showTooltip ? Math.max(this.chipMinWidth, room) : room)
|
|
1610
1832
|
);
|
|
1611
|
-
|
|
1612
|
-
|
|
1833
|
+
return { visibleCount: 1, widths: [fallback], hiddenCount: total - 1 };
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
private layoutChips(): void {
|
|
1837
|
+
const root = this.shadowRoot;
|
|
1838
|
+
if (!root) return;
|
|
1839
|
+
|
|
1840
|
+
const container = root.querySelector('.select__tags') as HTMLElement | null;
|
|
1841
|
+
if (!container) return;
|
|
1842
|
+
|
|
1843
|
+
const chips = Array.from(
|
|
1844
|
+
container.querySelectorAll('nile-tag')
|
|
1845
|
+
) as HTMLElement[];
|
|
1846
|
+
if (chips.length === 0) {
|
|
1847
|
+
this.hiddenChipCount = 0;
|
|
1613
1848
|
return;
|
|
1614
1849
|
}
|
|
1615
1850
|
|
|
1616
|
-
|
|
1617
|
-
|
|
1851
|
+
const overflowEl = container.querySelector(
|
|
1852
|
+
'.select__tags-count'
|
|
1853
|
+
) as HTMLElement | null;
|
|
1618
1854
|
|
|
1619
|
-
|
|
1620
|
-
|
|
1855
|
+
for (const chip of chips) {
|
|
1856
|
+
chip.style.display = '';
|
|
1857
|
+
chip.style.width = 'auto';
|
|
1858
|
+
chip.style.maxWidth = 'none';
|
|
1859
|
+
chip.style.minWidth = '0';
|
|
1860
|
+
chip.style.flex = '0 0 auto';
|
|
1861
|
+
}
|
|
1862
|
+
const natural = chips.map(chip =>
|
|
1863
|
+
Math.ceil(chip.getBoundingClientRect().width)
|
|
1864
|
+
);
|
|
1865
|
+
// Not rendered yet on the pass that first finds an overflow; reuse the last width.
|
|
1866
|
+
const overflowWidth = overflowEl
|
|
1867
|
+
? Math.ceil(overflowEl.getBoundingClientRect().width)
|
|
1868
|
+
: this.lastOverflowWidth;
|
|
1869
|
+
if (overflowEl) this.lastOverflowWidth = overflowWidth;
|
|
1870
|
+
|
|
1871
|
+
const containerStyles = getComputedStyle(container);
|
|
1872
|
+
const gap =
|
|
1873
|
+
parseFloat(containerStyles.columnGap || containerStyles.gap || '0') || 0;
|
|
1874
|
+
|
|
1875
|
+
const fit = this.computeChipFit(
|
|
1876
|
+
natural,
|
|
1877
|
+
container.clientWidth,
|
|
1878
|
+
overflowWidth,
|
|
1879
|
+
gap
|
|
1880
|
+
);
|
|
1621
1881
|
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1882
|
+
chips.forEach((chip, index) => {
|
|
1883
|
+
if (index < fit.visibleCount) {
|
|
1884
|
+
// Never squeeze a chip below chipMinWidth: narrower than that it is
|
|
1885
|
+
// also narrower than the tag's own box - the label is already at zero
|
|
1886
|
+
// while the remove button is fixed - so the box overflows the chip and
|
|
1887
|
+
// paints the X over the next chip or the +N button. A chip whose
|
|
1888
|
+
// natural width is already smaller is left alone, not padded out.
|
|
1889
|
+
const width = Math.min(
|
|
1890
|
+
natural[index],
|
|
1891
|
+
Math.max(fit.widths[index], this.chipMinWidth)
|
|
1892
|
+
);
|
|
1893
|
+
chip.style.display = '';
|
|
1894
|
+
chip.style.width = `${width}px`;
|
|
1895
|
+
chip.style.maxWidth = `${width}px`;
|
|
1896
|
+
chip.style.flex = '0 0 auto';
|
|
1897
|
+
} else {
|
|
1898
|
+
chip.style.display = 'none';
|
|
1625
1899
|
}
|
|
1900
|
+
});
|
|
1901
|
+
|
|
1902
|
+
if (
|
|
1903
|
+
this.lastChipLayout.hidden !== fit.hiddenCount ||
|
|
1904
|
+
this.lastChipLayout.visible !== fit.visibleCount
|
|
1905
|
+
) {
|
|
1906
|
+
this.lastChipLayout = {
|
|
1907
|
+
hidden: fit.hiddenCount,
|
|
1908
|
+
visible: fit.visibleCount
|
|
1909
|
+
};
|
|
1910
|
+
this.hiddenChipCount = fit.hiddenCount;
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
this.refreshClippedChips();
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
private isChipClipped(el: HTMLElement | null | undefined): boolean {
|
|
1917
|
+
if (!el) return false;
|
|
1918
|
+
|
|
1919
|
+
const root = el.shadowRoot;
|
|
1920
|
+
if (root) {
|
|
1921
|
+
const content = root.querySelector(
|
|
1922
|
+
'[part="content"]'
|
|
1923
|
+
) as HTMLElement | null;
|
|
1924
|
+
if (content && content.scrollWidth > content.clientWidth + 1) return true;
|
|
1925
|
+
|
|
1926
|
+
const inner = root.querySelector(
|
|
1927
|
+
'[part="prefix_content_wrapper"]'
|
|
1928
|
+
) as HTMLElement | null;
|
|
1929
|
+
if (inner && inner.scrollWidth > inner.clientWidth + 1) return true;
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
return el.scrollWidth > el.clientWidth + 1;
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
private refreshClippedChips(): void {
|
|
1936
|
+
if (!this.multiple) return;
|
|
1937
|
+
|
|
1938
|
+
const root = this.shadowRoot;
|
|
1939
|
+
if (!root) return;
|
|
1940
|
+
|
|
1941
|
+
const chips = Array.from(
|
|
1942
|
+
root.querySelectorAll('.select__tags nile-tag')
|
|
1943
|
+
) as HTMLElement[];
|
|
1944
|
+
|
|
1945
|
+
const next = chips.map(chip => this.isChipClipped(chip));
|
|
1946
|
+
|
|
1947
|
+
const same =
|
|
1948
|
+
next.length === this.clippedChips.length &&
|
|
1949
|
+
next.every((v, i) => v === this.clippedChips[i]);
|
|
1950
|
+
|
|
1951
|
+
if (!same) {
|
|
1952
|
+
this.clippedChips = next;
|
|
1626
1953
|
}
|
|
1627
|
-
|
|
1628
|
-
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
private scheduleClipCheck(): void {
|
|
1957
|
+
if (this.clipFrame) cancelAnimationFrame(this.clipFrame);
|
|
1958
|
+
this.clipFrame = requestAnimationFrame(() => {
|
|
1959
|
+
this.clipFrame = 0;
|
|
1960
|
+
this.refreshClippedChips();
|
|
1961
|
+
});
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
/** A chip gets a tooltip only while its text is measured as clipped. */
|
|
1965
|
+
private chipTooltipDisabled(index: number): boolean {
|
|
1966
|
+
return !this.showTooltip || !(this.clippedChips[index] ?? false);
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
private destroyChipFit(): void {
|
|
1970
|
+
if (this.chipFitFrame) cancelAnimationFrame(this.chipFitFrame);
|
|
1971
|
+
this.chipFitFrame = 0;
|
|
1972
|
+
if (this.clipFrame) cancelAnimationFrame(this.clipFrame);
|
|
1973
|
+
this.clipFrame = 0;
|
|
1974
|
+
this.lastChipLayout = { hidden: -1, visible: -1 };
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
private getChipLabel(option: VirtualOption): string {
|
|
1978
|
+
return (option.getTextLabel?.() ?? '').trim();
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
private handleChipKeyDown(event: KeyboardEvent, option: VirtualOption): void {
|
|
1982
|
+
if (event.key !== KeyCode.BACKSPACE && event.key !== KeyCode.DELETE) return;
|
|
1983
|
+
if (this.disabled) return;
|
|
1984
|
+
|
|
1985
|
+
event.preventDefault();
|
|
1986
|
+
event.stopPropagation();
|
|
1987
|
+
|
|
1988
|
+
const removedIndex = this.selectedOptions.indexOf(option);
|
|
1989
|
+
|
|
1990
|
+
this.handleTagRemove(
|
|
1991
|
+
new CustomEvent('nile-remove') as unknown as NileRemoveEvent,
|
|
1992
|
+
option
|
|
1993
|
+
);
|
|
1994
|
+
|
|
1995
|
+
this.restoreFocusAfterChipRemoval(removedIndex);
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
/** Moves focus to the next chip, the last chip, or the field after a removal. */
|
|
1999
|
+
private async restoreFocusAfterChipRemoval(
|
|
2000
|
+
removedIndex: number
|
|
2001
|
+
): Promise<void> {
|
|
2002
|
+
if (removedIndex < 0) return;
|
|
2003
|
+
|
|
2004
|
+
await this.updateComplete;
|
|
2005
|
+
|
|
2006
|
+
// Wait for the fit pass so hidden chips are not focused.
|
|
2007
|
+
await new Promise<void>(resolve => requestAnimationFrame(() => resolve()));
|
|
2008
|
+
|
|
2009
|
+
const visibleChips = Array.from(
|
|
2010
|
+
this.shadowRoot?.querySelectorAll<HTMLElement>('.select__tags nile-tag') ??
|
|
2011
|
+
[]
|
|
2012
|
+
).filter(chip => chip.style.display !== 'none');
|
|
2013
|
+
|
|
2014
|
+
const target =
|
|
2015
|
+
visibleChips[removedIndex] ?? visibleChips[visibleChips.length - 1];
|
|
2016
|
+
|
|
2017
|
+
if (target) {
|
|
2018
|
+
target.focus({ preventScroll: true });
|
|
2019
|
+
} else {
|
|
2020
|
+
this.displayInput?.focus({ preventScroll: true });
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
private handleOverflowActivate(event: Event): void {
|
|
2025
|
+
event.preventDefault();
|
|
2026
|
+
event.stopPropagation();
|
|
2027
|
+
if (this.disabled) return;
|
|
2028
|
+
this.open = !this.open;
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
private handleOverflowKeyDown(event: KeyboardEvent): void {
|
|
2032
|
+
if (event.key !== KeyCode.ENTER && event.key !== KeyCode.SPACE) return;
|
|
2033
|
+
this.handleOverflowActivate(event);
|
|
1629
2034
|
}
|
|
1630
2035
|
|
|
1631
2036
|
private async resetScrollPosition(): Promise<void> {
|