@aquera/nile-elements 1.7.2 → 1.7.3

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.
Files changed (47) hide show
  1. package/README.md +4 -0
  2. package/dist/index.cjs.js +1 -1
  3. package/dist/index.esm.js +1 -1
  4. package/dist/index.js +387 -265
  5. package/dist/nile-breadcrumb-item/nile-breadcrumb-item.cjs.js +1 -1
  6. package/dist/nile-breadcrumb-item/nile-breadcrumb-item.cjs.js.map +1 -1
  7. package/dist/nile-breadcrumb-item/nile-breadcrumb-item.esm.js +8 -6
  8. package/dist/nile-combobox/group-utils.cjs.js +2 -0
  9. package/dist/nile-combobox/group-utils.cjs.js.map +1 -0
  10. package/dist/nile-combobox/group-utils.esm.js +1 -0
  11. package/dist/nile-combobox/index.cjs.js +1 -1
  12. package/dist/nile-combobox/index.esm.js +1 -1
  13. package/dist/nile-combobox/nile-combobox.cjs.js +1 -1
  14. package/dist/nile-combobox/nile-combobox.cjs.js.map +1 -1
  15. package/dist/nile-combobox/nile-combobox.css.cjs.js +1 -1
  16. package/dist/nile-combobox/nile-combobox.css.cjs.js.map +1 -1
  17. package/dist/nile-combobox/nile-combobox.css.esm.js +77 -4
  18. package/dist/nile-combobox/nile-combobox.esm.js +13 -8
  19. package/dist/nile-combobox/renderer.cjs.js +1 -1
  20. package/dist/nile-combobox/renderer.cjs.js.map +1 -1
  21. package/dist/nile-combobox/renderer.esm.js +84 -42
  22. package/dist/src/nile-breadcrumb-item/nile-breadcrumb-item.js +4 -2
  23. package/dist/src/nile-breadcrumb-item/nile-breadcrumb-item.js.map +1 -1
  24. package/dist/src/nile-combobox/group-utils.d.ts +26 -0
  25. package/dist/src/nile-combobox/group-utils.js +140 -0
  26. package/dist/src/nile-combobox/group-utils.js.map +1 -0
  27. package/dist/src/nile-combobox/nile-combobox.css.js +77 -4
  28. package/dist/src/nile-combobox/nile-combobox.css.js.map +1 -1
  29. package/dist/src/nile-combobox/nile-combobox.d.ts +33 -0
  30. package/dist/src/nile-combobox/nile-combobox.js +171 -34
  31. package/dist/src/nile-combobox/nile-combobox.js.map +1 -1
  32. package/dist/src/nile-combobox/renderer.d.ts +4 -0
  33. package/dist/src/nile-combobox/renderer.js +71 -2
  34. package/dist/src/nile-combobox/renderer.js.map +1 -1
  35. package/dist/src/nile-combobox/types.d.ts +30 -0
  36. package/dist/src/nile-combobox/types.js.map +1 -1
  37. package/dist/src/version.js +1 -1
  38. package/dist/src/version.js.map +1 -1
  39. package/dist/tsconfig.tsbuildinfo +1 -1
  40. package/package.json +1 -1
  41. package/src/nile-breadcrumb-item/nile-breadcrumb-item.ts +4 -2
  42. package/src/nile-combobox/group-utils.ts +157 -0
  43. package/src/nile-combobox/nile-combobox.css.ts +77 -4
  44. package/src/nile-combobox/nile-combobox.ts +223 -70
  45. package/src/nile-combobox/renderer.ts +119 -2
  46. package/src/nile-combobox/types.ts +36 -0
  47. package/vscode-html-custom-data.json +6 -1
@@ -139,9 +139,10 @@ export const styles = css `
139
139
  /* Size: medium */
140
140
  .combobox--medium .combobox__trigger {
141
141
  border-radius: var(--nile-radius-sm, var(--ng-radius-md));
142
- font-size: var(--nile-type-scale-3, var(--ng-font-size-text-sm));
143
- padding: var(--nile-spacing-5px, var(--ng-spacing-md)) var(--nile-spacing-10px, var(--ng-spacing-lg));
142
+ font-size: var(--nile-type-scale-3, var(--ng-font-size-text-md));
143
+ padding: var(--nile-spacing-lg, var(--ng-spacing-md)) var(--nile-spacing-lg, var(--ng-spacing-lg));
144
144
  min-height: var(--nile-height-40px, var(--ng-height-40px));
145
+ box-sizing: border-box;
145
146
  }
146
147
 
147
148
  /* Size: large */
@@ -254,10 +255,10 @@ export const styles = css `
254
255
  margin: 0;
255
256
  -webkit-appearance: none;
256
257
  font-family: var(--nile-font-family-serif, var(--ng-font-family-body));
257
- font-size: var(--nile-type-scale-3, var(--ng-font-size-text-md));
258
+ font-size: inherit;
258
259
  font-weight: var(--nile-font-weight-regular, var(--ng-font-weight-regular));
259
260
  text-overflow: ellipsis;
260
- line-height: var(--nile-spacing-2xl, var(--ng-line-height-text-sm));
261
+ line-height: var(--nile-line-height-xsmall, var(--ng-line-height-text-sm));
261
262
  }
262
263
 
