@smilodon/core 1.4.9 → 1.4.11

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/index.js CHANGED
@@ -1532,6 +1532,8 @@ class SelectOption extends HTMLElement {
1532
1532
  padding: var(--select-option-padding, 8px 12px);
1533
1533
  cursor: pointer;
1534
1534
  user-select: none;
1535
+ color: var(--select-option-color, var(--select-text-color, #1f2937));
1536
+ background: var(--select-option-bg, var(--select-dropdown-bg, var(--select-bg, white)));
1535
1537
  transition: var(--select-option-transition, background-color 0.2s ease);
1536
1538
  border: var(--select-option-border, none);
1537
1539
  border-bottom: var(--select-option-border-bottom, none);
@@ -1576,9 +1578,20 @@ class SelectOption extends HTMLElement {
1576
1578
 
1577
1579
  .option-content {
1578
1580
  flex: 1;
1579
- overflow: hidden;
1580
- text-overflow: ellipsis;
1581
- white-space: nowrap;
1581
+ overflow: var(--select-option-content-overflow, hidden);
1582
+ text-overflow: var(--select-option-content-text-overflow, ellipsis);
1583
+ white-space: var(--select-option-content-white-space, nowrap);
1584
+ }
1585
+
1586
+ .checkmark-icon {
1587
+ display: none;
1588
+ margin-left: var(--select-checkmark-margin-left, 8px);
1589
+ color: var(--select-checkmark-color, currentColor);
1590
+ }
1591
+
1592
+ :host([aria-selected="true"]) .checkmark-icon,
1593
+ .option-container.selected .checkmark-icon {
1594
+ display: inline-flex;
1582
1595
  }
1583
1596
 
1584
1597
  .remove-button {
@@ -1706,16 +1719,6 @@ class SelectOption extends HTMLElement {
1706
1719
  <path d="M4 8.5L6.5 11L12 5.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
1707
1720
  </svg>
1708
1721
  `;
1709
- // Visibility control via CSS or inline style
1710
- // We set it to display: none unless selected.
1711
- // User can override this behavior via part styling if they want transitions
1712
- if (!selected) {
1713
- checkmark.style.display = 'none';
1714
- }
1715
- else {
1716
- checkmark.style.marginLeft = '8px';
1717
- checkmark.style.color = 'currentColor';
1718
- }
1719
1722
  this._container.appendChild(checkmark);
1720
1723
  }
1721
1724
  // Data Attributes Contract on Host
@@ -1873,7 +1876,22 @@ class EnhancedSelect extends HTMLElement {
1873
1876
  }
1874
1877
  set classMap(map) {
1875
1878
  this._classMap = map;
1876
- this._setGlobalStylesMirroring(Boolean(this._optionRenderer || map));
1879
+ this._setGlobalStylesMirroring(Boolean(this._optionRenderer || map || this._groupHeaderRenderer));
1880
+ if (!this.isConnected)
1881
+ return;
1882
+ this._renderOptions();
1883
+ }
1884
+ /**
1885
+ * DOM-based renderer for group headers. When provided, the component will
1886
+ * call this function for each group during rendering. The returned element
1887
+ * will receive `.group-header` and `part="group-header"` automatically.
1888
+ */
1889
+ get groupHeaderRenderer() {
1890
+ return this._groupHeaderRenderer;
1891
+ }
1892
+ set groupHeaderRenderer(renderer) {
1893
+ this._groupHeaderRenderer = renderer;
1894
+ this._setGlobalStylesMirroring(Boolean(this._optionRenderer || this._classMap || renderer));
1877
1895
  if (!this.isConnected)
1878
1896
  return;
1879
1897
  this._renderOptions();
@@ -1932,11 +1950,12 @@ class EnhancedSelect extends HTMLElement {
1932
1950
  this._initializeObservers();
1933
1951
  }
1934
1952
  connectedCallback() {
1953
+ // register instance
1954
+ EnhancedSelect._instances.add(this);
1935
1955
  // WORKAROUND: Force display style on host element for Angular compatibility
1936
1956
  // Angular's rendering seems to not apply :host styles correctly in some cases
1937
1957
  // Must be done in connectedCallback when element is attached to DOM
1938
1958
  this.style.display = 'block';
1939
- this.style.width = '100%';
1940
1959
  if (this._optionRenderer) {
1941
1960
  this._setGlobalStylesMirroring(true);
1942
1961
  }
@@ -1950,6 +1969,8 @@ class EnhancedSelect extends HTMLElement {
1950
1969
  }
1951
1970
  }
1952
1971
  disconnectedCallback() {
1972
+ // unregister instance
1973
+ EnhancedSelect._instances.delete(this);
1953
1974
  // Cleanup observers
1954
1975
  this._resizeObserver?.disconnect();
1955
1976
  this._intersectionObserver?.disconnect();
@@ -2177,7 +2198,8 @@ class EnhancedSelect extends HTMLElement {
2177
2198
  :host {
2178
2199
  display: block;
2179
2200
  position: relative;
2180
- width: 100%;
2201
+ width: var(--select-width, 100%);
2202
+ height: var(--select-height, auto);
2181
2203
  }
2182
2204
 
2183
2205
  .select-container {
@@ -2193,6 +2215,7 @@ class EnhancedSelect extends HTMLElement {
2193
2215
  flex-wrap: wrap;
2194
2216
  gap: var(--select-input-gap, 6px);
2195
2217
  padding: var(--select-input-padding, 6px 52px 6px 8px);
2218
+ height: var(--select-input-height, auto);
2196
2219
  min-height: var(--select-input-min-height, 44px);
2197
2220
  max-height: var(--select-input-max-height, 160px);
2198
2221
  overflow-y: var(--select-input-overflow-y, auto);
@@ -2214,17 +2237,17 @@ class EnhancedSelect extends HTMLElement {
2214
2237
  content: '';
2215
2238
  position: absolute;
2216
2239
  top: 50%;
2217
- right: var(--select-separator-position, 40px);
2240
+ right: var(--select-separator-position, var(--select-seperator-position, 40px));
2218
2241
  transform: translateY(-50%);
2219
- width: var(--select-separator-width, 1px);
2220
- height: var(--select-separator-height, 60%);
2221
- background: var(--select-separator-bg, var(--select-separator-gradient, linear-gradient(
2242
+ width: var(--select-separator-width, var(--select-seperator-width, 1px));
2243
+ height: var(--select-separator-height, var(--select-seperator-height, 60%));
2244
+ background: var(--select-separator-bg, var(--select-seperator-bg, var(--select-separator-gradient, var(--select-seperator-gradient, linear-gradient(
2222
2245
  to bottom,
2223
2246
  transparent 0%,
2224
2247
  rgba(0, 0, 0, 0.1) 20%,
2225
2248
  rgba(0, 0, 0, 0.1) 80%,
2226
2249
  transparent 100%
2227
- )));
2250
+ ))));
2228
2251
  pointer-events: none;
2229
2252
  z-index: 1;
2230
2253
  }
@@ -2235,6 +2258,8 @@ class EnhancedSelect extends HTMLElement {
2235
2258
  right: 0;
2236
2259
  bottom: 0;
2237
2260
  width: var(--select-arrow-width, 40px);
2261
+ /* allow explicit height override even though container normally stretches */
2262
+ height: var(--select-arrow-height, auto);
2238
2263
  display: flex;
2239
2264
  align-items: center;
2240
2265
  justify-content: center;
@@ -2249,7 +2274,7 @@ class EnhancedSelect extends HTMLElement {
2249
2274
  }
2250
2275
 
2251
2276
  .input-container.has-clear-control::after {
2252
- right: var(--select-separator-position-with-clear, 72px);
2277
+ right: var(--select-separator-position-with-clear, var(--select-seperator-position-with-clear, 72px));
2253
2278
  }
2254
2279
 
2255
2280
  .dropdown-arrow-container.with-clear-control {
@@ -2300,9 +2325,14 @@ class EnhancedSelect extends HTMLElement {
2300
2325
  background-color: var(--select-arrow-hover-bg, rgba(102, 126, 234, 0.08));
2301
2326
  }
2302
2327
 
2328
+ .dropdown-arrow:hover {
2329
+ /* legacy alias --select-arrow-hover for icon color */
2330
+ color: var(--select-arrow-hover, var(--select-arrow-hover-color, #667eea));
2331
+ }
2332
+
2303
2333
  .dropdown-arrow {
2304
- width: var(--select-arrow-size, 16px);
2305
- height: var(--select-arrow-size, 16px);
2334
+ width: var(--select-arrow-width, var(--select-arrow-size, 16px));
2335
+ height: var(--select-arrow-height, var(--select-arrow-size, 16px));
2306
2336
  color: var(--select-arrow-color, #667eea);
2307
2337
  transition: transform 0.2s ease, color 0.2s ease;
2308
2338
  transform: translateY(0);
@@ -2322,6 +2352,7 @@ class EnhancedSelect extends HTMLElement {
2322
2352
 
2323
2353
  .select-input {
2324
2354
  flex: 1;
2355
+ width: var(--select-input-width, auto);
2325
2356
  min-width: var(--select-input-min-width, 120px);
2326
2357
  padding: var(--select-input-field-padding, 4px);
2327
2358
  border: none;
@@ -2416,6 +2447,7 @@ class EnhancedSelect extends HTMLElement {
2416
2447
  font-weight: var(--select-group-header-weight, 600);
2417
2448
  color: var(--select-group-header-color, #6b7280);
2418
2449
  background-color: var(--select-group-header-bg, #f3f4f6);
2450
+ text-align: var(--select-group-header-text-align, left);
2419
2451
  font-size: var(--select-group-header-font-size, 12px);
2420
2452
  text-transform: var(--select-group-header-text-transform, uppercase);
2421
2453
  letter-spacing: var(--select-group-header-letter-spacing, 0.05em);
@@ -2600,10 +2632,25 @@ class EnhancedSelect extends HTMLElement {
2600
2632
 
2601
2633
  /* Dark mode - Opt-in via class, data attribute, or ancestor context */
2602
2634
  :host(.dark-mode),
2635
+ :host([dark-mode]),
2636
+ :host([darkmode]),
2603
2637
  :host([data-theme="dark"]),
2638
+ :host([theme="dark"]),
2604
2639
  :host-context(.dark-mode),
2605
2640
  :host-context(.dark),
2606
- :host-context([data-theme="dark"]) {
2641
+ :host-context([dark-mode]),
2642
+ :host-context([darkmode]),
2643
+ :host-context([data-theme="dark"]),
2644
+ :host-context([theme="dark"]) {
2645
+ /* map dark tokens to base option tokens so nested <select-option>
2646
+ components also pick up dark mode via inherited CSS variables */
2647
+ --select-option-bg: var(--select-dark-option-bg, #1f2937);
2648
+ --select-option-color: var(--select-dark-option-color, #f9fafb);
2649
+ --select-option-hover-bg: var(--select-dark-option-hover-bg, #374151);
2650
+ --select-option-hover-color: var(--select-dark-option-hover-color, #f9fafb);
2651
+ --select-option-selected-bg: var(--select-dark-option-selected-bg, #3730a3);
2652
+ --select-option-selected-color: var(--select-dark-option-selected-text, #e0e7ff);
2653
+
2607
2654
  .input-container {
2608
2655
  background: var(--select-dark-bg, #1f2937);
2609
2656
  border-color: var(--select-dark-border, #4b5563);
@@ -2660,6 +2707,12 @@ class EnhancedSelect extends HTMLElement {
2660
2707
  outline: var(--select-dark-option-active-outline, 2px solid rgba(129, 140, 248, 0.55));
2661
2708
  }
2662
2709
 
2710
+ /* Group header in dark mode */
2711
+ .group-header {
2712
+ color: var(--select-dark-group-header-color, var(--select-group-header-color, #6b7280));
2713
+ background-color: var(--select-dark-group-header-bg, var(--select-group-header-bg, #374151));
2714
+ }
2715
+
2663
2716
  .option.selected.active {
2664
2717
  background-color: var(--select-dark-option-selected-active-bg, var(--select-dark-option-selected-bg, #3730a3));
2665
2718
  color: var(--select-dark-option-selected-active-color, var(--select-dark-option-selected-text, #e0e7ff));
@@ -2738,19 +2791,14 @@ class EnhancedSelect extends HTMLElement {
2738
2791
  this._boundArrowClick = (e) => {
2739
2792
  e.stopPropagation();
2740
2793
  e.preventDefault();
2741
- const wasOpen = this._state.isOpen;
2742
- this._state.isOpen = !this._state.isOpen;
2743
- this._updateDropdownVisibility();
2744
- this._updateArrowRotation();
2745
- if (this._state.isOpen && this._config.callbacks.onOpen) {
2746
- this._config.callbacks.onOpen();
2747
- }
2748
- else if (!this._state.isOpen && this._config.callbacks.onClose) {
2749
- this._config.callbacks.onClose();
2794
+ // delegate to the existing open/close helpers so we don't accidentally
2795
+ // drift out of sync with the logic in those methods (focus, events,
2796
+ // scroll-to-selected, etc.)
2797
+ if (this._state.isOpen) {
2798
+ this._handleClose();
2750
2799
  }
2751
- // Scroll to selected when opening
2752
- if (!wasOpen && this._state.isOpen && this._state.selectedIndices.size > 0) {
2753
- setTimeout(() => this._scrollToSelected(), 50);
2800
+ else {
2801
+ this._handleOpen();
2754
2802
  }
2755
2803
  };
2756
2804
  this._arrowContainer.addEventListener('click', this._boundArrowClick);
@@ -2784,6 +2832,16 @@ class EnhancedSelect extends HTMLElement {
2784
2832
  if (wasClosed) {
2785
2833
  this._handleOpen();
2786
2834
  }
2835
+ else {
2836
+ // Keep open while interacting directly with the input so users can
2837
+ // place cursor/type without accidental collapse.
2838
+ if (target === this._input) {
2839
+ this._input.focus();
2840
+ return;
2841
+ }
2842
+ // clicking other parts of the input container while open toggles close
2843
+ this._handleClose();
2844
+ }
2787
2845
  // Focus the input (do not prevent default behavior)
2788
2846
  this._input.focus();
2789
2847
  // If we just opened the dropdown, transfer pointer capture to the
@@ -2959,6 +3017,16 @@ class EnhancedSelect extends HTMLElement {
2959
3017
  _handleOpen() {
2960
3018
  if (!this._config.enabled || this._state.isOpen)
2961
3019
  return;
3020
+ // close any other open selects before proceeding
3021
+ EnhancedSelect._instances.forEach(inst => {
3022
+ if (inst !== this)
3023
+ inst._handleClose();
3024
+ });
3025
+ // Always focus the input when opening so callers (arrow click,
3026
+ // programmatic `open()`, etc.) get the keyboard cursor. This was a
3027
+ // frequent source of confusion in #14 where people opened the dropdown
3028
+ // but the text field never received focus.
3029
+ this._input.focus();
2962
3030
  this._markOpenStart();
2963
3031
  this._state.isOpen = true;
2964
3032
  this._dropdown.style.display = 'block';
@@ -3932,10 +4000,25 @@ class EnhancedSelect extends HTMLElement {
3932
4000
  const query = this._state.searchQuery.toLowerCase();
3933
4001
  // Handle Grouped Items Rendering (when no search query)
3934
4002
  if (this._state.groupedItems.length > 0 && !query) {
3935
- this._state.groupedItems.forEach(group => {
3936
- const header = document.createElement('div');
3937
- header.className = 'group-header';
3938
- header.textContent = group.label;
4003
+ this._state.groupedItems.forEach((group, groupIndex) => {
4004
+ let header;
4005
+ if (this.groupHeaderRenderer) {
4006
+ header = this.groupHeaderRenderer(group, groupIndex);
4007
+ // make sure the returned element has the correct semantics so
4008
+ // people can style it. we add the class/part even if the renderer
4009
+ // returned something else to ensure backward compatibility.
4010
+ if (!(header instanceof HTMLElement)) {
4011
+ // fall back to default if API is misused
4012
+ header = document.createElement('div');
4013
+ header.textContent = String(group.label);
4014
+ }
4015
+ }
4016
+ else {
4017
+ header = document.createElement('div');
4018
+ header.textContent = group.label;
4019
+ }
4020
+ header.classList.add('group-header');
4021
+ header.setAttribute('part', 'group-header');
3939
4022
  this._optionsContainer.appendChild(header);
3940
4023
  group.options.forEach(item => {
3941
4024
  // Find original index for correct ID generation and selection
@@ -4208,6 +4291,8 @@ class EnhancedSelect extends HTMLElement {
4208
4291
  }
4209
4292
  }
4210
4293
  }
4294
+ /** live set of all connected instances; used to auto-close siblings */
4295
+ EnhancedSelect._instances = new Set();
4211
4296
  // Register custom element
4212
4297
  if (!customElements.get('enhanced-select')) {
4213
4298
  customElements.define('enhanced-select', EnhancedSelect);