@smilodon/core 1.4.9 → 1.4.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -646,6 +646,28 @@ enhanced-select.dark-mode {
646
646
  --select-dropdown-bg /* Dropdown background (white) */
647
647
  --select-dropdown-border /* Dropdown border color (#ccc) */
648
648
  --select-dropdown-shadow /* Dropdown shadow */
649
+ --select-empty-padding /* Empty/no-results container padding */
650
+ --select-empty-color /* Text color for empty/no-results models */
651
+ --select-empty-font-size /* Font size */
652
+ --select-empty-bg /* Background for empty/no-results state */
653
+ --select-empty-min-height /* Minimum height of empty state box */
654
+
655
+ /* Arrow/button */
656
+ --select-arrow-size /* Width & height of SVG icon (16px default) */
657
+ --select-arrow-color /* Icon color (#667eea) */
658
+ --select-arrow-hover-color /* Icon color when hovered (#667eea) */
659
+ --select-arrow-hover-bg /* Background when hover (rgba(102,126,234,0.08)) */
660
+ --select-arrow-width /* Container width (40px) */
661
+ --select-arrow-border-radius /* Container border radius */
662
+
663
+ /* Group headers (when using groupedItems or flat items with `group` property) */
664
+ --select-group-header-padding /* Padding inside header (8px 12px) */
665
+ --select-group-header-color /* Text color (#6b7280) */
666
+ --select-group-header-bg /* Background (#f3f4f6) */
667
+ --select-group-header-font-size
668
+ --select-group-header-text-transform
669
+ --select-group-header-letter-spacing
670
+ --select-group-header-border-bottom
649
671
  ```
650
672
 
651
673
  **Dark Mode (Opt-in)**
@@ -659,6 +681,8 @@ enhanced-select.dark-mode {
659
681
  --select-dark-option-bg /* Dark option background (#1f2937) */
660
682
  --select-dark-option-hover-bg /* Dark hover background (#374151) */
661
683
  --select-dark-option-selected-bg /* Dark selected bg (#3730a3) */
684
+ --select-dark-group-header-color /* Dark header text */
685
+ --select-dark-group-header-bg /* Dark header background */
662
686
  ```
663
687
 
664
688
  **Complete CSS Variables List (60+ variables)**
@@ -698,6 +722,10 @@ enhanced-select {
698
722
  **Badge Remove/Delete Button (Multi-Select)**
699
723
  The × button that removes selected items in multi-select mode is fully customizable:
700
724
 
725
+ **Group Header & No‑Results Parts**
726
+ Both the group header and the no-results message are exposed as shadow parts (`group-header` and `no-results`) so you can target them with `::part()` selectors or CSS variables. This makes it straightforward to match the look of your host framework or UI kit.
727
+
728
+
701
729
  ```css
702
730
  enhanced-select {
703
731
  /* Customize badge appearance */
package/dist/index.cjs CHANGED
@@ -1875,7 +1875,22 @@ class EnhancedSelect extends HTMLElement {
1875
1875
  }
1876
1876
  set classMap(map) {
1877
1877
  this._classMap = map;
1878
- this._setGlobalStylesMirroring(Boolean(this._optionRenderer || map));
1878
+ this._setGlobalStylesMirroring(Boolean(this._optionRenderer || map || this._groupHeaderRenderer));
1879
+ if (!this.isConnected)
1880
+ return;
1881
+ this._renderOptions();
1882
+ }
1883
+ /**
1884
+ * DOM-based renderer for group headers. When provided, the component will
1885
+ * call this function for each group during rendering. The returned element
1886
+ * will receive `.group-header` and `part="group-header"` automatically.
1887
+ */
1888
+ get groupHeaderRenderer() {
1889
+ return this._groupHeaderRenderer;
1890
+ }
1891
+ set groupHeaderRenderer(renderer) {
1892
+ this._groupHeaderRenderer = renderer;
1893
+ this._setGlobalStylesMirroring(Boolean(this._optionRenderer || this._classMap || renderer));
1879
1894
  if (!this.isConnected)
1880
1895
  return;
1881
1896
  this._renderOptions();
@@ -1934,6 +1949,8 @@ class EnhancedSelect extends HTMLElement {
1934
1949
  this._initializeObservers();
1935
1950
  }
1936
1951
  connectedCallback() {
1952
+ // register instance
1953
+ EnhancedSelect._instances.add(this);
1937
1954
  // WORKAROUND: Force display style on host element for Angular compatibility
1938
1955
  // Angular's rendering seems to not apply :host styles correctly in some cases
1939
1956
  // Must be done in connectedCallback when element is attached to DOM
@@ -1952,6 +1969,8 @@ class EnhancedSelect extends HTMLElement {
1952
1969
  }
1953
1970
  }
1954
1971
  disconnectedCallback() {
1972
+ // unregister instance
1973
+ EnhancedSelect._instances.delete(this);
1955
1974
  // Cleanup observers
1956
1975
  this._resizeObserver?.disconnect();
1957
1976
  this._intersectionObserver?.disconnect();
@@ -2237,6 +2256,8 @@ class EnhancedSelect extends HTMLElement {
2237
2256
  right: 0;
2238
2257
  bottom: 0;
2239
2258
  width: var(--select-arrow-width, 40px);
2259
+ /* allow explicit height override even though container normally stretches */
2260
+ height: var(--select-arrow-height, auto);
2240
2261
  display: flex;
2241
2262
  align-items: center;
2242
2263
  justify-content: center;
@@ -2302,9 +2323,14 @@ class EnhancedSelect extends HTMLElement {
2302
2323
  background-color: var(--select-arrow-hover-bg, rgba(102, 126, 234, 0.08));
2303
2324
  }
2304
2325
 
2326
+ .dropdown-arrow:hover {
2327
+ /* legacy alias --select-arrow-hover for icon color */
2328
+ color: var(--select-arrow-hover, var(--select-arrow-hover-color, #667eea));
2329
+ }
2330
+
2305
2331
  .dropdown-arrow {
2306
- width: var(--select-arrow-size, 16px);
2307
- height: var(--select-arrow-size, 16px);
2332
+ width: var(--select-arrow-width, var(--select-arrow-size, 16px));
2333
+ height: var(--select-arrow-height, var(--select-arrow-size, 16px));
2308
2334
  color: var(--select-arrow-color, #667eea);
2309
2335
  transition: transform 0.2s ease, color 0.2s ease;
2310
2336
  transform: translateY(0);
@@ -2658,6 +2684,13 @@ class EnhancedSelect extends HTMLElement {
2658
2684
 
2659
2685
  .option.active:not(.selected) {
2660
2686
  background-color: var(--select-dark-option-active-bg, #374151);
2687
+ }
2688
+
2689
+ /* Group header in dark mode */
2690
+ .group-header {
2691
+ color: var(--select-dark-group-header-color, var(--select-group-header-color, #6b7280));
2692
+ background-color: var(--select-dark-group-header-bg, var(--select-group-header-bg, #374151));
2693
+ }
2661
2694
  color: var(--select-dark-option-active-color, #f9fafb);
2662
2695
  outline: var(--select-dark-option-active-outline, 2px solid rgba(129, 140, 248, 0.55));
2663
2696
  }
@@ -2740,19 +2773,14 @@ class EnhancedSelect extends HTMLElement {
2740
2773
  this._boundArrowClick = (e) => {
2741
2774
  e.stopPropagation();
2742
2775
  e.preventDefault();
2743
- const wasOpen = this._state.isOpen;
2744
- this._state.isOpen = !this._state.isOpen;
2745
- this._updateDropdownVisibility();
2746
- this._updateArrowRotation();
2747
- if (this._state.isOpen && this._config.callbacks.onOpen) {
2748
- this._config.callbacks.onOpen();
2749
- }
2750
- else if (!this._state.isOpen && this._config.callbacks.onClose) {
2751
- this._config.callbacks.onClose();
2776
+ // delegate to the existing open/close helpers so we don't accidentally
2777
+ // drift out of sync with the logic in those methods (focus, events,
2778
+ // scroll-to-selected, etc.)
2779
+ if (this._state.isOpen) {
2780
+ this._handleClose();
2752
2781
  }
2753
- // Scroll to selected when opening
2754
- if (!wasOpen && this._state.isOpen && this._state.selectedIndices.size > 0) {
2755
- setTimeout(() => this._scrollToSelected(), 50);
2782
+ else {
2783
+ this._handleOpen();
2756
2784
  }
2757
2785
  };
2758
2786
  this._arrowContainer.addEventListener('click', this._boundArrowClick);
@@ -2786,6 +2814,10 @@ class EnhancedSelect extends HTMLElement {
2786
2814
  if (wasClosed) {
2787
2815
  this._handleOpen();
2788
2816
  }
2817
+ else {
2818
+ // clicking the input while open should close the dropdown too
2819
+ this._handleClose();
2820
+ }
2789
2821
  // Focus the input (do not prevent default behavior)
2790
2822
  this._input.focus();
2791
2823
  // If we just opened the dropdown, transfer pointer capture to the
@@ -2961,6 +2993,16 @@ class EnhancedSelect extends HTMLElement {
2961
2993
  _handleOpen() {
2962
2994
  if (!this._config.enabled || this._state.isOpen)
2963
2995
  return;
2996
+ // close any other open selects before proceeding
2997
+ EnhancedSelect._instances.forEach(inst => {
2998
+ if (inst !== this)
2999
+ inst._handleClose();
3000
+ });
3001
+ // Always focus the input when opening so callers (arrow click,
3002
+ // programmatic `open()`, etc.) get the keyboard cursor. This was a
3003
+ // frequent source of confusion in #14 where people opened the dropdown
3004
+ // but the text field never received focus.
3005
+ this._input.focus();
2964
3006
  this._markOpenStart();
2965
3007
  this._state.isOpen = true;
2966
3008
  this._dropdown.style.display = 'block';
@@ -3934,10 +3976,25 @@ class EnhancedSelect extends HTMLElement {
3934
3976
  const query = this._state.searchQuery.toLowerCase();
3935
3977
  // Handle Grouped Items Rendering (when no search query)
3936
3978
  if (this._state.groupedItems.length > 0 && !query) {
3937
- this._state.groupedItems.forEach(group => {
3938
- const header = document.createElement('div');
3939
- header.className = 'group-header';
3940
- header.textContent = group.label;
3979
+ this._state.groupedItems.forEach((group, groupIndex) => {
3980
+ let header;
3981
+ if (this.groupHeaderRenderer) {
3982
+ header = this.groupHeaderRenderer(group, groupIndex);
3983
+ // make sure the returned element has the correct semantics so
3984
+ // people can style it. we add the class/part even if the renderer
3985
+ // returned something else to ensure backward compatibility.
3986
+ if (!(header instanceof HTMLElement)) {
3987
+ // fall back to default if API is misused
3988
+ header = document.createElement('div');
3989
+ header.textContent = String(group.label);
3990
+ }
3991
+ }
3992
+ else {
3993
+ header = document.createElement('div');
3994
+ header.textContent = group.label;
3995
+ }
3996
+ header.classList.add('group-header');
3997
+ header.setAttribute('part', 'group-header');
3941
3998
  this._optionsContainer.appendChild(header);
3942
3999
  group.options.forEach(item => {
3943
4000
  // Find original index for correct ID generation and selection
@@ -4210,6 +4267,8 @@ class EnhancedSelect extends HTMLElement {
4210
4267
  }
4211
4268
  }
4212
4269
  }
4270
+ /** live set of all connected instances; used to auto-close siblings */
4271
+ EnhancedSelect._instances = new Set();
4213
4272
  // Register custom element
4214
4273
  if (!customElements.get('enhanced-select')) {
4215
4274
  customElements.define('enhanced-select', EnhancedSelect);