263
264
  .combobox__input::placeholder {
@@ -634,6 +635,78 @@ export const styles = css `
634
635
  border-radius: 0;
635
636
  }
636
637
 
638
+ /* ── Group headers (data-driven grouping) ── */
639
+
640
+ /* Plain (non-virtualized) listbox: sticky relative to the scroll container.
641
+ * Activated only when the host has [sticky-group-header]. */
642
+ :host([sticky-group-header]) .combobox__options-plain .combobox__group-header {
643
+ position: sticky;
644
+ top: calc(var(--group-depth, 0) * 28px);
645
+ z-index: 1;
646
+ }
647
+
648
+ /* Virtualized listbox: each row is absolutely positioned by the virtualizer.
649
+ * Sticky doesn't pin against the scroll container because the row wrapper's
650
+ * transform creates a containing block. Instead we render a separate
651
+ * overlay inside the listbox that mirrors the currently-active group
652
+ * header (computed on scroll). */
653
+ .combobox__group-header-slot {
654
+ pointer-events: none;
655
+ }
656
+ .combobox__group-header-slot .combobox__group-header {
657
+ height: 100%;
658
+ box-sizing: border-box;
659
+ }
660
+
661
+ .combobox__group-sticky-overlay {
662
+ position: sticky;
663
+ top: 0;
664
+ z-index: 2;
665
+ pointer-events: none;
666
+ }
667
+ .combobox__group-sticky-overlay .combobox__group-header {
668
+ box-shadow: 0 1px 0 0 var(--nile-colors-neutral-300, var(--ng-colors-border-secondary));
669
+ }
670
+
671
+ .combobox__group-header {
672
+ display: flex;
673
+ align-items: center;
674
+ gap: 6px;
675
+ box-sizing: border-box;
676
+ width: 100%;
677
+ height: 100%;
678
+ /* Match the listbox's natural inline padding so the header sits like a
679
+ * section divider, edge-to-edge with options. Nested groups indent by
680
+ * depth. Override with --combobox-group-header-indent if needed. */
681
+ padding-block: 8px;
682
+ padding-inline-end: var(--nile-spacing-lg, var(--ng-spacing-lg));
683
+ padding-inline-start: calc(var(--combobox-group-header-indent, var(--nile-spacing-lg, var(--ng-spacing-lg))) + var(--group-depth, 0) * 16px);
684
+ font-family: var(--nile-font-family-sans-serif, var(--ng-font-family-body));
685
+ font-size: var(--nile-type-scale-2, var(--ng-font-size-text-xs));
686
+ font-weight: var(--nile-font-weight-semibold, var(--ng-font-weight-semibold));
687
+ line-height: 1;
688
+ text-transform: uppercase;
689
+ letter-spacing: 0.06em;
690
+ color: var(--nile-colors-dark-500, var(--ng-colors-text-tertiary-600));
691
+ background: var(--nile-colors-neutral-100, var(--ng-colors-bg-secondary));
692
+ border-bottom: 1px solid var(--nile-colors-neutral-300, var(--ng-colors-border-secondary));
693
+ pointer-events: none;
694
+ user-select: none;
695
+ }
696
+
697
+ .combobox__group-label {
698
+ flex: 1;
699
+ min-width: 0;
700
+ overflow: hidden;
701
+ text-overflow: ellipsis;
702
+ white-space: nowrap;
703
+ }
704
+
705
+ .combobox__group-prefix {
706
+ flex-shrink: 0;
707
+ color: inherit;
708
+ }
709
+
637
710
  /* ── Help / Error ── */
638
711
 
639
712
  .form-control__help-text {
@@ -1 +1 @@
1
- {"version":3,"file":"nile-combobox.css.js","sourceRoot":"","sources":["../../../src/nile-combobox/nile-combobox.css.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B,MAAM,CAAC,MAAM,MAAM,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAioBxB,CAAC;AAEF,eAAe,CAAC,MAAM,CAAC,CAAC","sourcesContent":["/**\n * Copyright Aquera Inc 2025\n *\n * This source code is licensed under the BSD-3-Clause license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\nimport { css } from 'lit';\n\nexport const styles = css`\n :host {\n display: block;\n box-sizing: border-box;\n -webkit-font-smoothing: var(--nile-webkit-font-smoothing, var(--ng-webkit-font-smoothing));\n -moz-osx-font-smoothing: var(--nile-moz-osx-font-smoothing, var(--ng-moz-osx-font-smoothing));\n text-rendering: var(--nile-text-rendering, var(--ng-text-rendering));\n }\n\n :host *,\n :host *::before,\n :host *::after {\n box-sizing: inherit;\n }\n\n [hidden] {\n display: none;\n }\n\n /* ── Form control ── */\n\n .form-control .form-control__label {\n display: none;\n }\n\n .form-control--has-label .form-control__label {\n display: block;\n margin-bottom: var(--nile-spacing-sm, var(--ng-spacing-sm));\n color: var(--nile-colors-dark-900, var(--ng-colors-text-secondary-700));\n font-family: var(--nile-font-family-serif, var(--ng-font-family-body));\n font-size: var(--nile-type-scale-3, var(--ng-font-size-text-md));\n font-style: normal;\n font-weight: var(--nile-font-weight-regular, var(--ng-font-weight-medium));\n line-height: var(--nile-spacing-2xl, var(--ng-line-height-text-sm));\n letter-spacing: 0.2px;\n }\n\n .form-control--has-label.form-control--medium .form-control__label {\n font-size: var(--nile-type-scale-3, var(--ng-font-size-text-sm));\n }\n\n :host([required]) .form-control--has-label .form-control__label::after {\n content: '*';\n margin-inline-start: -2px;\n color: var(--nile-colors-red-700, var(--ng-colors-text-error-primary-600));\n }\n\n :host([disabled]) .form-control--has-label .form-control__label {\n user-select: none;\n -webkit-user-select: none;\n }\n\n /* ── Popup ── */\n\n .combobox-popup {\n flex: 1 1 auto;\n display: inline-flex;\n width: 100%;\n position: relative;\n vertical-align: middle;\n }\n\n .combobox-popup::part(popup) {\n z-index: 9999;\n }\n\n .combobox-popup[data-current-placement^='top']::part(popup) {\n transform-origin: bottom;\n }\n\n .combobox-popup[data-current-placement^='bottom']::part(popup) {\n transform-origin: top;\n }\n\n /* ── Trigger: outer row that never scrolls ── */\n\n .combobox__trigger {\n display: flex;\n align-items: center;\n width: 100%;\n min-width: 0;\n position: relative;\n font-family: var(--nile-font-family-sans-serif, var(--ng-font-family-body));\n font-weight: var(--nile-font-weight-regular, var(--ng-font-weight-medium));\n letter-spacing: normal;\n cursor: text;\n transition: 150ms color, 150ms border, 150ms box-shadow, 150ms background-color;\n background-color: var(--nile-colors-white-base, var(--ng-colors-bg-primary));\n border: solid 1px var(--nile-colors-neutral-500, var(--ng-colors-border-primary));\n outline: none;\n }\n\n .combobox__trigger:hover {\n border-color: var(--nile-colors-dark-900, var(--ng-colors-border-neutral));\n }\n\n .combobox--open .combobox__trigger,\n .combobox--focused .combobox__trigger {\n border-color: var(--ng-colors-border-brand);\n box-shadow: 0 0 0 1px var(--ng-colors-border-brand) inset;\n background-color: var(--nile-colors-neutral-100, var(--ng-colors-bg-secondary));\n }\n\n .combobox--disabled .combobox__trigger {\n background-color: var(--nile-colors-dark-200, var(--ng-colors-bg-disabled-subtle));\n border-color: var(--nile-colors-neutral-500, var(--ng-colors-border-primary));\n color: var(--nile-colors-dark-500, var(--ng-colors-text-disabled));\n cursor: not-allowed;\n user-select: none;\n }\n\n .combobox--warning .combobox__trigger {\n border-color: var(--nile-colors-yellow-500, var(--ng-colors-border-warning));\n }\n\n .combobox--error .combobox__trigger {\n border-color: var(--nile-colors-red-500, var(--ng-colors-border-error));\n }\n\n .combobox--success .combobox__trigger {\n border-color: var(--nile-colors-green-500, var(--ng-componentcolors-utility-success-500));\n }\n\n /* Size: small */\n .combobox--small .combobox__trigger {\n border-radius: var(--nile-radius-sm, var(--ng-radius-md));\n font-size: var(--nile-type-scale-2, var(--ng-font-size-text-xs));\n padding: var(--nile-spacing-md, var(--ng-spacing-sm)) var(--nile-spacing-md, var(--ng-spacing-md));\n min-height: var(--nile-height-32px, var(--ng-height-32px));\n }\n\n /* Size: medium */\n .combobox--medium .combobox__trigger {\n border-radius: var(--nile-radius-sm, var(--ng-radius-md));\n font-size: var(--nile-type-scale-3, var(--ng-font-size-text-sm));\n padding: var(--nile-spacing-5px, var(--ng-spacing-md)) var(--nile-spacing-10px, var(--ng-spacing-lg));\n min-height: var(--nile-height-40px, var(--ng-height-40px));\n }\n\n /* Size: large */\n .combobox--large .combobox__trigger {\n border-radius: var(--nile-radius-sm, var(--ng-radius-md));\n font-size: var(--nile-type-scale-4, var(--ng-font-size-text-md));\n padding: var(--nile-spacing-xl, var(--ng-spacing-lg)) var(--nile-spacing-xl, var(--ng-spacing-xl));\n min-height: var(--nile-height-48px, var(--ng-height-48px));\n }\n\n .combobox--pill .combobox__trigger {\n border-radius: var(--nile-radius-3xl, var(--ng-radius-3xl));\n }\n\n .combobox--filled .combobox__trigger {\n border: none;\n background-color: var(--nile-colors-neutral-100, var(--ng-colors-bg-secondary));\n }\n\n .combobox--filled.combobox--open .combobox__trigger,\n .combobox--filled.combobox--focused .combobox__trigger {\n outline: 3px solid rgba(0, 89, 255, 0.4);\n }\n\n /* ── Inner area: holds tags + input ── */\n\n .combobox__scroll-area {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 4px;\n overflow: hidden;\n }\n\n /* Single-line: horizontal scroll for overflowing tags */\n .combobox__scroll-area--single-line {\n flex-wrap: nowrap;\n overflow-x: auto;\n overflow-y: hidden;\n -webkit-overflow-scrolling: touch;\n scrollbar-width: thin;\n scrollbar-color: var(--nile-colors-neutral-500, var(--ng-colors-border-primary)) transparent;\n }\n\n .combobox__scroll-area--single-line::-webkit-scrollbar {\n height: 3px;\n }\n\n .combobox__scroll-area--single-line::-webkit-scrollbar-track {\n background: transparent;\n }\n\n .combobox__scroll-area--single-line::-webkit-scrollbar-thumb {\n background: var(--nile-colors-neutral-500, var(--ng-colors-bg-quaternary));\n border-radius: 3px;\n }\n\n .combobox__scroll-area--single-line::-webkit-scrollbar-thumb:hover {\n background: var(--nile-colors-dark-500, var(--ng-colors-fg-quaternary-400));\n }\n\n /* Wrap: tags flow to multiple lines, trigger grows in height */\n .combobox__scroll-area--wrap {\n flex-wrap: wrap;\n overflow: visible;\n }\n\n /* ── Tags inside the scroll area ── */\n\n .combobox__scroll-area nile-tag {\n cursor: pointer;\n flex-shrink: 0;\n }\n\n .combobox--disabled .combobox__scroll-area nile-tag {\n cursor: not-allowed;\n }\n\n .combobox__tags-count {\n color: var(--nile-colors-primary-600, var(--ng-colors-text-brand-secondary-700));\n font-family: var(--nile-font-family-serif, var(--ng-font-family-body));\n font-size: var(--nile-type-scale-3, var(--ng-font-size-text-sm));\n font-weight: var(--nile-font-weight-regular, var(--ng-font-weight-medium));\n line-height: var(--nile-line-height-xsmall, var(--ng-line-height-text-sm));\n letter-spacing: 0.2px;\n white-space: nowrap;\n flex-shrink: 0;\n }\n\n /* ── Input wrapper inside scroll area ── */\n\n .combobox__input-wrapper {\n flex: 1;\n min-width: 60px;\n display: flex;\n align-items: center;\n }\n\n .combobox__input {\n width: 100%;\n font: inherit;\n border: none;\n background: none;\n color: var(--nile-colors-dark-900, var(--ng-colors-text-primary-900));\n cursor: inherit;\n overflow: hidden;\n padding: 0;\n margin: 0;\n -webkit-appearance: none;\n font-family: var(--nile-font-family-serif, var(--ng-font-family-body));\n font-size: var(--nile-type-scale-3, var(--ng-font-size-text-md));\n font-weight: var(--nile-font-weight-regular, var(--ng-font-weight-regular));\n text-overflow: ellipsis;\n line-height: var(--nile-spacing-2xl, var(--ng-line-height-text-sm));\n }\n\n .combobox__input::placeholder {\n font-family: var(--nile-font-family-serif, var(--ng-font-family-body));\n color: var(--nile-colors-text-placeholder, var(--ng-colors-text-placeholder));\n }\n\n .combobox__input:focus {\n outline: none;\n }\n\n .combobox--disabled .combobox__input {\n color: var(--nile-colors-dark-500, var(--ng-colors-text-disabled));\n pointer-events: none;\n }\n\n /* ── Hidden form value input ── */\n\n .combobox__value-input {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n padding: 0;\n margin: 0;\n opacity: 0;\n z-index: -1;\n }\n\n /* ── Action icons (clear, expand) ── */\n\n .combobox__actions {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n gap: var(--nile-spacing-sm, var(--ng-spacing-sm));\n margin-inline-start: auto;\n }\n\n .combobox__clear {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: none;\n background: none;\n padding: 0;\n cursor: pointer;\n color: var(--nile-colors-dark-500, var(--ng-colors-fg-quaternary-400));\n transition: 150ms color;\n }\n\n .combobox__clear:hover {\n color: var(--nile-colors-dark-900, var(--ng-colors-text-primary-900));\n }\n\n .combobox__clear:focus {\n outline: none;\n }\n\n .combobox__expand-icon {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n transition: 250ms rotate ease;\n rotate: 0;\n }\n\n .combobox--open .combobox__expand-icon {\n rotate: -180deg;\n }\n\n /* ── Prefix / suffix ── */\n\n .combobox__prefix,\n .combobox__suffix {\n flex: 0;\n display: inline-flex;\n align-items: center;\n color: var(--nile-colors-dark-500, var(--ng-colors-text-tertiary-600));\n }\n\n .combobox__prefix {\n margin-inline-end: var(--nile-spacing-md, var(--ng-spacing-md));\n }\n\n /* ── Listbox (dropdown) ── */\n\n .combobox__listbox {\n display: block;\n position: relative;\n font-size: var(--nile-type-scale-3, var(--ng-font-size-text-md));\n font-weight: var(--nile-font-weight-regular, var(--ng-font-weight-regular));\n background: var(--nile-colors-white-base, var(--ng-colors-bg-primary));\n border: solid 1px var(--nile-colors-neutral-500, var(--ng-colors-border-secondary-alt));\n border-radius: var(--nile-radius-sm, var(--ng-radius-md));\n padding-block: var(--nile-spacing-none, var(--ng-spacing-xs));\n padding-inline: var(--nile-spacing-none, var(--ng-spacing-xs));\n overscroll-behavior: none;\n overflow-y: auto;\n max-width: var(--auto-size-available-width);\n max-height: var(--auto-size-available-height);\n box-shadow: 0px 2px 4px 0px rgba(119, 125, 130, 0.15);\n }\n\n /* ── Options ── */\n\n .combobox__options {\n font-size: var(--nile-type-scale-3, var(--ng-font-size-text-sm));\n color: rgb(133, 129, 129);\n width: 100%;\n }\n\n .combobox__options.loading {\n opacity: 0.5;\n pointer-events: none;\n }\n\n .combobox__no-results {\n padding: var(--nile-spacing-lg, var(--ng-spacing-lg));\n font-family: var(--nile-font-family-serif, var(--ng-font-family-body));\n font-size: var(--nile-type-scale-3, var(--ng-font-size-text-sm));\n color: var(--nile-colors-dark-500, var(--ng-colors-text-tertiary-600));\n text-align: center;\n }\n\n .combobox__no-results-title {\n font-weight: var(--nile-font-weight-medium, var(--ng-font-weight-medium));\n color: var(--nile-colors-dark-700, var(--ng-colors-text-secondary-700));\n }\n\n .combobox__no-results-subtitle {\n margin-top: var(--nile-spacing-xs, var(--ng-spacing-xs));\n font-size: var(--nile-type-scale-2, var(--ng-font-size-text-xs));\n color: var(--nile-colors-dark-500, var(--ng-colors-text-tertiary-600));\n }\n\n /* ── Options (both virtual and plain) ── */\n\n .combobox__options nile-option {\n width: 100%;\n display: block;\n }\n\n .combobox__options nile-option[selected] {\n background-color: var(--nile-colors-neutral-100, var(--ng-colors-bg-secondary));\n }\n\n .combobox__options nile-option[selected]::part(base) {\n color: var(--nile-colors-primary-600, var(--ng-colors-text-brand-secondary-700));\n }\n\n .combobox__options-plain {\n display: flex;\n flex-direction: column;\n }\n\n .combobox__options-plain nile-option {\n flex-shrink: 0;\n }\n\n /* ── Top actions (Select All + Selected/All filter toggle) ── */\n\n .combobox__top-actions {\n position: sticky;\n top: 0;\n z-index: 2;\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: space-between;\n gap: var(--nile-spacing-lg, var(--ng-spacing-lg));\n background: var(--nile-colors-neutral-100, var(--ng-colors-bg-secondary));\n border-bottom: 1px solid var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));\n padding: var(--nile-spacing-lg, var(--ng-spacing-lg));\n user-select: none;\n }\n\n .combobox__top-actions--disabled {\n opacity: 0.6;\n pointer-events: none;\n }\n\n .combobox__select-all {\n display: flex;\n align-items: center;\n gap: var(--nile-spacing-md, var(--ng-spacing-md));\n cursor: pointer;\n flex: 1 1 auto;\n font-family: var(--nile-font-family-serif, var(--ng-font-family-body));\n font-size: var(--nile-type-scale-3, var(--ng-font-size-text-sm));\n line-height: 14px;\n letter-spacing: 0.2px;\n color: var(--nile-colors-dark-900, var(--ng-colors-text-primary));\n }\n\n .combobox__show-toggle {\n display: inline-flex;\n align-items: center;\n gap: var(--nile-spacing-md, var(--ng-spacing-md));\n border: none;\n background: none;\n padding: 0;\n border-radius: var(--nile-radius-sm, var(--ng-radius-sm));\n cursor: pointer;\n color: var(--nile-colors-primary-600, var(--ng-colors-text-brand-secondary-700));\n font-family: var(--nile-font-family-serif, var(--ng-font-family-body));\n font-size: var(--nile-type-scale-3, var(--ng-font-size-text-sm));\n line-height: 14px;\n letter-spacing: 0.2px;\n white-space: nowrap;\n }\n\n .combobox__show-toggle[disabled] {\n color: var(--nile-colors-primary-500, var(--ng-colors-fg-quaternary-400));\n cursor: not-allowed;\n }\n\n /* ── Footer ── */\n\n .combobox__footer {\n position: sticky;\n bottom: 0;\n background: var(--nile-colors-neutral-100, var(--ng-colors-bg-secondary));\n border-top: 1px solid var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));\n display: flex;\n flex-direction: row;\n align-items: flex-start;\n padding: var(--nile-spacing-md, var(--ng-spacing-md)) var(--nile-spacing-lg, var(--ng-spacing-lg)) var(--nile-spacing-xl, var(--ng-spacing-xl));\n gap: var(--nile-spacing-lg, var(--ng-spacing-lg));\n justify-content: space-between;\n }\n\n .combobox__footer.loading {\n opacity: 0.5;\n pointer-events: none;\n }\n\n .combobox__footer-clear {\n display: inline-flex;\n align-items: center;\n color: var(--nile-colors-primary-600, var(--ng-colors-text-brand-secondary-700));\n border: none;\n background: none;\n padding: 0;\n cursor: pointer;\n font-family: var(--nile-font-family-serif, var(--ng-font-family-body));\n font-size: var(--nile-type-scale-3, var(--ng-font-size-text-sm));\n letter-spacing: 0.2px;\n }\n\n .combobox__footer-clear:hover {\n text-decoration: underline;\n }\n\n /* ── Loader ── */\n\n .combobox__loader {\n width: 100%;\n text-align: center;\n display: block;\n }\n\n .combobox__loader--icon {\n margin-top: var(--nile-spacing-xl, var(--ng-spacing-xl));\n animation: combobox-spin 0.6s linear infinite;\n }\n\n .combobox__loader--center {\n position: absolute;\n display: flex;\n justify-content: center;\n align-items: center;\n width: 100%;\n height: 75%;\n }\n\n @keyframes combobox-spin {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n }\n\n .combobox__add-option::part(base) {\n color: var(--nile-colors-primary-600, var(--ng-colors-text-brand-secondary-700));\n }\n\n /* ── Grid layout ── */\n\n :host([grid-columns]) .combobox__listbox {\n overflow-y: auto;\n }\n\n .combobox__grid-row {\n width: 100%;\n }\n\n .combobox__grid-row nile-option {\n min-width: 0;\n overflow: hidden;\n }\n\n .combobox__grid-row nile-option::part(base) {\n border-radius: var(--nile-radius-xs, var(--ng-radius-sm));\n }\n\n /* ── Bidirectional grid layout (vertical + horizontal scroll) ── */\n\n .combobox__listbox.combobox__listbox--bidirectional {\n overflow: auto;\n max-width: none;\n scrollbar-width: thin;\n scrollbar-color: var(--nile-colors-neutral-500, var(--ng-colors-bg-quaternary)) transparent;\n }\n\n .combobox__listbox--bidirectional .combobox__options {\n width: max-content;\n min-width: 100%;\n }\n\n .combobox__listbox--bidirectional .combobox__grid-row {\n width: max-content;\n min-width: 100%;\n }\n\n /* ── Horizontal grid layout ── */\n\n .combobox__listbox.combobox__listbox--horizontal {\n overflow-x: auto;\n overflow-y: hidden;\n max-height: none;\n max-width: none;\n width: 100%;\n scrollbar-width: thin;\n scrollbar-color: var(--nile-colors-neutral-500, var(--ng-colors-bg-quaternary)) transparent;\n }\n\n .combobox__listbox--horizontal::-webkit-scrollbar {\n height: 6px;\n }\n\n .combobox__listbox--horizontal::-webkit-scrollbar-track {\n background: transparent;\n }\n\n .combobox__listbox--horizontal::-webkit-scrollbar-thumb {\n background: var(--nile-colors-neutral-500, var(--ng-colors-bg-quaternary));\n border-radius: 3px;\n }\n\n .combobox__listbox--horizontal::-webkit-scrollbar-thumb:hover {\n background: var(--nile-colors-dark-500, var(--ng-colors-fg-quaternary-400));\n }\n\n .combobox__options--horizontal {\n display: block;\n width: max-content;\n min-width: 100%;\n }\n\n .combobox__grid-col {\n border-right: 1px solid var(--nile-colors-neutral-200, var(--ng-colors-border-secondary));\n }\n\n .combobox__grid-col:last-child {\n border-right: none;\n }\n\n .combobox__grid-col nile-option {\n width: 100%;\n display: block;\n }\n\n .combobox__grid-col nile-option::part(base) {\n border-radius: 0;\n }\n\n /* ── Help / Error ── */\n\n .form-control__help-text {\n display: none;\n }\n\n .form-control--has-help-text .form-control__help-text {\n display: block;\n color: var(--nile-colors-dark-500, var(--ng-colors-text-tertiary-600));\n margin-top: var(--nile-spacing-lg, var(--ng-spacing-lg));\n font-size: var(--nile-type-scale-3, var(--ng-font-size-text-sm));\n }\n`;\n\nexport default [styles];\n"]}
1
+ {"version":3,"file":"nile-combobox.css.js","sourceRoot":"","sources":["../../../src/nile-combobox/nile-combobox.css.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B,MAAM,CAAC,MAAM,MAAM,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0sBxB,CAAC;AAEF,eAAe,CAAC,MAAM,CAAC,CAAC","sourcesContent":["/**\n * Copyright Aquera Inc 2025\n *\n * This source code is licensed under the BSD-3-Clause license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\nimport { css } from 'lit';\n\nexport const styles = css`\n :host {\n display: block;\n box-sizing: border-box;\n -webkit-font-smoothing: var(--nile-webkit-font-smoothing, var(--ng-webkit-font-smoothing));\n -moz-osx-font-smoothing: var(--nile-moz-osx-font-smoothing, var(--ng-moz-osx-font-smoothing));\n text-rendering: var(--nile-text-rendering, var(--ng-text-rendering));\n }\n\n :host *,\n :host *::before,\n :host *::after {\n box-sizing: inherit;\n }\n\n [hidden] {\n display: none;\n }\n\n /* ── Form control ── */\n\n .form-control .form-control__label {\n display: none;\n }\n\n .form-control--has-label .form-control__label {\n display: block;\n margin-bottom: var(--nile-spacing-sm, var(--ng-spacing-sm));\n color: var(--nile-colors-dark-900, var(--ng-colors-text-secondary-700));\n font-family: var(--nile-font-family-serif, var(--ng-font-family-body));\n font-size: var(--nile-type-scale-3, var(--ng-font-size-text-md));\n font-style: normal;\n font-weight: var(--nile-font-weight-regular, var(--ng-font-weight-medium));\n line-height: var(--nile-spacing-2xl, var(--ng-line-height-text-sm));\n letter-spacing: 0.2px;\n }\n\n .form-control--has-label.form-control--medium .form-control__label {\n font-size: var(--nile-type-scale-3, var(--ng-font-size-text-sm));\n }\n\n :host([required]) .form-control--has-label .form-control__label::after {\n content: '*';\n margin-inline-start: -2px;\n color: var(--nile-colors-red-700, var(--ng-colors-text-error-primary-600));\n }\n\n :host([disabled]) .form-control--has-label .form-control__label {\n user-select: none;\n -webkit-user-select: none;\n }\n\n /* ── Popup ── */\n\n .combobox-popup {\n flex: 1 1 auto;\n display: inline-flex;\n width: 100%;\n position: relative;\n vertical-align: middle;\n }\n\n .combobox-popup::part(popup) {\n z-index: 9999;\n }\n\n .combobox-popup[data-current-placement^='top']::part(popup) {\n transform-origin: bottom;\n }\n\n .combobox-popup[data-current-placement^='bottom']::part(popup) {\n transform-origin: top;\n }\n\n /* ── Trigger: outer row that never scrolls ── */\n\n .combobox__trigger {\n display: flex;\n align-items: center;\n width: 100%;\n min-width: 0;\n position: relative;\n font-family: var(--nile-font-family-sans-serif, var(--ng-font-family-body));\n font-weight: var(--nile-font-weight-regular, var(--ng-font-weight-medium));\n letter-spacing: normal;\n cursor: text;\n transition: 150ms color, 150ms border, 150ms box-shadow, 150ms background-color;\n background-color: var(--nile-colors-white-base, var(--ng-colors-bg-primary));\n border: solid 1px var(--nile-colors-neutral-500, var(--ng-colors-border-primary));\n outline: none;\n }\n\n .combobox__trigger:hover {\n border-color: var(--nile-colors-dark-900, var(--ng-colors-border-neutral));\n }\n\n .combobox--open .combobox__trigger,\n .combobox--focused .combobox__trigger {\n border-color: var(--ng-colors-border-brand);\n box-shadow: 0 0 0 1px var(--ng-colors-border-brand) inset;\n background-color: var(--nile-colors-neutral-100, var(--ng-colors-bg-secondary));\n }\n\n .combobox--disabled .combobox__trigger {\n background-color: var(--nile-colors-dark-200, var(--ng-colors-bg-disabled-subtle));\n border-color: var(--nile-colors-neutral-500, var(--ng-colors-border-primary));\n color: var(--nile-colors-dark-500, var(--ng-colors-text-disabled));\n cursor: not-allowed;\n user-select: none;\n }\n\n .combobox--warning .combobox__trigger {\n border-color: var(--nile-colors-yellow-500, var(--ng-colors-border-warning));\n }\n\n .combobox--error .combobox__trigger {\n border-color: var(--nile-colors-red-500, var(--ng-colors-border-error));\n }\n\n .combobox--success .combobox__trigger {\n border-color: var(--nile-colors-green-500, var(--ng-componentcolors-utility-success-500));\n }\n\n /* Size: small */\n .combobox--small .combobox__trigger {\n border-radius: var(--nile-radius-sm, var(--ng-radius-md));\n font-size: var(--nile-type-scale-2, var(--ng-font-size-text-xs));\n padding: var(--nile-spacing-md, var(--ng-spacing-sm)) var(--nile-spacing-md, var(--ng-spacing-md));\n min-height: var(--nile-height-32px, var(--ng-height-32px));\n }\n\n /* Size: medium */\n .combobox--medium .combobox__trigger {\n border-radius: var(--nile-radius-sm, var(--ng-radius-md));\n font-size: var(--nile-type-scale-3, var(--ng-font-size-text-md));\n padding: var(--nile-spacing-lg, var(--ng-spacing-md)) var(--nile-spacing-lg, var(--ng-spacing-lg));\n min-height: var(--nile-height-40px, var(--ng-height-40px));\n box-sizing: border-box;\n }\n\n /* Size: large */\n .combobox--large .combobox__trigger {\n border-radius: var(--nile-radius-sm, var(--ng-radius-md));\n font-size: var(--nile-type-scale-4, var(--ng-font-size-text-md));\n padding: var(--nile-spacing-xl, var(--ng-spacing-lg)) var(--nile-spacing-xl, var(--ng-spacing-xl));\n min-height: var(--nile-height-48px, var(--ng-height-48px));\n }\n\n .combobox--pill .combobox__trigger {\n border-radius: var(--nile-radius-3xl, var(--ng-radius-3xl));\n }\n\n .combobox--filled .combobox__trigger {\n border: none;\n background-color: var(--nile-colors-neutral-100, var(--ng-colors-bg-secondary));\n }\n\n .combobox--filled.combobox--open .combobox__trigger,\n .combobox--filled.combobox--focused .combobox__trigger {\n outline: 3px solid rgba(0, 89, 255, 0.4);\n }\n\n /* ── Inner area: holds tags + input ── */\n\n .combobox__scroll-area {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 4px;\n overflow: hidden;\n }\n\n /* Single-line: horizontal scroll for overflowing tags */\n .combobox__scroll-area--single-line {\n flex-wrap: nowrap;\n overflow-x: auto;\n overflow-y: hidden;\n -webkit-overflow-scrolling: touch;\n scrollbar-width: thin;\n scrollbar-color: var(--nile-colors-neutral-500, var(--ng-colors-border-primary)) transparent;\n }\n\n .combobox__scroll-area--single-line::-webkit-scrollbar {\n height: 3px;\n }\n\n .combobox__scroll-area--single-line::-webkit-scrollbar-track {\n background: transparent;\n }\n\n .combobox__scroll-area--single-line::-webkit-scrollbar-thumb {\n background: var(--nile-colors-neutral-500, var(--ng-colors-bg-quaternary));\n border-radius: 3px;\n }\n\n .combobox__scroll-area--single-line::-webkit-scrollbar-thumb:hover {\n background: var(--nile-colors-dark-500, var(--ng-colors-fg-quaternary-400));\n }\n\n /* Wrap: tags flow to multiple lines, trigger grows in height */\n .combobox__scroll-area--wrap {\n flex-wrap: wrap;\n overflow: visible;\n }\n\n /* ── Tags inside the scroll area ── */\n\n .combobox__scroll-area nile-tag {\n cursor: pointer;\n flex-shrink: 0;\n }\n\n .combobox--disabled .combobox__scroll-area nile-tag {\n cursor: not-allowed;\n }\n\n .combobox__tags-count {\n color: var(--nile-colors-primary-600, var(--ng-colors-text-brand-secondary-700));\n font-family: var(--nile-font-family-serif, var(--ng-font-family-body));\n font-size: var(--nile-type-scale-3, var(--ng-font-size-text-sm));\n font-weight: var(--nile-font-weight-regular, var(--ng-font-weight-medium));\n line-height: var(--nile-line-height-xsmall, var(--ng-line-height-text-sm));\n letter-spacing: 0.2px;\n white-space: nowrap;\n flex-shrink: 0;\n }\n\n /* ── Input wrapper inside scroll area ── */\n\n .combobox__input-wrapper {\n flex: 1;\n min-width: 60px;\n display: flex;\n align-items: center;\n }\n\n .combobox__input {\n width: 100%;\n font: inherit;\n border: none;\n background: none;\n color: var(--nile-colors-dark-900, var(--ng-colors-text-primary-900));\n cursor: inherit;\n overflow: hidden;\n padding: 0;\n margin: 0;\n -webkit-appearance: none;\n font-family: var(--nile-font-family-serif, var(--ng-font-family-body));\n font-size: inherit;\n font-weight: var(--nile-font-weight-regular, var(--ng-font-weight-regular));\n text-overflow: ellipsis;\n line-height: var(--nile-line-height-xsmall, var(--ng-line-height-text-sm));\n }\n\n .combobox__input::placeholder {\n font-family: var(--nile-font-family-serif, var(--ng-font-family-body));\n color: var(--nile-colors-text-placeholder, var(--ng-colors-text-placeholder));\n }\n\n .combobox__input:focus {\n outline: none;\n }\n\n .combobox--disabled .combobox__input {\n color: var(--nile-colors-dark-500, var(--ng-colors-text-disabled));\n pointer-events: none;\n }\n\n /* ── Hidden form value input ── */\n\n .combobox__value-input {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n padding: 0;\n margin: 0;\n opacity: 0;\n z-index: -1;\n }\n\n /* ── Action icons (clear, expand) ── */\n\n .combobox__actions {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n gap: var(--nile-spacing-sm, var(--ng-spacing-sm));\n margin-inline-start: auto;\n }\n\n .combobox__clear {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: none;\n background: none;\n padding: 0;\n cursor: pointer;\n color: var(--nile-colors-dark-500, var(--ng-colors-fg-quaternary-400));\n transition: 150ms color;\n }\n\n .combobox__clear:hover {\n color: var(--nile-colors-dark-900, var(--ng-colors-text-primary-900));\n }\n\n .combobox__clear:focus {\n outline: none;\n }\n\n .combobox__expand-icon {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n transition: 250ms rotate ease;\n rotate: 0;\n }\n\n .combobox--open .combobox__expand-icon {\n rotate: -180deg;\n }\n\n /* ── Prefix / suffix ── */\n\n .combobox__prefix,\n .combobox__suffix {\n flex: 0;\n display: inline-flex;\n align-items: center;\n color: var(--nile-colors-dark-500, var(--ng-colors-text-tertiary-600));\n }\n\n .combobox__prefix {\n margin-inline-end: var(--nile-spacing-md, var(--ng-spacing-md));\n }\n\n /* ── Listbox (dropdown) ── */\n\n .combobox__listbox {\n display: block;\n position: relative;\n font-size: var(--nile-type-scale-3, var(--ng-font-size-text-md));\n font-weight: var(--nile-font-weight-regular, var(--ng-font-weight-regular));\n background: var(--nile-colors-white-base, var(--ng-colors-bg-primary));\n border: solid 1px var(--nile-colors-neutral-500, var(--ng-colors-border-secondary-alt));\n border-radius: var(--nile-radius-sm, var(--ng-radius-md));\n padding-block: var(--nile-spacing-none, var(--ng-spacing-xs));\n padding-inline: var(--nile-spacing-none, var(--ng-spacing-xs));\n overscroll-behavior: none;\n overflow-y: auto;\n max-width: var(--auto-size-available-width);\n max-height: var(--auto-size-available-height);\n box-shadow: 0px 2px 4px 0px rgba(119, 125, 130, 0.15);\n }\n\n /* ── Options ── */\n\n .combobox__options {\n font-size: var(--nile-type-scale-3, var(--ng-font-size-text-sm));\n color: rgb(133, 129, 129);\n width: 100%;\n }\n\n .combobox__options.loading {\n opacity: 0.5;\n pointer-events: none;\n }\n\n .combobox__no-results {\n padding: var(--nile-spacing-lg, var(--ng-spacing-lg));\n font-family: var(--nile-font-family-serif, var(--ng-font-family-body));\n font-size: var(--nile-type-scale-3, var(--ng-font-size-text-sm));\n color: var(--nile-colors-dark-500, var(--ng-colors-text-tertiary-600));\n text-align: center;\n }\n\n .combobox__no-results-title {\n font-weight: var(--nile-font-weight-medium, var(--ng-font-weight-medium));\n color: var(--nile-colors-dark-700, var(--ng-colors-text-secondary-700));\n }\n\n .combobox__no-results-subtitle {\n margin-top: var(--nile-spacing-xs, var(--ng-spacing-xs));\n font-size: var(--nile-type-scale-2, var(--ng-font-size-text-xs));\n color: var(--nile-colors-dark-500, var(--ng-colors-text-tertiary-600));\n }\n\n /* ── Options (both virtual and plain) ── */\n\n .combobox__options nile-option {\n width: 100%;\n display: block;\n }\n\n .combobox__options nile-option[selected] {\n background-color: var(--nile-colors-neutral-100, var(--ng-colors-bg-secondary));\n }\n\n .combobox__options nile-option[selected]::part(base) {\n color: var(--nile-colors-primary-600, var(--ng-colors-text-brand-secondary-700));\n }\n\n .combobox__options-plain {\n display: flex;\n flex-direction: column;\n }\n\n .combobox__options-plain nile-option {\n flex-shrink: 0;\n }\n\n /* ── Top actions (Select All + Selected/All filter toggle) ── */\n\n .combobox__top-actions {\n position: sticky;\n top: 0;\n z-index: 2;\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: space-between;\n gap: var(--nile-spacing-lg, var(--ng-spacing-lg));\n background: var(--nile-colors-neutral-100, var(--ng-colors-bg-secondary));\n border-bottom: 1px solid var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));\n padding: var(--nile-spacing-lg, var(--ng-spacing-lg));\n user-select: none;\n }\n\n .combobox__top-actions--disabled {\n opacity: 0.6;\n pointer-events: none;\n }\n\n .combobox__select-all {\n display: flex;\n align-items: center;\n gap: var(--nile-spacing-md, var(--ng-spacing-md));\n cursor: pointer;\n flex: 1 1 auto;\n font-family: var(--nile-font-family-serif, var(--ng-font-family-body));\n font-size: var(--nile-type-scale-3, var(--ng-font-size-text-sm));\n line-height: 14px;\n letter-spacing: 0.2px;\n color: var(--nile-colors-dark-900, var(--ng-colors-text-primary));\n }\n\n .combobox__show-toggle {\n display: inline-flex;\n align-items: center;\n gap: var(--nile-spacing-md, var(--ng-spacing-md));\n border: none;\n background: none;\n padding: 0;\n border-radius: var(--nile-radius-sm, var(--ng-radius-sm));\n cursor: pointer;\n color: var(--nile-colors-primary-600, var(--ng-colors-text-brand-secondary-700));\n font-family: var(--nile-font-family-serif, var(--ng-font-family-body));\n font-size: var(--nile-type-scale-3, var(--ng-font-size-text-sm));\n line-height: 14px;\n letter-spacing: 0.2px;\n white-space: nowrap;\n }\n\n .combobox__show-toggle[disabled] {\n color: var(--nile-colors-primary-500, var(--ng-colors-fg-quaternary-400));\n cursor: not-allowed;\n }\n\n /* ── Footer ── */\n\n .combobox__footer {\n position: sticky;\n bottom: 0;\n background: var(--nile-colors-neutral-100, var(--ng-colors-bg-secondary));\n border-top: 1px solid var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));\n display: flex;\n flex-direction: row;\n align-items: flex-start;\n padding: var(--nile-spacing-md, var(--ng-spacing-md)) var(--nile-spacing-lg, var(--ng-spacing-lg)) var(--nile-spacing-xl, var(--ng-spacing-xl));\n gap: var(--nile-spacing-lg, var(--ng-spacing-lg));\n justify-content: space-between;\n }\n\n .combobox__footer.loading {\n opacity: 0.5;\n pointer-events: none;\n }\n\n .combobox__footer-clear {\n display: inline-flex;\n align-items: center;\n color: var(--nile-colors-primary-600, var(--ng-colors-text-brand-secondary-700));\n border: none;\n background: none;\n padding: 0;\n cursor: pointer;\n font-family: var(--nile-font-family-serif, var(--ng-font-family-body));\n font-size: var(--nile-type-scale-3, var(--ng-font-size-text-sm));\n letter-spacing: 0.2px;\n }\n\n .combobox__footer-clear:hover {\n text-decoration: underline;\n }\n\n /* ── Loader ── */\n\n .combobox__loader {\n width: 100%;\n text-align: center;\n display: block;\n }\n\n .combobox__loader--icon {\n margin-top: var(--nile-spacing-xl, var(--ng-spacing-xl));\n animation: combobox-spin 0.6s linear infinite;\n }\n\n .combobox__loader--center {\n position: absolute;\n display: flex;\n justify-content: center;\n align-items: center;\n width: 100%;\n height: 75%;\n }\n\n @keyframes combobox-spin {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n }\n\n .combobox__add-option::part(base) {\n color: var(--nile-colors-primary-600, var(--ng-colors-text-brand-secondary-700));\n }\n\n /* ── Grid layout ── */\n\n :host([grid-columns]) .combobox__listbox {\n overflow-y: auto;\n }\n\n .combobox__grid-row {\n width: 100%;\n }\n\n .combobox__grid-row nile-option {\n min-width: 0;\n overflow: hidden;\n }\n\n .combobox__grid-row nile-option::part(base) {\n border-radius: var(--nile-radius-xs, var(--ng-radius-sm));\n }\n\n /* ── Bidirectional grid layout (vertical + horizontal scroll) ── */\n\n .combobox__listbox.combobox__listbox--bidirectional {\n overflow: auto;\n max-width: none;\n scrollbar-width: thin;\n scrollbar-color: var(--nile-colors-neutral-500, var(--ng-colors-bg-quaternary)) transparent;\n }\n\n .combobox__listbox--bidirectional .combobox__options {\n width: max-content;\n min-width: 100%;\n }\n\n .combobox__listbox--bidirectional .combobox__grid-row {\n width: max-content;\n min-width: 100%;\n }\n\n /* ── Horizontal grid layout ── */\n\n .combobox__listbox.combobox__listbox--horizontal {\n overflow-x: auto;\n overflow-y: hidden;\n max-height: none;\n max-width: none;\n width: 100%;\n scrollbar-width: thin;\n scrollbar-color: var(--nile-colors-neutral-500, var(--ng-colors-bg-quaternary)) transparent;\n }\n\n .combobox__listbox--horizontal::-webkit-scrollbar {\n height: 6px;\n }\n\n .combobox__listbox--horizontal::-webkit-scrollbar-track {\n background: transparent;\n }\n\n .combobox__listbox--horizontal::-webkit-scrollbar-thumb {\n background: var(--nile-colors-neutral-500, var(--ng-colors-bg-quaternary));\n border-radius: 3px;\n }\n\n .combobox__listbox--horizontal::-webkit-scrollbar-thumb:hover {\n background: var(--nile-colors-dark-500, var(--ng-colors-fg-quaternary-400));\n }\n\n .combobox__options--horizontal {\n display: block;\n width: max-content;\n min-width: 100%;\n }\n\n .combobox__grid-col {\n border-right: 1px solid var(--nile-colors-neutral-200, var(--ng-colors-border-secondary));\n }\n\n .combobox__grid-col:last-child {\n border-right: none;\n }\n\n .combobox__grid-col nile-option {\n width: 100%;\n display: block;\n }\n\n .combobox__grid-col nile-option::part(base) {\n border-radius: 0;\n }\n\n /* ── Group headers (data-driven grouping) ── */\n\n /* Plain (non-virtualized) listbox: sticky relative to the scroll container.\n * Activated only when the host has [sticky-group-header]. */\n :host([sticky-group-header]) .combobox__options-plain .combobox__group-header {\n position: sticky;\n top: calc(var(--group-depth, 0) * 28px);\n z-index: 1;\n }\n\n /* Virtualized listbox: each row is absolutely positioned by the virtualizer.\n * Sticky doesn't pin against the scroll container because the row wrapper's\n * transform creates a containing block. Instead we render a separate\n * overlay inside the listbox that mirrors the currently-active group\n * header (computed on scroll). */\n .combobox__group-header-slot {\n pointer-events: none;\n }\n .combobox__group-header-slot .combobox__group-header {\n height: 100%;\n box-sizing: border-box;\n }\n\n .combobox__group-sticky-overlay {\n position: sticky;\n top: 0;\n z-index: 2;\n pointer-events: none;\n }\n .combobox__group-sticky-overlay .combobox__group-header {\n box-shadow: 0 1px 0 0 var(--nile-colors-neutral-300, var(--ng-colors-border-secondary));\n }\n\n .combobox__group-header {\n display: flex;\n align-items: center;\n gap: 6px;\n box-sizing: border-box;\n width: 100%;\n height: 100%;\n /* Match the listbox's natural inline padding so the header sits like a\n * section divider, edge-to-edge with options. Nested groups indent by\n * depth. Override with --combobox-group-header-indent if needed. */\n padding-block: 8px;\n padding-inline-end: var(--nile-spacing-lg, var(--ng-spacing-lg));\n padding-inline-start: calc(var(--combobox-group-header-indent, var(--nile-spacing-lg, var(--ng-spacing-lg))) + var(--group-depth, 0) * 16px);\n font-family: var(--nile-font-family-sans-serif, var(--ng-font-family-body));\n font-size: var(--nile-type-scale-2, var(--ng-font-size-text-xs));\n font-weight: var(--nile-font-weight-semibold, var(--ng-font-weight-semibold));\n line-height: 1;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n color: var(--nile-colors-dark-500, var(--ng-colors-text-tertiary-600));\n background: var(--nile-colors-neutral-100, var(--ng-colors-bg-secondary));\n border-bottom: 1px solid var(--nile-colors-neutral-300, var(--ng-colors-border-secondary));\n pointer-events: none;\n user-select: none;\n }\n\n .combobox__group-label {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .combobox__group-prefix {\n flex-shrink: 0;\n color: inherit;\n }\n\n /* ── Help / Error ── */\n\n .form-control__help-text {\n display: none;\n }\n\n .form-control--has-help-text .form-control__help-text {\n display: block;\n color: var(--nile-colors-dark-500, var(--ng-colors-text-tertiary-600));\n margin-top: var(--nile-spacing-lg, var(--ng-spacing-lg));\n font-size: var(--nile-type-scale-3, var(--ng-font-size-text-sm));\n }\n`;\n\nexport default [styles];\n"]}
@@ -96,6 +96,14 @@ export declare class NileCombobox extends NileElement implements NileFormControl
96
96
  selectedOptions: ComboboxOption[];
97
97
  /** The items displayed after filtering. Renderer reads from this. */
98
98
  filteredData: any[];
99
+ /**
100
+ * Mixed (header + option) row list, only populated when `data` contains
101
+ * group entries (`type: 'group'`). When non-empty, the listbox renders from
102
+ * this instead of `filteredData`. `filteredData` stays in sync as the
103
+ * option-only projection so existing select-all / strict-match / etc. logic
104
+ * keeps working unchanged.
105
+ */
106
+ private filteredRows;
99
107
  /** The complete unfiltered dataset (preserved for re-filtering). */
100
108
  private originalData;
101
109
  showNoResults: boolean;
@@ -104,6 +112,12 @@ export declare class NileCombobox extends NileElement implements NileFormControl
104
112
  private showSelectedOnly;
105
113
  private selectAllChecked;
106
114
  private selectAllIndeterminate;
115
+ /**
116
+ * Index into `filteredRows` of the group header that should be pinned at
117
+ * the top of the (virtualized) listbox right now. -1 means none.
118
+ * Recomputed on scroll.
119
+ */
120
+ private stickyHeaderIndex;
107
121
  name: string;
108
122
  data: any[];
109
123
  value: string | string[];
@@ -144,6 +158,13 @@ export declare class NileCombobox extends NileElement implements NileFormControl
144
158
  * non-disabled options (respects the active search filter).
145
159
  */
146
160
  selectAllEnabled: boolean;
161
+ /**
162
+ * When true (default), data-driven group headers stick to the top of the
163
+ * listbox while scrolling through that group's options (Atlassian-style).
164
+ * Works in both plain and virtualized rendering modes. Set to false for
165
+ * inline-only headers that scroll away with their options.
166
+ */
167
+ stickyGroupHeader: boolean;
147
168
  portal: boolean;
148
169
  hoist: boolean;
149
170
  placement: ComboboxPlacement;
@@ -184,12 +205,23 @@ export declare class NileCombobox extends NileElement implements NileFormControl
184
205
  protected firstUpdated(_changed: PropertyValues): void;
185
206
  protected updated(changedProperties: PropertyValues): void;
186
207
  private get isHorizontalGrid();
208
+ /** True when the source data contains at least one group entry. */
209
+ private get hasGroupedData();
210
+ /**
211
+ * Walk filteredRows and find the index of the deepest group header whose
212
+ * virtual position is at or above `scrollTop`. That's the header that
213
+ * should be pinned at the top of the listbox right now.
214
+ */
215
+ private updateStickyHeader;
216
+ /** Recursively keep only options whose value is in `selectedSet`; drop empty groups. */
217
+ private pruneTreeBySelection;
187
218
  private get hasActiveFilter();
188
219
  private renderEmptyState;
189
220
  private get isBidirectionalGrid();
190
221
  private get virtualRowCount();
191
222
  private get virtualColumnCount();
192
223
  private updateVirtualizerCount;
224
+ private lastVirtualizerGrouped;
193
225
  private getDisplayText;
194
226
  private getItemValue;
195
227
  private getSearchText;
@@ -273,6 +305,7 @@ export declare class NileCombobox extends NileElement implements NileFormControl
273
305
  private renderTrigger;
274
306
  private renderInlineTags;
275
307
  private renderClearButton;
308
+ private renderStickyHeaderOverlay;
276
309
  private renderListbox;
277
310
  private renderHorizontalListbox;
278
311
  private renderLoader;
@@ -30,6 +30,7 @@ import { ComboboxSelectionManager } from './selection-manager.js';
30
30
  import { ComboboxSearchManager } from './search-manager.js';
31
31
  import { ComboboxRenderer } from './renderer.js';
32
32
  import { ComboboxPortalManager } from './portal-manager.js';
33
+ import { hasGroups, flattenRows, filterRows, getOptionRows } from './group-utils.js';
33
34
  import { VisibilityManager } from '../utilities/visibility-manager.js';
34
35
  /**
35
36
  * @summary A data-driven combobox with virtualized options, inline search, multi-select tags,
@@ -120,6 +121,14 @@ let NileCombobox = class NileCombobox extends NileElement {
120
121
  this.selectedOptions = [];
121
122
  /** The items displayed after filtering. Renderer reads from this. */
122
123
  this.filteredData = [];
124
+ /**
125
+ * Mixed (header + option) row list, only populated when `data` contains
126
+ * group entries (`type: 'group'`). When non-empty, the listbox renders from
127
+ * this instead of `filteredData`. `filteredData` stays in sync as the
128
+ * option-only projection so existing select-all / strict-match / etc. logic
129
+ * keeps working unchanged.
130
+ */
131
+ this.filteredRows = [];
123
132
  /** The complete unfiltered dataset (preserved for re-filtering). */
124
133
  this.originalData = [];
125
134
  this.showNoResults = false;
@@ -128,6 +137,12 @@ let NileCombobox = class NileCombobox extends NileElement {
128
137
  this.showSelectedOnly = false;
129
138
  this.selectAllChecked = false;
130
139
  this.selectAllIndeterminate = false;
140
+ /**
141
+ * Index into `filteredRows` of the group header that should be pinned at
142
+ * the top of the (virtualized) listbox right now. -1 means none.
143
+ * Recomputed on scroll.
144
+ */
145
+ this.stickyHeaderIndex = -1;
131
146
  // ── Public properties ──
132
147
  this.name = '';
133
148
  this.data = [];
@@ -169,6 +184,13 @@ let NileCombobox = class NileCombobox extends NileElement {
169
184
  * non-disabled options (respects the active search filter).
170
185
  */
171
186
  this.selectAllEnabled = false;
187
+ /**
188
+ * When true (default), data-driven group headers stick to the top of the
189
+ * listbox while scrolling through that group's options (Atlassian-style).
190
+ * Works in both plain and virtualized rendering modes. Set to false for
191
+ * inline-only headers that scroll away with their options.
192
+ */
193
+ this.stickyGroupHeader = true;
172
194
  this.portal = false;
173
195
  this.hoist = false;
174
196
  this.placement = 'bottom';
@@ -201,6 +223,7 @@ let NileCombobox = class NileCombobox extends NileElement {
201
223
  this.gridRows = 0;
202
224
  /** Width of each column in horizontal grid mode (px). */
203
225
  this.gridColumnWidth = 160;
226
+ this.lastVirtualizerGrouped = false;
204
227
  }
205
228
  // ── Accessors ──
206
229
  get validity() {
@@ -261,6 +284,51 @@ let NileCombobox = class NileCombobox extends NileElement {
261
284
  get isHorizontalGrid() {
262
285
  return this.gridRows > 0 && this.gridColumns <= 1;
263
286
  }
287
+ /** True when the source data contains at least one group entry. */
288
+ get hasGroupedData() {
289
+ const base = this.originalData.length > 0 ? this.originalData : this.data;
290
+ return hasGroups(base);
291
+ }
292
+ /**
293
+ * Walk filteredRows and find the index of the deepest group header whose
294
+ * virtual position is at or above `scrollTop`. That's the header that
295
+ * should be pinned at the top of the listbox right now.
296
+ */
297
+ updateStickyHeader(scrollTop) {
298
+ if (!this.stickyGroupHeader || !this.hasGroupedData) {
299
+ if (this.stickyHeaderIndex !== -1)
300
+ this.stickyHeaderIndex = -1;
301
+ return;
302
+ }
303
+ let offset = 0;
304
+ let stuck = -1;
305
+ for (let i = 0; i < this.filteredRows.length; i++) {
306
+ const row = this.filteredRows[i];
307
+ const size = row.kind === 'header' ? 32 : 38;
308
+ if (offset > scrollTop)
309
+ break;
310
+ if (row.kind === 'header')
311
+ stuck = i;
312
+ offset += size;
313
+ }
314
+ if (stuck !== this.stickyHeaderIndex)
315
+ this.stickyHeaderIndex = stuck;
316
+ }
317
+ /** Recursively keep only options whose value is in `selectedSet`; drop empty groups. */
318
+ pruneTreeBySelection(items, selectedSet) {
319
+ const out = [];
320
+ for (const item of items) {
321
+ if (item && typeof item === 'object' && item.type === 'group' && Array.isArray(item.options)) {
322
+ const kept = this.pruneTreeBySelection(item.options, selectedSet);
323
+ if (kept.length > 0)
324
+ out.push({ ...item, options: kept });
325
+ }
326
+ else if (selectedSet.has(String(this.getItemValue(item)))) {
327
+ out.push(item);
328
+ }
329
+ }
330
+ return out;
331
+ }
264
332
  get hasActiveFilter() {
265
333
  return !!this.searchValue || this.showSelectedOnly;
266
334
  }
@@ -277,6 +345,9 @@ let NileCombobox = class NileCombobox extends NileElement {
277
345
  if (this.gridColumns > 1) {
278
346
  return Math.ceil(this.filteredData.length / this.gridColumns);
279
347
  }
348
+ if (this.hasGroupedData) {
349
+ return this.filteredRows.length;
350
+ }
280
351
  return this.filteredData.length;
281
352
  }
282
353
  get virtualColumnCount() {
@@ -298,12 +369,20 @@ let NileCombobox = class NileCombobox extends NileElement {
298
369
  else {
299
370
  const virtualizer = this.virtualizerCtrl.getVirtualizer();
300
371
  const count = this.virtualRowCount;
301
- if (virtualizer.options.count !== count) {
372
+ const grouped = this.hasGroupedData;
373
+ const countChanged = virtualizer.options.count !== count;
374
+ const modeChanged = this.lastVirtualizerGrouped !== grouped;
375
+ if (countChanged || modeChanged) {
376
+ const estimateSize = grouped
377
+ ? (index) => (this.filteredRows[index]?.kind === 'header' ? 32 : 38)
378
+ : () => 38;
302
379
  virtualizer.setOptions({
303
380
  ...virtualizer.options,
304
381
  count,
382
+ estimateSize,
305
383
  });
306
384
  virtualizer.measure();
385
+ this.lastVirtualizerGrouped = grouped;
307
386
  }
308
387
  }
309
388
  }
@@ -334,7 +413,10 @@ let NileCombobox = class NileCombobox extends NileElement {
334
413
  }
335
414
  // ── Selection ──
336
415
  syncSelection() {
337
- const items = this.originalData.length > 0 ? this.originalData : this.data;
416
+ const baseData = this.originalData.length > 0 ? this.originalData : this.data;
417
+ const items = this.hasGroupedData
418
+ ? getOptionRows(flattenRows(baseData)).map(r => r.item)
419
+ : baseData;
338
420
  this.selectedOptions = ComboboxSelectionManager.createOptionsFromValues(this.value, items, this.getDisplayText.bind(this), this.renderItemConfig?.getValue);
339
421
  if (!this.multiple) {
340
422
  const label = this.selectedOptions[0]?.getTextLabel() ?? '';
@@ -649,15 +731,34 @@ let NileCombobox = class NileCombobox extends NileElement {
649
731
  }
650
732
  filterOptions(search, preserveScroll = false) {
651
733
  const baseData = this.originalData.length > 0 ? this.originalData : this.data;
652
- let source = baseData;
653
- if (this.showSelectedOnly) {
654
- const selectedValues = Array.isArray(this.value) ? this.value : [this.value];
655
- const selectedSet = new Set(selectedValues.map(v => String(v)));
656
- source = baseData.filter((item) => selectedSet.has(String(this.getItemValue(item))));
734
+ if (this.hasGroupedData) {
735
+ // Grouped path: filter the tree, derive options-only projection.
736
+ let tree = baseData;
737
+ if (this.showSelectedOnly) {
738
+ const selectedValues = Array.isArray(this.value) ? this.value : [this.value];
739
+ const selectedSet = new Set(selectedValues.map(v => String(v)));
740
+ tree = this.pruneTreeBySelection(baseData, selectedSet);
741
+ }
742
+ const { rows } = filterRows(tree, search, this.getSearchText.bind(this));
743
+ this.filteredRows = rows;
744
+ this.filteredData = getOptionRows(rows).map(r => r.item);
745
+ this.showNoResults = this.filteredData.length === 0;
746
+ // Recompute sticky header at current scroll (defaults to top on filter).
747
+ const st = this.scrollElementRef.value?.scrollTop ?? 0;
748
+ this.updateStickyHeader(st);
749
+ }
750
+ else {
751
+ let source = baseData;
752
+ if (this.showSelectedOnly) {
753
+ const selectedValues = Array.isArray(this.value) ? this.value : [this.value];
754
+ const selectedSet = new Set(selectedValues.map(v => String(v)));
755
+ source = baseData.filter((item) => selectedSet.has(String(this.getItemValue(item))));
756
+ }
757
+ const { filteredItems, showNoResults } = this.searchManager.filter(search, source, this.getSearchText.bind(this));
758
+ this.filteredData = filteredItems;
759
+ this.filteredRows = [];
760
+ this.showNoResults = showNoResults;
657
761
  }
658
- const { filteredItems, showNoResults } = this.searchManager.filter(search, source, this.getSearchText.bind(this));
659
- this.filteredData = filteredItems;
660
- this.showNoResults = showNoResults;
661
762
  this.portalManager.resetMeasuredHeight();
662
763
  if (!preserveScroll) {
663
764
  this.resetScrollPosition();
@@ -684,7 +785,10 @@ let NileCombobox = class NileCombobox extends NileElement {
684
785
  return;
685
786
  if (!this.multiple) {
686
787
  if (this.strict) {
687
- const allItems = this.originalData.length > 0 ? this.originalData : this.data;
788
+ const baseData = this.originalData.length > 0 ? this.originalData : this.data;
789
+ const allItems = this.hasGroupedData
790
+ ? getOptionRows(flattenRows(baseData)).map(r => r.item)
791
+ : baseData;
688
792
  const match = allItems.find((item) => this.getDisplayText(item).toLowerCase() === this.searchValue.toLowerCase());
689
793
  if (match) {
690
794
  const val = this.getItemValue(match);
@@ -812,26 +916,14 @@ let NileCombobox = class NileCombobox extends NileElement {
812
916
  if (this.selectedOptions.length === 0)
813
917
  return;
814
918
  this.showSelectedOnly = !this.showSelectedOnly;
815
- if (this.showSelectedOnly) {
919
+ if (this.showSelectedOnly)
816
920
  this.searchValue = '';
817
- const selectedValues = Array.isArray(this.value) ? this.value : [this.value];
818
- this.filteredData = this.originalData.filter((item) => {
819
- const iv = this.getItemValue(item);
820
- return selectedValues.some(val => String(val) === String(iv));
821
- });
822
- }
823
- else {
824
- this.filteredData = [...this.originalData];
825
- }
826
- this.portalManager.resetMeasuredHeight();
827
- this.resetScrollPosition();
828
- this.updateSelectAllState();
829
- this.requestUpdate();
921
+ this.filterOptions(this.searchValue);
830
922
  }
831
923
  clearAll() {
832
924
  this.showSelectedOnly = false;
833
925
  this.value = this.multiple ? [] : '';
834
- this.filteredData = [...this.originalData];
926
+ this.filterOptions('');
835
927
  this.syncSelection();
836
928
  this.emit('nile-change', { value: this.value, name: this.name });
837
929
  this.emit('nile-clear', { value: this.value, name: this.name });
@@ -839,9 +931,10 @@ let NileCombobox = class NileCombobox extends NileElement {
839
931
  }
840
932
  // ── Scroll ──
841
933
  onScroll(e) {
934
+ const target = e.target;
935
+ this.updateStickyHeader(target.scrollTop);
842
936
  if (this.showSelectedOnly)
843
937
  return;
844
- const target = e.target;
845
938
  this.emit('nile-scroll', { scrollTop: target.scrollTop, scrollLeft: target.scrollLeft, name: this.name });
846
939
  if (!this.scrolling) {
847
940
  this.scrolling = true;
@@ -893,8 +986,14 @@ let NileCombobox = class NileCombobox extends NileElement {
893
986
  if (this.originalData.length === 0 && this.data.length > 0) {
894
987
  this.originalData = [...this.data];
895
988
  }
896
- this.filteredData = [...(this.originalData.length > 0 ? this.originalData : this.data)];
897
- this.showNoResults = this.filteredData.length === 0;
989
+ if (this.hasGroupedData) {
990
+ this.filterOptions(this.searchValue);
991
+ }
992
+ else {
993
+ this.filteredData = [...(this.originalData.length > 0 ? this.originalData : this.data)];
994
+ this.filteredRows = [];
995
+ this.showNoResults = this.filteredData.length === 0;
996
+ }
898
997
  await stopAnimations(this);
899
998
  if (this.popup?.popup) {
900
999
  this.popup.popup.style.visibility = 'hidden';
@@ -944,7 +1043,13 @@ let NileCombobox = class NileCombobox extends NileElement {
944
1043
  if (this.data.length > 0 && !this.showSelectedOnly) {
945
1044
  this.originalData = [...this.data];
946
1045
  }
947
- this.filteredData = [...this.data];
1046
+ if (this.hasGroupedData) {
1047
+ this.filterOptions(this.searchValue);
1048
+ }
1049
+ else {
1050
+ this.filteredData = [...this.data];
1051
+ this.filteredRows = [];
1052
+ }
948
1053
  this.syncSelection();
949
1054
  this.updateSelectAllState();
950
1055
  if (!this.optionsLoading && !this.loading && this.data.length === 0) {
@@ -1189,6 +1294,21 @@ let NileCombobox = class NileCombobox extends NileElement {
1189
1294
  </button>
1190
1295
  `;
1191
1296
  }
