@nova-design-system/nova-react 3.22.0 → 3.23.0
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/dist/cjs/_commonjsHelpers-B85MJLTf-CFO10eej.js +7 -0
- package/dist/cjs/{collapse.animation-6e0b08df-AHWzNGm_.js → collapse.animation-DZDm0vSK-C2TOIhIK.js} +3 -3
- package/dist/cjs/{fade.animation-9b939939-DV--bM4S.js → fade.animation-DcRL9lcm-DAZeHoKN.js} +75 -75
- package/dist/cjs/generated/components.server.js +245 -50
- package/dist/cjs/{grow.animation-24ad5cf8-LUp_ITEx.js → grow.animation-CvHGHBL4-DDIEYBK-.js} +5 -5
- package/dist/cjs/i18n.utils-DOZbXX2L-BizoXo6c.js +2498 -0
- package/dist/cjs/{index-WPRkQD3O.js → index-CtjeeUI-.js} +12063 -7049
- package/dist/cjs/index.js +12 -1
- package/dist/cjs/inputmask-CSo292ul-DlvupPk6.js +3758 -0
- package/dist/cjs/{nv-accordion-item.entry-BuTvA6w9.js → nv-accordion-item.entry-B_l0-ux0.js} +11 -10
- package/dist/cjs/{nv-accordion.entry-Dtsfw6He.js → nv-accordion.entry-BX8_YuZF.js} +8 -5
- package/dist/cjs/{nv-alert.entry-TIdfdU7Y.js → nv-alert.entry-DCWYR0OK.js} +22 -22
- package/dist/cjs/{nv-avatar.entry-CaxrhPuw.js → nv-avatar.entry-C_xZD3Lp.js} +11 -11
- package/dist/cjs/{nv-badge_2.entry-CfYvTZxX.js → nv-badge_2.entry-JjqANStV.js} +24 -24
- package/dist/cjs/{nv-breadcrumb.entry-BCZ4MmfD.js → nv-breadcrumb.entry-DQZDn6cm.js} +5 -5
- package/dist/cjs/{nv-breadcrumbs.entry-DwzCE7F6.js → nv-breadcrumbs.entry-Bz0GjhY_.js} +3 -3
- package/dist/cjs/{nv-button.entry-Cr_86bcZ.js → nv-button.entry-Br1DH9Vj.js} +9 -9
- package/dist/cjs/{nv-buttongroup.entry-CWjRoHY1.js → nv-buttongroup.entry-BZaTKN_n.js} +3 -3
- package/dist/cjs/{nv-calendar.entry-CXfwNt6G.js → nv-calendar.entry-D9ESuu7C.js} +105 -79
- package/dist/cjs/{nv-col.entry-CJLDS3LY.js → nv-col.entry-CfgPMMxS.js} +5 -5
- package/dist/cjs/{nv-datagrid.entry-Cns8XSud.js → nv-datagrid.entry-DcB5q2oC.js} +14 -14
- package/dist/cjs/{nv-datagridcolumn.entry-CFFAipHF.js → nv-datagridcolumn.entry-BhKOzXA6.js} +2 -1
- package/dist/cjs/{nv-dialog.entry-CDSK9pUH.js → nv-dialog.entry-O47Eol_7.js} +23 -23
- package/dist/cjs/{nv-dialogfooter_2.entry-To_dGUn4.js → nv-dialogfooter_2.entry-Dn16bI8a.js} +10 -11
- package/dist/cjs/nv-fieldcheckbox.entry-Bx6ArV_b.js +177 -0
- package/dist/cjs/{nv-fielddate.entry-C3pXtMHL.js → nv-fielddate.entry-B4P0U8QG.js} +86 -40
- package/dist/cjs/{nv-fielddaterange.entry-CjVVPEK_.js → nv-fielddaterange.entry-BORwYJ-k.js} +150 -101
- package/dist/cjs/nv-fielddropdown.entry-DzBAIynY.js +687 -0
- package/dist/cjs/{nv-fielddropdownitem.entry-Dah-PaE8.js → nv-fielddropdownitem.entry-C_17isWd.js} +6 -5
- package/dist/cjs/{nv-fieldmultiselect.entry-BMLjhqoJ.js → nv-fieldmultiselect.entry-DiqRreWh.js} +347 -232
- package/dist/cjs/nv-fieldnumber.entry-C9O4UPp3.js +187 -0
- package/dist/cjs/nv-fieldpassword.entry-BfVJNT0A.js +165 -0
- package/dist/cjs/{nv-fieldradio.entry-X_2VT1Dj.js → nv-fieldradio.entry-CG22oETM.js} +10 -10
- package/dist/cjs/{nv-fieldselect.entry-pSp-2rNn.js → nv-fieldselect.entry-BPQEtrv2.js} +52 -13
- package/dist/cjs/{nv-fieldslider.entry-pZf7zzLU.js → nv-fieldslider.entry-CozmnUfN.js} +16 -31
- package/dist/cjs/nv-fieldtext.entry-BD-z01ru.js +163 -0
- package/dist/cjs/{nv-fieldtextarea.entry-t3Ixxi23.js → nv-fieldtextarea.entry-7UrKWDHg.js} +51 -12
- package/dist/cjs/{nv-fieldtime.entry-DY7D5_6K.js → nv-fieldtime.entry-DakOlLiO.js} +109 -57
- package/dist/cjs/{nv-icon.entry-6oYPSf4c.js → nv-icon.entry-Db00kB2u.js} +11 -11
- package/dist/cjs/{nv-iconbutton_2.entry-ChULGovr.js → nv-iconbutton_2.entry-CaKCa8NT.js} +7 -8
- package/dist/cjs/{nv-menu.entry-sd0tatWq.js → nv-menu.entry-CK2HdmBt.js} +16 -29
- package/dist/cjs/{nv-menuitem.entry-CCOqR7UF.js → nv-menuitem.entry-mKMqCAdz.js} +6 -5
- package/dist/cjs/nv-notification-bullet.entry-DtbjtFxs.js +77 -0
- package/dist/cjs/{nv-notification.entry-Cc3zE3yY.js → nv-notification.entry-CLb0gNu3.js} +39 -39
- package/dist/cjs/{nv-notificationcontainer.entry-DV4D6oOH.js → nv-notificationcontainer.entry-Cijivlm6.js} +3 -3
- package/dist/cjs/{nv-popover.entry-DQSwI2jT.js → nv-popover.entry-mLdLSp6n.js} +49 -45
- package/dist/cjs/{nv-row.entry-UUuMSAY5.js → nv-row.entry-C2C94fcv.js} +3 -3
- package/dist/cjs/nv-sidebar.entry-inDVNJ4s.js +177 -0
- package/dist/cjs/nv-sidebarcontent.entry-DxoljE15.js +22 -0
- package/dist/cjs/nv-sidebardivider.entry-D_yern0R.js +22 -0
- package/dist/cjs/nv-sidebarfooter.entry-Rkkn9TB_.js +22 -0
- package/dist/cjs/nv-sidebargroup.entry-C1p9qqxr.js +23 -0
- package/dist/cjs/nv-sidebarheader.entry-CYpD_4pI.js +22 -0
- package/dist/cjs/nv-sidebarlogo.entry-BgK03M1v.js +32 -0
- package/dist/cjs/nv-sidebarnavitem.entry-DglvcCOD.js +297 -0
- package/dist/cjs/nv-sidebarnavsubitem.entry-Dt1jKmC-.js +35 -0
- package/dist/cjs/{nv-split.entry-CYP2bTTM.js → nv-split.entry-mzg2F66T.js} +6 -6
- package/dist/cjs/{nv-stack.entry-D35-75Vw.js → nv-stack.entry-nnvjTrBy.js} +5 -5
- package/dist/cjs/{nv-table.entry-DevWJBnL.js → nv-table.entry-DkbNgxtI.js} +4 -4
- package/dist/cjs/{nv-tableheader.entry-Hs7nUSOC.js → nv-tableheader.entry-CRVFTQA-.js} +6 -6
- package/dist/cjs/{nv-toggle.entry-Eje9d--6.js → nv-toggle.entry-oC9TVkr1.js} +9 -9
- package/dist/cjs/nv-togglebutton.entry-BTWCzbS9.js +67 -0
- package/dist/cjs/{nv-togglebuttongroup.entry-CWN_Ucry.js → nv-togglebuttongroup.entry-BYXX5ejg.js} +8 -5
- package/dist/cjs/{nv-tooltip.entry-BGkKDEFg.js → nv-tooltip.entry-OJGxfJEh.js} +5 -5
- package/dist/cjs/{style-value-types.es-f5d10b79-D0QCM8OB.js → style-value-types.es-xlgmw4x8-B1vLqX9m.js} +6 -3
- package/dist/cjs/{v4-a79185f4-2n0dOd_Y.js → v4-BdYh22OP-C1vaJ4yP.js} +1 -1
- package/dist/generated/components.js +80 -0
- package/dist/generated/components.server.js +216 -50
- package/dist/providers/NotificationProvider.js +3 -4
- package/dist/types/generated/components.d.ts +32 -0
- package/dist/types/generated/components.server.d.ts +32 -0
- package/package.json +2 -2
- package/dist/cjs/_commonjsHelpers-1789f0cf-BJu3ubxk.js +0 -10
- package/dist/cjs/inputmask-edcad3c1-B9Omti4z.js +0 -3749
- package/dist/cjs/nv-fieldcheckbox.entry-fdonR07Z.js +0 -138
- package/dist/cjs/nv-fielddropdown.entry-C9mXuNNj.js +0 -392
- package/dist/cjs/nv-fieldnumber.entry-DBdJviXu.js +0 -148
- package/dist/cjs/nv-fieldpassword.entry-Cim_usSM.js +0 -122
- package/dist/cjs/nv-fieldtext.entry-DlI_ExaV.js +0 -124
- package/dist/cjs/nv-togglebutton.entry-LGI7pIeK.js +0 -56
- /package/dist/cjs/{clsx-297c1ffe-BtxeOLZW.js → clsx-ChV9xqsO-BtxeOLZW.js} +0 -0
- /package/dist/cjs/{constants-69bafca2-DpB_ghPF.js → constants-BReL3Lsa-DpB_ghPF.js} +0 -0
- /package/dist/cjs/{timeline.animation-79215cd4-KteJaZPb.js → timeline.animation-CgHCo_Ho-KteJaZPb.js} +0 -0
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var index = require('./index-CtjeeUI-.js');
|
|
4
|
+
var i18n_utilsDOZbXX2L = require('./i18n.utils-DOZbXX2L-BizoXo6c.js');
|
|
5
|
+
var v4BdYh22OP = require('./v4-BdYh22OP-C1vaJ4yP.js');
|
|
6
|
+
require('@stencil/react-output-target/runtime');
|
|
7
|
+
require('react');
|
|
8
|
+
require('react-dom');
|
|
9
|
+
|
|
10
|
+
const nvFielddropdownCss = "nv-fieldslider .slider-container .track-container:has(.thumb:hover) .track-range{background:var(--components-slider-track-filled-hover)}nv-fieldslider .slider-container .track-container:has(.thumb:hover) .thumb{border-color:var(--components-slider-track-filled-hover)}nv-fieldslider .slider-container .track-container:has(.thumb:focus) .track-range{background:var(--components-slider-track-filled-focus)}nv-fieldslider .slider-container .track-container:has(.thumb:focus) .thumb{border-color:var(--components-slider-track-filled-focus)}nv-fieldslider[error] .slider-container .track-container .track .track-range{background:var(--components-slider-track-filled-error)}nv-fieldslider[error] .slider-container .track-container .track .thumb{border-color:var(--components-slider-track-filled-error)}nv-fieldslider[error] .slider-container .track-container .track .thumb:hover{border-color:var(--components-slider-track-filled-error);outline:calc(var(--focus-outline-stroke) * 1) solid var(--components-slider-track-filled-error);outline-offset:calc(var(--focus-outline-offset) * 1);background-color:var(--components-slider-handler-background-error)}nv-fieldslider[error] .slider-container .track-container .track .thumb:focus{border-color:var(--components-slider-track-filled-error);outline:calc(var(--focus-outline-stroke) * 1) solid var(--components-slider-track-filled-error);outline-offset:calc(var(--focus-outline-offset) * 1);background-color:var(--components-slider-track-filled-error)}nv-fieldslider[error] .slider-container .track-container:has(.thumb:hover) .track-range{background:var(--components-slider-track-filled-error)}nv-fieldslider[error] .slider-container .track-container:has(.thumb:hover) .thumb{border-color:var(--components-slider-track-filled-error)}nv-fielddropdown{--nv-field-border-default:var(--components-form-field-border-default);--nv-field-border-hover:var(--components-form-field-border-hover);--nv-field-border-focus:var(--components-form-field-border-focus);--nv-field-border-disabled:var(--components-form-field-border-default);--nv-field-border-readonly:var(--components-form-field-border-default);--nv-field-focus-box-shadow:var(--color-focus-brand);--nv-field-background:var(--components-form-field-background-default);display:flex;flex-direction:column;align-items:flex-start;gap:var(--form-gap-y);box-sizing:border-box;max-width:480px}nv-fielddropdown[fluid]:not([fluid=false]){max-width:unset}nv-fielddropdown[readonly]:not([readonly=false]){--nv-field-border-default:var(--components-form-field-border-readonly);--nv-field-border-hover:var(--nv-field-border-default);--nv-field-border-focus:var(--components-form-field-border-focus);--nv-field-border-disabled:var(--nv-field-border-default);--nv-field-border-readonly:var(--nv-field-border-default);--nv-field-background:var(--components-form-field-background-readonly)}nv-fielddropdown[error]:not([error=false]){--nv-field-border-default:var(--components-form-field-border-error);--nv-field-border-hover:var(--nv-field-border-default);--nv-field-border-focus:var(--nv-field-border-default);--nv-field-border-disabled:var(--nv-field-border-default);--nv-field-border-readonly:var(--nv-field-border-default);--nv-field-focus-box-shadow:var(--color-focus-destructive-in-field)}nv-fielddropdown[required]:not([required=false]) label::after,nv-fielddropdown[aria-required=true] label::after{content:\"*\";color:var(--components-form-text-required);font-weight:var(--font-weight-high-emphasis)}nv-fielddropdown[hidden]:not([hidden=false]) label{display:none}nv-fielddropdown label{display:flex;align-items:center;gap:var(--form-label-gap);align-self:stretch;color:var(--components-form-text-label-default);font-family:var(--font-family-default), var(--font-family-fallback), sans-serif;font-size:var(--form-label-font-size);font-style:normal;font-weight:var(--font-weight-medium-emphasis);line-height:var(--form-label-line-height)}nv-fielddropdown nv-popover{width:100%;display:block}nv-fielddropdown nv-popover [data-scope=popover]{min-width:100%;width:max-content;max-width:400px;padding:var(--list-dropdown-padding);border-radius:var(--list-dropdown-radius);background-color:var(--components-list-dropdown-background);border:1px solid var(--components-list-dropdown-border)}nv-fielddropdown nv-popover [slot=content]{gap:var(--list-dropdown-gap-y);display:flex;flex-direction:column}nv-fielddropdown nv-popover hr{color:var(--components-list-dropdown-separator)}nv-fielddropdown nv-popover div[slot=content]::-webkit-scrollbar{width:6px;height:6px}nv-fielddropdown nv-popover div[slot=content]::-webkit-scrollbar-track{background-color:var(--color-level-10-background);border-radius:9999px}nv-fielddropdown nv-popover div[slot=content]::-webkit-scrollbar-thumb{background-color:var(--color-gray-200);border-radius:9999px}nv-fielddropdown nv-popover div[slot=content]{max-height:calc(90vh - var(--list-dropdown-padding) * 2);overflow-y:auto;position:relative}nv-fielddropdown .input-wrapper{display:flex;flex-wrap:wrap;gap:var(--form-gap-x);align-items:stretch;align-self:stretch;width:100%}nv-fielddropdown .input-container{display:flex;flex-grow:1;justify-content:center;align-items:center;align-self:stretch;border-radius:var(--form-field-radius);border-width:1px;border-style:solid;border-color:var(--nv-field-border-default);opacity:var(--components-form-opacity-default, 1);background:var(--nv-field-background);transition:all 150ms ease-out}nv-fielddropdown .input-container:hover{border-color:var(--nv-field-border-hover)}nv-fielddropdown .input-container:focus-within,nv-fielddropdown .input-container:focus-within:hover,nv-fielddropdown .input-container:focus,nv-fielddropdown .input-container:focus:hover{border-color:var(--nv-field-border-focus);box-shadow:0px 0px 0px var(--focus-field-stroke) var(--nv-field-focus-box-shadow)}nv-fielddropdown .input-container:has(input:read-only){background-color:var(--components-form-field-background-readonly);border-color:var(--nv-field-border-readonly)}nv-fielddropdown .input-container:has(input:disabled){opacity:0.5;background-color:var(--components-form-field-background-disabled);border-color:var(--nv-field-border-disabled)}nv-fielddropdown .input-container{position:relative;width:100%;min-height:40px}nv-fielddropdown .input-container input[type=search]::-webkit-search-decoration,nv-fielddropdown .input-container input[type=search]::-webkit-search-cancel-button,nv-fielddropdown .input-container input[type=search]::-webkit-search-results-button,nv-fielddropdown .input-container input[type=search]::-webkit-search-results-decoration{-webkit-appearance:none}nv-fielddropdown .input-container input,nv-fielddropdown .input-container p.non-filterable-text{display:flex;align-items:center;flex:1 0 0;overflow:hidden;background-color:transparent;color:var(--components-form-field-content-text);padding:var(--form-field-padding-y) var(--form-field-padding-x);font-size:var(--form-field-font-size);font-style:normal;font-weight:var(--font-weight-medium-emphasis);line-height:var(--form-field-line-height);width:100%}nv-fielddropdown .input-container input:focus,nv-fielddropdown .input-container p.non-filterable-text:focus{outline:none}nv-fielddropdown .input-container input::placeholder,nv-fielddropdown .input-container p.non-filterable-text::placeholder{overflow:hidden;color:var(--components-form-field-content-placeholder);text-overflow:ellipsis;font-family:var(--font-family-default), var(--font-family-fallback), sans-serif;font-size:var(--form-field-font-size);font-style:normal;font-weight:var(--font-weight-low-emphasis);line-height:var(--form-field-line-height)}nv-fielddropdown .input-container input,nv-fielddropdown .input-container p.non-filterable-text{}nv-fielddropdown .input-container input[type=password]::-ms-clear,nv-fielddropdown .input-container input[type=password]::-ms-reveal,nv-fielddropdown .input-container p.non-filterable-text[type=password]::-ms-clear,nv-fielddropdown .input-container p.non-filterable-text[type=password]::-ms-reveal{display:none;width:0;height:0}nv-fielddropdown .input-container p.non-filterable-text{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;border-radius:var(--form-field-radius);background-color:var(--nv-field-background);color:var(--components-form-field-content-text);font-size:var(--form-field-font-size);font-weight:var(--font-weight-medium-emphasis);line-height:var(--form-field-line-height);box-sizing:border-box;cursor:pointer;height:100%;min-height:40px}nv-fielddropdown .input-container p.non-filterable-text span{display:inline-block;width:100%;overflow:hidden;text-overflow:ellipsis}nv-fielddropdown .input-container>nv-iconbutton{border:0px;border-radius:0px}nv-fielddropdown .input-container>nv-iconbutton:focus-visible{border-radius:var(--button-md-border-radius);outline-offset:-3px}nv-fielddropdown .input-container>nv-iconbutton:last-of-type{border-top-right-radius:var(--form-field-radius);border-bottom-right-radius:var(--form-field-radius)}nv-fielddropdown .input-container nv-icon.validation{color:var(--nv-field-border-default)}nv-fielddropdown .description{display:flex;align-items:center;align-self:stretch;gap:var(--spacing-1);color:var(--components-form-text-description-default);font-family:var(--font-family-default), var(--font-family-fallback), sans-serif;font-size:var(--form-description-font-size);font-style:normal;line-height:var(--form-description-line-height)}nv-fielddropdown .error-description{display:flex;align-items:center;align-self:stretch;gap:var(--spacing-1);color:var(--components-form-text-description-default);font-family:var(--font-family-default), var(--font-family-fallback), sans-serif;font-size:var(--form-description-font-size);font-style:normal;line-height:var(--form-description-line-height);color:var(--components-form-text-description-error)}";
|
|
11
|
+
|
|
12
|
+
const NvFielddropdown = class {
|
|
13
|
+
constructor(hostRef) {
|
|
14
|
+
index.registerInstance(this, hostRef);
|
|
15
|
+
this.valueChanged = index.createEvent(this, "valueChanged", 3);
|
|
16
|
+
this.filterTextChanged = index.createEvent(this, "filterTextChanged", 3);
|
|
17
|
+
this.openChanged = index.createEvent(this, "openChanged", 3);
|
|
18
|
+
this.dropdownItemSelected = index.createEvent(this, "dropdownItemSelected", 3);
|
|
19
|
+
/** Pre-computed search index for efficient filtering */
|
|
20
|
+
this.indexedItems = [];
|
|
21
|
+
/** Raw items for worker initialization */
|
|
22
|
+
this.rawItems = [];
|
|
23
|
+
/** Web Worker client for async search operations */
|
|
24
|
+
this.workerClient = null;
|
|
25
|
+
/** Effective filter mode (may differ from prop if fuzzy falls back to smart) */
|
|
26
|
+
this.effectiveFilterMode = 'strict';
|
|
27
|
+
/****************************************************************************/
|
|
28
|
+
//#region PROPERTIES
|
|
29
|
+
/**
|
|
30
|
+
* Sets the ID for the input element and the for attribute of the associated
|
|
31
|
+
* label. If no ID is provided, a random one will be automatically generated
|
|
32
|
+
* to ensure unique identification, facilitating proper label association and
|
|
33
|
+
* accessibility.
|
|
34
|
+
*/
|
|
35
|
+
this.inputId = v4BdYh22OP.v4();
|
|
36
|
+
/**
|
|
37
|
+
* The autocomplete prop helps users fill out the input field faster by
|
|
38
|
+
* suggesting entries they've used before, like their email or address.
|
|
39
|
+
* You can turn it on to make forms more convenient or off to ensure users
|
|
40
|
+
* always type in fresh data.
|
|
41
|
+
*/
|
|
42
|
+
this.autocomplete = 'off';
|
|
43
|
+
/**
|
|
44
|
+
* Marks the input field as required, ensuring that the user must fill it out
|
|
45
|
+
* before submitting the form.
|
|
46
|
+
* @note This uses the native HTML `required` attribute, which triggers browser validation.
|
|
47
|
+
*/
|
|
48
|
+
this.required = false;
|
|
49
|
+
/**
|
|
50
|
+
* Marks the input field as required for accessibility purposes without triggering
|
|
51
|
+
* native HTML validation. Use this when implementing custom validation logic.
|
|
52
|
+
* @note When set, this uses `aria-required` instead of the native `required` attribute.
|
|
53
|
+
* This allows developers to implement custom validation while maintaining accessibility.
|
|
54
|
+
* @note If this prop is not explicitly set, the component will check for the HTML attribute
|
|
55
|
+
* 'aria-required' directly to determine if it should be applied.
|
|
56
|
+
*/
|
|
57
|
+
this.ariaRequiredAttr = false;
|
|
58
|
+
/**
|
|
59
|
+
* Display the input field's content without allowing users to change it.
|
|
60
|
+
* Users can still click on it, select, and copy the text, but they won't be
|
|
61
|
+
* able to type or delete anything.
|
|
62
|
+
*/
|
|
63
|
+
this.readonly = false;
|
|
64
|
+
/**
|
|
65
|
+
* The disabled prop lets you turn off the input field so that users can't
|
|
66
|
+
* type in it. When disabled, the field is grayed out and won't respond to
|
|
67
|
+
* clicks or touches.
|
|
68
|
+
*/
|
|
69
|
+
this.disabled = false;
|
|
70
|
+
/**
|
|
71
|
+
* Alters the input field's appearance to indicate an error, helping users
|
|
72
|
+
* identify fields that need correction.
|
|
73
|
+
* @validator error
|
|
74
|
+
*/
|
|
75
|
+
this.error = false;
|
|
76
|
+
/**
|
|
77
|
+
* Defines the maximum height of the multiselect list when open.
|
|
78
|
+
*/
|
|
79
|
+
this.maxHeight = '';
|
|
80
|
+
/**
|
|
81
|
+
* The text to display when no items match the filter.
|
|
82
|
+
*/
|
|
83
|
+
this.emptyResult = 'No results found';
|
|
84
|
+
/**
|
|
85
|
+
* Enables or disables the filtering feature for the dropdown items.
|
|
86
|
+
*/
|
|
87
|
+
this.filterable = false;
|
|
88
|
+
/**
|
|
89
|
+
* When an item is selected by the user, the dropdown will continue to stay
|
|
90
|
+
* open.
|
|
91
|
+
*/
|
|
92
|
+
this.openOnSelect = false;
|
|
93
|
+
/**
|
|
94
|
+
* Determines if the component’s filtering behavior is managed externally.
|
|
95
|
+
* When set to true and filterable is enabled, the component won’t
|
|
96
|
+
* automatically filter items. Instead, you must implement your own filtering
|
|
97
|
+
* logic (e.g., server-side search or custom matching) using the
|
|
98
|
+
* filterTextChanged event.
|
|
99
|
+
*/
|
|
100
|
+
this.controlledFilter = false;
|
|
101
|
+
/**
|
|
102
|
+
* Delay in milliseconds before the search is triggered when typing in the
|
|
103
|
+
* filter input.
|
|
104
|
+
* @default 300
|
|
105
|
+
*/
|
|
106
|
+
this.debounceDelay = 300;
|
|
107
|
+
/**
|
|
108
|
+
* Applies focus to the input field as soon as the component is mounted. This
|
|
109
|
+
* is equivalent to setting the native autofocus attribute on an <input>
|
|
110
|
+
* element.
|
|
111
|
+
*/
|
|
112
|
+
this.autofocus = false;
|
|
113
|
+
/**
|
|
114
|
+
* Allows the field to stretch and fill the entire width of its container.
|
|
115
|
+
*/
|
|
116
|
+
this.fluid = false;
|
|
117
|
+
/**
|
|
118
|
+
* Filter mode for dropdown search:
|
|
119
|
+
* - 'strict': Simple substring matching (normalized includes)
|
|
120
|
+
* - 'smart': Token-based matching (all query tokens must exist, order ignored)
|
|
121
|
+
* - 'fuzzy': Typo-tolerant matching using Fuse.js (runs in Web Worker)
|
|
122
|
+
* @default 'strict'
|
|
123
|
+
*/
|
|
124
|
+
this.filterMode = 'strict';
|
|
125
|
+
/**
|
|
126
|
+
* Maximum number of results to display. Protects UI performance on large datasets.
|
|
127
|
+
* Values are clamped between 10 and 500 (hard cap).
|
|
128
|
+
* @default 25
|
|
129
|
+
*/
|
|
130
|
+
this.maxResults = 25;
|
|
131
|
+
/**
|
|
132
|
+
* Minimum number of characters required before filtering starts.
|
|
133
|
+
* Useful for preventing overwhelming results on very large datasets.
|
|
134
|
+
* @default 0
|
|
135
|
+
*/
|
|
136
|
+
this.startFilterAt = 0;
|
|
137
|
+
/**
|
|
138
|
+
* Number of items above which filtering is offloaded to a Web Worker.
|
|
139
|
+
* This keeps the main thread responsive for large datasets.
|
|
140
|
+
* @default 2000
|
|
141
|
+
*/
|
|
142
|
+
this.workerThreshold = 2000;
|
|
143
|
+
/**
|
|
144
|
+
* Threshold for fuzzy matching (0-1). Lower values are stricter.
|
|
145
|
+
* Only applies when filterMode is 'fuzzy'.
|
|
146
|
+
* @default 0.3
|
|
147
|
+
* @see {@link https://fusejs.io/api/options.html#threshold} Fuse.js threshold documentation
|
|
148
|
+
*/
|
|
149
|
+
this.fuzzyThreshold = 0.3;
|
|
150
|
+
//#endregion PROPERTIES
|
|
151
|
+
/****************************************************************************/
|
|
152
|
+
//#region STATE
|
|
153
|
+
/**
|
|
154
|
+
* The text entered by the user for filtering dropdown items.
|
|
155
|
+
*/
|
|
156
|
+
this.filterText = '';
|
|
157
|
+
this.selectedValues = new Set();
|
|
158
|
+
this.open = false;
|
|
159
|
+
/**
|
|
160
|
+
* Whether search results were truncated due to maxResults limit.
|
|
161
|
+
*/
|
|
162
|
+
this.resultsTruncated = false;
|
|
163
|
+
/**
|
|
164
|
+
* Total number of matches before truncation.
|
|
165
|
+
*/
|
|
166
|
+
this.totalResults = 0;
|
|
167
|
+
/**
|
|
168
|
+
* Whether a worker search is in progress (shows loading indicator).
|
|
169
|
+
*/
|
|
170
|
+
this.isSearching = false;
|
|
171
|
+
/**
|
|
172
|
+
* Closes the popover when a click is detected outside the component.
|
|
173
|
+
* @param {MouseEvent} event - The click event.
|
|
174
|
+
*/
|
|
175
|
+
this.handleClickOutside = (event) => {
|
|
176
|
+
var _a;
|
|
177
|
+
if (!(event.target instanceof Node))
|
|
178
|
+
return;
|
|
179
|
+
if (!((_a = this.el) === null || _a === void 0 ? void 0 : _a.contains(event.target)))
|
|
180
|
+
this.open = false;
|
|
181
|
+
};
|
|
182
|
+
this.handleFilterInput = () => {
|
|
183
|
+
this.open = true;
|
|
184
|
+
this.filterText = this.inputElement.value;
|
|
185
|
+
this.filterTextChanged.emit(this.inputElement.value);
|
|
186
|
+
clearTimeout(this.debounceTimer);
|
|
187
|
+
// Use longer debounce for fuzzy mode (Fuse.js needs more time)
|
|
188
|
+
// For fuzzy mode, use FUZZY_DEBOUNCE_DELAY (300ms), otherwise use this.debounceDelay
|
|
189
|
+
const debounce = this.effectiveFilterMode === 'fuzzy'
|
|
190
|
+
? i18n_utilsDOZbXX2L.FUZZY_DEBOUNCE_DELAY
|
|
191
|
+
: this.debounceDelay;
|
|
192
|
+
this.debounceTimer = window.setTimeout(() => {
|
|
193
|
+
this.filterItems();
|
|
194
|
+
}, debounce);
|
|
195
|
+
};
|
|
196
|
+
this.getSelectedLabel = () => {
|
|
197
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
198
|
+
if (!this.value)
|
|
199
|
+
return '';
|
|
200
|
+
if ((_a = this.filterText) === null || _a === void 0 ? void 0 : _a.length)
|
|
201
|
+
return this.filterText;
|
|
202
|
+
if (((_b = this.options) === null || _b === void 0 ? void 0 : _b.length) > 1) {
|
|
203
|
+
const matchingItem = this.options.find(option => option.value === this.value);
|
|
204
|
+
return (_d = (_c = matchingItem === null || matchingItem === void 0 ? void 0 : matchingItem.label) !== null && _c !== void 0 ? _c : matchingItem === null || matchingItem === void 0 ? void 0 : matchingItem.value) !== null && _d !== void 0 ? _d : this.value;
|
|
205
|
+
}
|
|
206
|
+
const items = Array.from(this.el.querySelectorAll('nv-fielddropdownitem'));
|
|
207
|
+
const matchingItem = items.find(item => item.value === this.value);
|
|
208
|
+
const selectedLabel = matchingItem
|
|
209
|
+
? (_g = (_e = matchingItem.label) !== null && _e !== void 0 ? _e : (_f = matchingItem.textContent) === null || _f === void 0 ? void 0 : _f.trim()) !== null && _g !== void 0 ? _g : matchingItem.value
|
|
210
|
+
: '';
|
|
211
|
+
return selectedLabel;
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
/* eslint-enable nova/event-bubbling */
|
|
215
|
+
//#endregion EVENTS
|
|
216
|
+
/****************************************************************************/
|
|
217
|
+
//#region WATCHERS
|
|
218
|
+
handleOpenChange(newOpen) {
|
|
219
|
+
if (newOpen === false) {
|
|
220
|
+
clearTimeout(this.debounceTimer);
|
|
221
|
+
setTimeout(() => {
|
|
222
|
+
this.clearFilter();
|
|
223
|
+
}, 100);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
handleValueChange() {
|
|
227
|
+
this.updateSelectedItem();
|
|
228
|
+
if (!this.open || !this.filterText)
|
|
229
|
+
this.setFilterInputToSelectedValue();
|
|
230
|
+
}
|
|
231
|
+
handleOptionsChange() {
|
|
232
|
+
this.updateSelectedItem();
|
|
233
|
+
this.rebuildSearchIndex();
|
|
234
|
+
}
|
|
235
|
+
//#endregion WATCHERS
|
|
236
|
+
/****************************************************************************/
|
|
237
|
+
//#region LISTENERS
|
|
238
|
+
/* eslint-disable nova/native-event-listener */
|
|
239
|
+
handleDropdownItemSelected(event) {
|
|
240
|
+
event.stopPropagation();
|
|
241
|
+
if (this.disabled || this.readonly)
|
|
242
|
+
return;
|
|
243
|
+
if (!this.openOnSelect)
|
|
244
|
+
this.open = false;
|
|
245
|
+
if (event.detail.detached)
|
|
246
|
+
return;
|
|
247
|
+
const items = this.getAllItems();
|
|
248
|
+
items.forEach(item => {
|
|
249
|
+
if (item !== event.target) {
|
|
250
|
+
item.removeAttribute('selected');
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
item.setAttribute('selected', 'true');
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
this.value = event.detail.value;
|
|
257
|
+
this.setFilterInputToSelectedValue();
|
|
258
|
+
this.valueChanged.emit(event.detail.value);
|
|
259
|
+
}
|
|
260
|
+
/* eslint-enable nova/native-event-listener */
|
|
261
|
+
handleFocus(event) {
|
|
262
|
+
if (event.relatedTarget instanceof HTMLElement &&
|
|
263
|
+
event.relatedTarget.tagName.includes('NV-FIELDDROPDOWNITEM')) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (event.target != this.toggleElement)
|
|
267
|
+
this.open = true;
|
|
268
|
+
}
|
|
269
|
+
handleFocusOut(event) {
|
|
270
|
+
if (!(event.relatedTarget instanceof Node))
|
|
271
|
+
return;
|
|
272
|
+
if (this.el.contains(event.relatedTarget))
|
|
273
|
+
return;
|
|
274
|
+
this.open = false;
|
|
275
|
+
}
|
|
276
|
+
handleKeyDown(event) {
|
|
277
|
+
if (event.key === 'Escape') {
|
|
278
|
+
this.focusField();
|
|
279
|
+
this.open = false;
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (event.key === ' ') {
|
|
283
|
+
if (event.target == this.selectElement) {
|
|
284
|
+
event.preventDefault(); // Prevent scrolling down
|
|
285
|
+
this.open = true;
|
|
286
|
+
}
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const items = this.getNavigableItems();
|
|
290
|
+
const highlightedItem = this.getHighlightedItemIndex(items);
|
|
291
|
+
if (event.key === 'ArrowDown') {
|
|
292
|
+
event.preventDefault(); // Prevent scrolling down
|
|
293
|
+
this.open = true;
|
|
294
|
+
const nextIndex = highlightedItem + 1;
|
|
295
|
+
this.updateHighlightedItem(items, nextIndex >= items.length ? 0 : nextIndex);
|
|
296
|
+
}
|
|
297
|
+
if (event.key === 'ArrowUp') {
|
|
298
|
+
event.preventDefault(); // Prevent scrolling up
|
|
299
|
+
this.open = true;
|
|
300
|
+
const nextIndex = highlightedItem - 1;
|
|
301
|
+
this.updateHighlightedItem(items, nextIndex < 0 ? items.length - 1 : nextIndex);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
handleDocumentClick(event) {
|
|
305
|
+
this.handleClickOutside(event);
|
|
306
|
+
}
|
|
307
|
+
//#endregion LISTENERS
|
|
308
|
+
/****************************************************************************/
|
|
309
|
+
//#region METHODS
|
|
310
|
+
/** Clears the filter text */
|
|
311
|
+
async clearFilter() {
|
|
312
|
+
var _a;
|
|
313
|
+
if (!this.filterable)
|
|
314
|
+
return;
|
|
315
|
+
if ((_a = this.filterText) === null || _a === void 0 ? void 0 : _a.length) {
|
|
316
|
+
this.filterText = '';
|
|
317
|
+
this.filterTextChanged.emit(this.filterText);
|
|
318
|
+
}
|
|
319
|
+
this.filterItems();
|
|
320
|
+
// Wait for wrapper lifecycle to finish
|
|
321
|
+
setTimeout(() => {
|
|
322
|
+
this.setFilterInputToSelectedValue();
|
|
323
|
+
}, 0);
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Toggles the dropdown popover open state
|
|
327
|
+
* @param {boolean} open - The open state to set, if null, toggles the state
|
|
328
|
+
*/
|
|
329
|
+
async toggleDropdown(open) {
|
|
330
|
+
if (open !== undefined) {
|
|
331
|
+
setTimeout(() => {
|
|
332
|
+
this.open = open;
|
|
333
|
+
}, 0);
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
setTimeout(() => {
|
|
337
|
+
this.open = !this.open;
|
|
338
|
+
}, 0);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
syncToggleDropdown() {
|
|
342
|
+
this.open = !this.open;
|
|
343
|
+
}
|
|
344
|
+
setFilterInputToSelectedValue() {
|
|
345
|
+
if (!this.filterable && !this.disabled && !this.readonly)
|
|
346
|
+
return;
|
|
347
|
+
if (!this.inputElement)
|
|
348
|
+
return;
|
|
349
|
+
if (!this.value)
|
|
350
|
+
return (this.inputElement.value = '');
|
|
351
|
+
this.inputElement.value = this.getSelectedLabel();
|
|
352
|
+
}
|
|
353
|
+
// Will exclude detached items and data-empty
|
|
354
|
+
getFilterableItems() {
|
|
355
|
+
return Array.from(this.el.querySelectorAll('nv-fielddropdownitem:not([data-empty]):not([detached])'));
|
|
356
|
+
}
|
|
357
|
+
getNavigableItems() {
|
|
358
|
+
return Array.from(this.el.querySelectorAll('nv-fielddropdownitem:not([disabled]):not([hidden])'));
|
|
359
|
+
}
|
|
360
|
+
getAllItems() {
|
|
361
|
+
return Array.from(this.el.querySelectorAll('nv-fielddropdownitem'));
|
|
362
|
+
}
|
|
363
|
+
getHighlightedItemIndex(items) {
|
|
364
|
+
return items.findIndex(item => item.classList.contains('highlighted'));
|
|
365
|
+
}
|
|
366
|
+
updateHighlightedItem(items, index) {
|
|
367
|
+
items.forEach((item, i) => {
|
|
368
|
+
item.classList.remove('highlighted');
|
|
369
|
+
if (i === index) {
|
|
370
|
+
item.classList.add('highlighted');
|
|
371
|
+
item.focus();
|
|
372
|
+
item.scrollIntoView({ block: 'nearest' });
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
focusField() {
|
|
377
|
+
const focusableItem = this.el.querySelector('[data-scope="focusable"]');
|
|
378
|
+
focusableItem === null || focusableItem === void 0 ? void 0 : focusableItem.focus();
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Rebuilds the search index from current items (options prop or slot items).
|
|
382
|
+
*/
|
|
383
|
+
async rebuildSearchIndex() {
|
|
384
|
+
var _a;
|
|
385
|
+
const items = this.getFilterableItems();
|
|
386
|
+
// Build raw items array for indexing
|
|
387
|
+
this.rawItems = items.map((item, index) => {
|
|
388
|
+
var _a;
|
|
389
|
+
return ({
|
|
390
|
+
id: item.value || `item-${index}`,
|
|
391
|
+
label: item.label || ((_a = item.textContent) === null || _a === void 0 ? void 0 : _a.trim()) || item.value || '',
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
// Also include options prop items if present
|
|
395
|
+
if ((_a = this.options) === null || _a === void 0 ? void 0 : _a.length) {
|
|
396
|
+
this.options.forEach((opt, index) => {
|
|
397
|
+
this.rawItems.push({
|
|
398
|
+
id: opt.value || `opt-${index}`,
|
|
399
|
+
label: opt.label || opt.value || '',
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
// Build local index for main-thread search
|
|
404
|
+
this.indexedItems = i18n_utilsDOZbXX2L.buildIndex(this.rawItems);
|
|
405
|
+
// Determine effective filter mode
|
|
406
|
+
this.effectiveFilterMode = i18n_utilsDOZbXX2L.getEffectiveFilterMode(this.filterMode);
|
|
407
|
+
// Initialize worker if needed
|
|
408
|
+
const itemCount = this.rawItems.length;
|
|
409
|
+
const threshold = i18n_utilsDOZbXX2L.clampWorkerThreshold(this.workerThreshold);
|
|
410
|
+
if (i18n_utilsDOZbXX2L.shouldUseWorker(itemCount, this.effectiveFilterMode, threshold)) {
|
|
411
|
+
await this.initWorker();
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
// Clean up worker if no longer needed
|
|
415
|
+
this.terminateWorker();
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Initializes the Web Worker for search operations.
|
|
420
|
+
* Falls back to main thread if worker is not available.
|
|
421
|
+
* Note: Fuzzy search now works on main thread with Fuse.js, so no mode change needed.
|
|
422
|
+
*/
|
|
423
|
+
async initWorker() {
|
|
424
|
+
if (!i18n_utilsDOZbXX2L.isWorkerSupported()) {
|
|
425
|
+
// Workers not supported - will use main thread (fuzzy still works with Fuse.js)
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
if (!this.workerClient) {
|
|
429
|
+
this.workerClient = new i18n_utilsDOZbXX2L.SearchWorkerClient();
|
|
430
|
+
// No fallback mode change needed - fuzzy works on main thread now
|
|
431
|
+
}
|
|
432
|
+
// Init will silently handle fallback if worker can't be created
|
|
433
|
+
await this.workerClient.init(this.rawItems);
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Terminates the Web Worker and cleans up resources.
|
|
437
|
+
*/
|
|
438
|
+
terminateWorker() {
|
|
439
|
+
if (this.workerClient) {
|
|
440
|
+
this.workerClient.terminate();
|
|
441
|
+
this.workerClient = null;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Filter dropdown items based on the text entered by the user.
|
|
446
|
+
* Uses the search engine for optimized filtering with configurable modes.
|
|
447
|
+
* If no items are found, display a message indicating no results.
|
|
448
|
+
*/
|
|
449
|
+
async filterItems() {
|
|
450
|
+
var _a;
|
|
451
|
+
if (this.controlledFilter)
|
|
452
|
+
return;
|
|
453
|
+
// Ensure index is built
|
|
454
|
+
if (!this.indexedItems.length) {
|
|
455
|
+
await this.rebuildSearchIndex();
|
|
456
|
+
}
|
|
457
|
+
const items = this.getFilterableItems();
|
|
458
|
+
// Remove any existing "no results found" or "truncated" items
|
|
459
|
+
this.el
|
|
460
|
+
.querySelectorAll('nv-fielddropdownitem[data-empty], nv-fielddropdownitem[data-truncated]')
|
|
461
|
+
.forEach(item => item.remove());
|
|
462
|
+
// Check if we should start filtering
|
|
463
|
+
if (this.filterText.length < this.startFilterAt) {
|
|
464
|
+
// Show all items up to maxResults
|
|
465
|
+
const effectiveMaxResults = i18n_utilsDOZbXX2L.clampMaxResults(this.maxResults);
|
|
466
|
+
let visibleCount = 0;
|
|
467
|
+
items.forEach(item => {
|
|
468
|
+
if (visibleCount < effectiveMaxResults) {
|
|
469
|
+
item.removeAttribute('hidden');
|
|
470
|
+
visibleCount++;
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
item.setAttribute('hidden', '');
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
this.resultsTruncated = items.length > effectiveMaxResults;
|
|
477
|
+
this.totalResults = items.length;
|
|
478
|
+
if (this.resultsTruncated) {
|
|
479
|
+
this.addTruncatedMessage(effectiveMaxResults, items.length);
|
|
480
|
+
}
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
// Determine if we should use worker
|
|
484
|
+
const itemCount = this.rawItems.length;
|
|
485
|
+
const threshold = i18n_utilsDOZbXX2L.clampWorkerThreshold(this.workerThreshold);
|
|
486
|
+
const useWorker = i18n_utilsDOZbXX2L.shouldUseWorker(itemCount, this.effectiveFilterMode, threshold);
|
|
487
|
+
let result;
|
|
488
|
+
if (useWorker && ((_a = this.workerClient) === null || _a === void 0 ? void 0 : _a.isReady)) {
|
|
489
|
+
// Use worker for search
|
|
490
|
+
this.isSearching = true;
|
|
491
|
+
try {
|
|
492
|
+
result = await this.workerClient.search(this.filterText, this.effectiveFilterMode, this.maxResults, this.fuzzyThreshold);
|
|
493
|
+
}
|
|
494
|
+
catch (error) {
|
|
495
|
+
console.error('Worker search failed, falling back to main thread:', error);
|
|
496
|
+
// Fall back to main thread search (now supports fuzzy with Fuse.js)
|
|
497
|
+
result = i18n_utilsDOZbXX2L.search(this.indexedItems, {
|
|
498
|
+
query: this.filterText,
|
|
499
|
+
filterMode: this.effectiveFilterMode,
|
|
500
|
+
maxResults: this.maxResults,
|
|
501
|
+
startFilterAt: this.startFilterAt,
|
|
502
|
+
fuzzyThreshold: this.fuzzyThreshold,
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
finally {
|
|
506
|
+
this.isSearching = false;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
// Use main thread search (now supports fuzzy mode with Fuse.js)
|
|
511
|
+
result = i18n_utilsDOZbXX2L.search(this.indexedItems, {
|
|
512
|
+
query: this.filterText,
|
|
513
|
+
filterMode: this.effectiveFilterMode,
|
|
514
|
+
maxResults: this.maxResults,
|
|
515
|
+
startFilterAt: this.startFilterAt,
|
|
516
|
+
fuzzyThreshold: this.fuzzyThreshold,
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
this.resultsTruncated = result.truncated;
|
|
520
|
+
this.totalResults = result.total;
|
|
521
|
+
// Apply results to DOM
|
|
522
|
+
this.applySearchResults(result, items);
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Applies search results to the DOM, showing/hiding items as needed.
|
|
526
|
+
* @param {SearchResult} result - The search result.
|
|
527
|
+
* @param {HTMLNvFielddropdownitemElement[]} items - The items to apply the search results to.
|
|
528
|
+
* @example
|
|
529
|
+
* const result: SearchResult = {
|
|
530
|
+
* ids: ['item-1', 'item-2'],
|
|
531
|
+
* total: 2,
|
|
532
|
+
* truncated: false,
|
|
533
|
+
* };
|
|
534
|
+
* const items = Array.from(
|
|
535
|
+
* this.el.querySelectorAll('nv-fielddropdownitem'),
|
|
536
|
+
* ) as HTMLNvFielddropdownitemElement[];
|
|
537
|
+
* this.applySearchResults(result, items);
|
|
538
|
+
* // Matching items are shown, non-matching items are hidden.
|
|
539
|
+
* // If no items match, a "no results" item is prepended to the list.
|
|
540
|
+
*/
|
|
541
|
+
applySearchResults(result, items) {
|
|
542
|
+
var _a;
|
|
543
|
+
// Create a Set of matching IDs for fast lookup
|
|
544
|
+
const matchingIds = new Set(result.ids);
|
|
545
|
+
let hasVisibleItems = false;
|
|
546
|
+
let visibleCount = 0;
|
|
547
|
+
const effectiveMaxResults = i18n_utilsDOZbXX2L.clampMaxResults(this.maxResults);
|
|
548
|
+
items.forEach(item => {
|
|
549
|
+
var _a;
|
|
550
|
+
const itemId = item.value || '';
|
|
551
|
+
const itemLabel = item.label || ((_a = item.textContent) === null || _a === void 0 ? void 0 : _a.trim()) || '';
|
|
552
|
+
// Check if this item matches (by ID or by re-running search on label)
|
|
553
|
+
const isMatch = matchingIds.has(itemId) ||
|
|
554
|
+
result.ids.some(id => {
|
|
555
|
+
const indexed = this.indexedItems.find(i => i.id === id);
|
|
556
|
+
return (indexed && (indexed.label === itemLabel || indexed.id === itemId));
|
|
557
|
+
});
|
|
558
|
+
if (isMatch && visibleCount < effectiveMaxResults) {
|
|
559
|
+
item.removeAttribute('hidden');
|
|
560
|
+
hasVisibleItems = true;
|
|
561
|
+
visibleCount++;
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
item.setAttribute('hidden', '');
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
// If no items are visible, add the "no results found" item
|
|
568
|
+
if (!hasVisibleItems) {
|
|
569
|
+
const emptyItem = document.createElement('nv-fielddropdownitem');
|
|
570
|
+
emptyItem.setAttribute('data-empty', 'true');
|
|
571
|
+
emptyItem.setAttribute('disabled', 'true');
|
|
572
|
+
emptyItem.textContent = this.emptyResult;
|
|
573
|
+
(_a = this.el.querySelector('div[slot="content"] ul')) === null || _a === void 0 ? void 0 : _a.prepend(emptyItem);
|
|
574
|
+
}
|
|
575
|
+
else if (result.truncated) {
|
|
576
|
+
// Add truncation message if results were limited
|
|
577
|
+
this.addTruncatedMessage(result.ids.length, result.total);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Adds a non-selectable item showing truncation info.
|
|
582
|
+
* @param {number} shown - The number of items shown.
|
|
583
|
+
* @param {number} total - The total number of items.
|
|
584
|
+
* @example
|
|
585
|
+
* // Show a truncation message like "10 of 100 results".
|
|
586
|
+
* this.addTruncatedMessage(10, 100);
|
|
587
|
+
*/
|
|
588
|
+
addTruncatedMessage(shown, total) {
|
|
589
|
+
// Get the text template: use provided text or auto-detect from locale
|
|
590
|
+
const textTemplate = this.truncatedResultsText || i18n_utilsDOZbXX2L.getTruncatedResultsText(this.locale);
|
|
591
|
+
const truncatedItem = document.createElement('nv-fielddropdownitem');
|
|
592
|
+
truncatedItem.setAttribute('data-truncated', 'true');
|
|
593
|
+
truncatedItem.setAttribute('disabled', 'true');
|
|
594
|
+
truncatedItem.setAttribute('detached', 'true');
|
|
595
|
+
truncatedItem.className = 'truncated-message';
|
|
596
|
+
truncatedItem.textContent = i18n_utilsDOZbXX2L.formatTruncatedResults(textTemplate, shown, total);
|
|
597
|
+
const ul = this.el.querySelector('div[slot="content"] ul');
|
|
598
|
+
if (ul) {
|
|
599
|
+
ul.appendChild(truncatedItem);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
async updateSelectedItem() {
|
|
603
|
+
const items = this.getAllItems();
|
|
604
|
+
if (this.value) {
|
|
605
|
+
items.forEach(item => {
|
|
606
|
+
if (item.value === this.value) {
|
|
607
|
+
item.selected = true;
|
|
608
|
+
}
|
|
609
|
+
else {
|
|
610
|
+
item.selected = false;
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
//#endregion METHODS
|
|
616
|
+
/****************************************************************************/
|
|
617
|
+
//#region LIFECYCLE
|
|
618
|
+
componentDidLoad() {
|
|
619
|
+
this.updateSelectedItem();
|
|
620
|
+
this.setFilterInputToSelectedValue();
|
|
621
|
+
// Build the search index on load
|
|
622
|
+
this.rebuildSearchIndex();
|
|
623
|
+
}
|
|
624
|
+
disconnectedCallback() {
|
|
625
|
+
clearTimeout(this.debounceTimer);
|
|
626
|
+
this.terminateWorker();
|
|
627
|
+
}
|
|
628
|
+
componentDidRender() {
|
|
629
|
+
// Make sure to show the value when the field is disabled or readonly
|
|
630
|
+
// as we switch to an input instead of a p in that case
|
|
631
|
+
if (!this.filterable && (this.disabled || this.readonly)) {
|
|
632
|
+
this.inputElement.value = this.getSelectedLabel();
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
//#endregion LIFECYCLE
|
|
636
|
+
/****************************************************************************/
|
|
637
|
+
//#region RENDER
|
|
638
|
+
render() {
|
|
639
|
+
var _a;
|
|
640
|
+
// Check aria-required from multiple sources:
|
|
641
|
+
// 1. JavaScript prop (most reliable)
|
|
642
|
+
// 2. HTML attribute 'aria-required' (direct HTML)
|
|
643
|
+
// 3. HTML attribute 'aria-required-attr' (from JSX kebab-case conversion)
|
|
644
|
+
// Check aria-required from multiple sources:
|
|
645
|
+
// 1. HTML attribute 'aria-required' (direct HTML) - check if explicitly set
|
|
646
|
+
// 2. JavaScript prop (when prop is explicitly set via JavaScript)
|
|
647
|
+
// We use hasAttribute to determine if the attribute was explicitly set by the user,
|
|
648
|
+
// since the prop now defaults to false (to maintain Blazor compatibility)
|
|
649
|
+
const hasAriaRequiredAttr = this.el.hasAttribute('aria-required') ||
|
|
650
|
+
this.el.hasAttribute('aria-required-attr');
|
|
651
|
+
const ariaRequiredFromAttr = hasAriaRequiredAttr
|
|
652
|
+
? this.el.getAttribute('aria-required') ||
|
|
653
|
+
this.el.getAttribute('aria-required-attr')
|
|
654
|
+
: null;
|
|
655
|
+
// Use aria-required if the attribute was explicitly set
|
|
656
|
+
// With reflect: true, setting the prop will also set the attribute
|
|
657
|
+
const useAriaRequired = hasAriaRequiredAttr;
|
|
658
|
+
// Determine the value: use attribute if it exists (prop reflects to attribute via reflect: true)
|
|
659
|
+
// If attribute doesn't exist, the prop was never set and we don't use aria-required
|
|
660
|
+
const ariaRequiredAttrValue = hasAriaRequiredAttr
|
|
661
|
+
? ariaRequiredFromAttr
|
|
662
|
+
: null;
|
|
663
|
+
// Determine which attributes to use
|
|
664
|
+
// If aria-required HTML attribute is present, use it (convert string to boolean)
|
|
665
|
+
// If required is set and aria-required is not "false", use native required
|
|
666
|
+
// If aria-required is "false", don't use native required even if required is set
|
|
667
|
+
const ariaRequiredValue = useAriaRequired
|
|
668
|
+
? ariaRequiredAttrValue === 'true' || ariaRequiredAttrValue === ''
|
|
669
|
+
: undefined;
|
|
670
|
+
const useNativeRequired = this.required && (!useAriaRequired || ariaRequiredValue === true);
|
|
671
|
+
return (index.h(index.Host, { key: 'dc41eb22c752a710e01d3e1f4c1df7b6ddef13c5', role: "combobox", "aria-expanded": this.open.toString(), "aria-haspopup": "listbox", "aria-label": this.label }, (this.label || this.el.querySelector('[slot="label"]')) && (index.h("label", { key: 'a4dd17caf33e640ca500c7978e2434fda451e6b2', htmlFor: this.inputId, onClick: this.syncToggleDropdown.bind(this) }, index.h("slot", { key: 'b9a6e9ca717e8e2521805c46f73c3b3bc23c6fab', name: "label" }, this.label))), index.h("nv-popover", { key: '6144e9bcfdad2d3a8cf64a7519c3a3e6b83c3e7b', triggerMode: "controlled", placement: "bottom-start", open: this.open, onOpenChanged: e => this.openChanged.emit(e.detail) }, index.h("div", { key: '18644c078edd2735d0eda0d50ad6313c18c66332', class: "input-wrapper", slot: "trigger" }, index.h("slot", { key: '08249f09060353a2bb1c4dfe5c87959dc5e7c5e8', name: "before-input" }), index.h("div", { key: '28cbb91e02827a257bce0ead67e38f07bf75e190', class: "input-container" }, index.h("slot", { key: '68b4d1d34df6699f880667758cfc9d1a65cea271', name: "leading-input" }), this.filterable || this.disabled || this.readonly ? (index.h("input", Object.assign({ "data-scope": "focusable", id: this.inputId, type: "search", ref: e => (this.inputElement = e), autofocus: this.autofocus, autocomplete: this.autocomplete, placeholder: this.placeholder, name: this.name, required: useNativeRequired ? this.required : undefined }, (ariaRequiredValue !== undefined && {
|
|
672
|
+
'aria-required': String(ariaRequiredValue),
|
|
673
|
+
}), { disabled: this.disabled, readOnly: this.readonly, onInput: this.handleFilterInput }))) : (index.h("p", { "data-scope": "focusable", id: this.inputId, ref: el => (this.selectElement = el), class: "non-filterable-text", tabIndex: this.disabled ? -1 : 0 }, this.getSelectedLabel() || this.value || this.placeholder)), this.filterable && this.filterText && (index.h("nv-iconbutton", { key: '100f2b73b8da4cc6994b284286cf53e7761a6df7', name: "x", size: "md", emphasis: "lower", class: "clear-button", onClick: this.clearFilter.bind(this), "aria-label": "Clear input" })), this.error && (index.h("nv-icon", { key: 'bd9d0419115d0c4342e30bf52f215f010cb09318', name: "alert-circle", class: "validation", size: "md" })), index.h("nv-iconbutton", { key: 'e9cc3bfe124a299071fd99bf17d838268ffb1c68', "data-scope": "toggle-dropdown", ref: el => (this.toggleElement = el), name: this.open ? 'chevron-top' : 'chevron-down', size: "md", emphasis: "lower", "aria-label": this.open ? 'Hide dropdown' : 'Show dropdown', onClick: this.syncToggleDropdown.bind(this), tabIndex: this.disabled ? -1 : 0 })), index.h("slot", { key: '24ab68b197eeb1d57a1197f084e6a990f68dd4e0', name: "after-input" })), index.h("div", { key: '2b75c07d5da9c43f9bdfa8bf3352a6788a2b5a10', slot: "content", style: this.maxHeight ? { maxHeight: this.maxHeight } : {} }, ((_a = this.options) === null || _a === void 0 ? void 0 : _a.length) > 0 && (index.h("ul", { key: '3d79e3bc111e020eebcf3fb4af002e645a5a9772' }, this.options.map(option => (index.h("nv-fielddropdownitem", { label: option.label, value: option.value, disabled: option.disabled, selected: option.value === this.value }))))), index.h("slot", { key: '47c7b6deb916ba09f49374eedf0d57e4def92d4c', name: "content" }))), (this.description ||
|
|
674
|
+
this.el.querySelector('[slot="description"]')) && (index.h("div", { key: 'f54cca0ebdf76ec3ac19ec9cebb757ec571431f9', class: "description" }, index.h("slot", { key: 'e8e939d10bb4fb379ba926a2f9b04077ad094231', name: "description" }, this.description))), (this.errorDescription ||
|
|
675
|
+
this.el.querySelector('[slot="error-description"]')) && (index.h("div", { key: 'e57ad2dd9b02c949a85f27811d930904c0c51e1f', hidden: !this.error, class: "error-description" }, index.h("slot", { key: '1db0ce749cc46bf5a15029f0fe88394af1b2ca7a', name: "error-description" }, this.errorDescription)))));
|
|
676
|
+
}
|
|
677
|
+
static get formAssociated() { return true; }
|
|
678
|
+
get el() { return index.getElement(this); }
|
|
679
|
+
static get watchers() { return {
|
|
680
|
+
"open": ["handleOpenChange"],
|
|
681
|
+
"value": ["handleValueChange"],
|
|
682
|
+
"options": ["handleOptionsChange"]
|
|
683
|
+
}; }
|
|
684
|
+
};
|
|
685
|
+
NvFielddropdown.style = nvFielddropdownCss;
|
|
686
|
+
|
|
687
|
+
exports.nv_fielddropdown = NvFielddropdown;
|