1297
+ renderStickyHeaderOverlay(grouped, useVirtual) {
1298
+ if (!this.stickyGroupHeader || !grouped || !useVirtual)
1299
+ return html ``;
1300
+ const idx = this.stickyHeaderIndex;
1301
+ if (idx < 0 || idx >= this.filteredRows.length)
1302
+ return html ``;
1303
+ const row = this.filteredRows[idx];
1304
+ if (row.kind !== 'header')
1305
+ return html ``;
1306
+ return html `
1307
+ <div class="combobox__group-sticky-overlay">
1308
+ ${ComboboxRenderer.renderGroupHeader(row)}
1309
+ </div>
1310
+ `;
1311
+ }
1192
1312
  renderListbox() {
1193
1313
  const showAddOption = this.allowCustomValue
1194
1314
  && this.searchValue.trim()
@@ -1204,7 +1324,10 @@ let NileCombobox = class NileCombobox extends NileElement {
1204
1324
  return this.renderHorizontalListbox(!!showAddOption);
1205
1325
  }
1206
1326
  const isGrid = this.gridColumns > 1 || this.isBidirectionalGrid;
1207
- const useVirtual = ComboboxRenderer.shouldUseVirtualizer(this.filteredData, this.gridColumns);
1327
+ const grouped = this.hasGroupedData && !isGrid;
1328
+ const useVirtual = grouped
1329
+ ? this.filteredRows.length >= 5
1330
+ : ComboboxRenderer.shouldUseVirtualizer(this.filteredData, this.gridColumns);
1208
1331
  const virtualizer = this.virtualizerCtrl.getVirtualizer();
1209
1332
  const virtualItems = (useVirtual || isGrid) ? virtualizer.getVirtualItems() : [];
1210
1333
  const totalSize = (useVirtual || isGrid) ? virtualizer.getTotalSize() : 0;
@@ -1225,13 +1348,18 @@ let NileCombobox = class NileCombobox extends NileElement {
1225
1348
  >
1226
1349
  ${this.renderLoader()}
1227
1350
  ${this.renderSelectAll()}
1351
+ ${this.renderStickyHeaderOverlay(grouped, useVirtual)}
1228
1352
  ${this.showNoResults && !this.optionsLoading && !this.loading
1229
1353
  ? this.renderEmptyState()
1230
1354
  : isGrid
1231
1355
  ? ComboboxRenderer.renderVirtualizedGrid(virtualItems, totalSize, this.filteredData, this.value, this.multiple, this.gridColumns, this.getDisplayText.bind(this), this.getItemValue.bind(this), this.optionsLoading || this.loading, this.allowHtmlLabel, this.renderItemConfig?.getDescription ? this.getItemDescription.bind(this) : undefined, this.renderItemConfig?.getPrefix ? this.getItemPrefix.bind(this) : undefined, this.renderItemConfig?.getSuffix ? this.getItemSuffix.bind(this) : undefined, this.isBidirectionalGrid ? this.gridColumnWidth : undefined)
1232
- : useVirtual
1233
- ? ComboboxRenderer.renderVirtualizedOptions(virtualItems, totalSize, this.filteredData, this.value, this.multiple, this.getDisplayText.bind(this), this.getItemValue.bind(this), this.optionsLoading || this.loading, this.allowHtmlLabel, virtualizer.measureElement, this.renderItemConfig?.getDescription ? this.getItemDescription.bind(this) : undefined, this.renderItemConfig?.getPrefix ? this.getItemPrefix.bind(this) : undefined, this.renderItemConfig?.getSuffix ? this.getItemSuffix.bind(this) : undefined)
1234
- : ComboboxRenderer.renderPlainOptions(this.filteredData, this.value, this.multiple, this.getDisplayText.bind(this), this.getItemValue.bind(this), this.showNoResults, this.noResultsMessage, this.optionsLoading || this.loading, this.onScroll.bind(this), this.allowHtmlLabel, this.renderItemConfig?.getDescription ? this.getItemDescription.bind(this) : undefined, this.renderItemConfig?.getPrefix ? this.getItemPrefix.bind(this) : undefined, this.renderItemConfig?.getSuffix ? this.getItemSuffix.bind(this) : undefined, undefined, this.hasActiveFilter ? this.noResultsSubtitle : undefined)}
1356
+ : grouped
1357
+ ? (useVirtual
1358
+ ? ComboboxRenderer.renderRowsVirtualized(virtualItems, totalSize, this.filteredRows, this.value, this.multiple, this.getDisplayText.bind(this), this.getItemValue.bind(this), this.optionsLoading || this.loading, this.allowHtmlLabel, this.renderItemConfig?.getDescription ? this.getItemDescription.bind(this) : undefined, this.renderItemConfig?.getPrefix ? this.getItemPrefix.bind(this) : undefined, this.renderItemConfig?.getSuffix ? this.getItemSuffix.bind(this) : undefined)
1359
+ : ComboboxRenderer.renderRowsPlain(this.filteredRows, this.value, this.multiple, this.getDisplayText.bind(this), this.getItemValue.bind(this), this.showNoResults, this.noResultsMessage, this.optionsLoading || this.loading, this.onScroll.bind(this), this.allowHtmlLabel, this.renderItemConfig?.getDescription ? this.getItemDescription.bind(this) : undefined, this.renderItemConfig?.getPrefix ? this.getItemPrefix.bind(this) : undefined, this.renderItemConfig?.getSuffix ? this.getItemSuffix.bind(this) : undefined, undefined, this.hasActiveFilter ? this.noResultsSubtitle : undefined))
1360
+ : useVirtual
1361
+ ? ComboboxRenderer.renderVirtualizedOptions(virtualItems, totalSize, this.filteredData, this.value, this.multiple, this.getDisplayText.bind(this), this.getItemValue.bind(this), this.optionsLoading || this.loading, this.allowHtmlLabel, virtualizer.measureElement, this.renderItemConfig?.getDescription ? this.getItemDescription.bind(this) : undefined, this.renderItemConfig?.getPrefix ? this.getItemPrefix.bind(this) : undefined, this.renderItemConfig?.getSuffix ? this.getItemSuffix.bind(this) : undefined)
1362
+ : ComboboxRenderer.renderPlainOptions(this.filteredData, this.value, this.multiple, this.getDisplayText.bind(this), this.getItemValue.bind(this), this.showNoResults, this.noResultsMessage, this.optionsLoading || this.loading, this.onScroll.bind(this), this.allowHtmlLabel, this.renderItemConfig?.getDescription ? this.getItemDescription.bind(this) : undefined, this.renderItemConfig?.getPrefix ? this.getItemPrefix.bind(this) : undefined, this.renderItemConfig?.getSuffix ? this.getItemSuffix.bind(this) : undefined, undefined, this.hasActiveFilter ? this.noResultsSubtitle : undefined)}
1235
1363
  ${showAddOption ? html `
1236
1364
  <div @mouseup=${(e) => { e.stopPropagation(); this.addCustomValue(this.searchValue.trim()); }}>
1237
1365
  ${ComboboxRenderer.renderAddCustomOption(this.searchValue.trim(), this.multiple)}
@@ -1375,6 +1503,9 @@ __decorate([
1375
1503
  __decorate([
1376
1504
  state()
1377
1505
  ], NileCombobox.prototype, "filteredData", void 0);
1506
+ __decorate([
1507
+ state()
1508
+ ], NileCombobox.prototype, "filteredRows", void 0);
1378
1509
  __decorate([
1379
1510
  state()
1380
1511
  ], NileCombobox.prototype, "originalData", void 0);
@@ -1396,6 +1527,9 @@ __decorate([
1396
1527
  __decorate([
1397
1528
  state()
1398
1529
  ], NileCombobox.prototype, "selectAllIndeterminate", void 0);
1530
+ __decorate([
1531
+ state()
1532
+ ], NileCombobox.prototype, "stickyHeaderIndex", void 0);
1399
1533
  __decorate([
1400
1534
  property()
1401
1535
  ], NileCombobox.prototype, "name", void 0);
@@ -1470,6 +1604,9 @@ __decorate([
1470
1604
  __decorate([
1471
1605
  property({ type: Boolean, reflect: true, attribute: 'select-all-enabled' })
1472
1606
  ], NileCombobox.prototype, "selectAllEnabled", void 0);
1607
+ __decorate([
1608
+ property({ type: Boolean, reflect: true, attribute: 'sticky-group-header' })
1609
+ ], NileCombobox.prototype, "stickyGroupHeader", void 0);
1473
1610
  __decorate([
1474
1611
  property({ type: Boolean, reflect: true })
1475
1612
  ], NileCombobox.prototype, "portal", void 0);