@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.umd.js CHANGED
@@ -1538,6 +1538,8 @@
1538
1538
  padding: var(--select-option-padding, 8px 12px);
1539
1539
  cursor: pointer;
1540
1540
  user-select: none;
1541
+ color: var(--select-option-color, var(--select-text-color, #1f2937));
1542
+ background: var(--select-option-bg, var(--select-dropdown-bg, var(--select-bg, white)));
1541
1543
  transition: var(--select-option-transition, background-color 0.2s ease);
1542
1544
  border: var(--select-option-border, none);
1543
1545
  border-bottom: var(--select-option-border-bottom, none);
@@ -1582,9 +1584,20 @@
1582
1584
 
1583
1585
  .option-content {
1584
1586
  flex: 1;
1585
- overflow: hidden;
1586
- text-overflow: ellipsis;
1587
- white-space: nowrap;
1587
+ overflow: var(--select-option-content-overflow, hidden);
1588
+ text-overflow: var(--select-option-content-text-overflow, ellipsis);
1589
+ white-space: var(--select-option-content-white-space, nowrap);
1590
+ }
1591
+
1592
+ .checkmark-icon {
1593
+ display: none;
1594
+ margin-left: var(--select-checkmark-margin-left, 8px);
1595
+ color: var(--select-checkmark-color, currentColor);
1596
+ }
1597
+
1598
+ :host([aria-selected="true"]) .checkmark-icon,
1599
+ .option-container.selected .checkmark-icon {
1600
+ display: inline-flex;
1588
1601
  }
1589
1602
 
1590
1603
  .remove-button {
@@ -1712,16 +1725,6 @@
1712
1725
  <path d="M4 8.5L6.5 11L12 5.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
1713
1726
  </svg>
1714
1727
  `;
1715
- // Visibility control via CSS or inline style
1716
- // We set it to display: none unless selected.
1717
- // User can override this behavior via part styling if they want transitions
1718
- if (!selected) {
1719
- checkmark.style.display = 'none';
1720
- }
1721
- else {
1722
- checkmark.style.marginLeft = '8px';
1723
- checkmark.style.color = 'currentColor';
1724
- }
1725
1728
  this._container.appendChild(checkmark);
1726
1729
  }
1727
1730
  // Data Attributes Contract on Host
@@ -1879,7 +1882,22 @@
1879
1882
  }
1880
1883
  set classMap(map) {
1881
1884
  this._classMap = map;
1882
- this._setGlobalStylesMirroring(Boolean(this._optionRenderer || map));
1885
+ this._setGlobalStylesMirroring(Boolean(this._optionRenderer || map || this._groupHeaderRenderer));
1886
+ if (!this.isConnected)
1887
+ return;
1888
+ this._renderOptions();
1889
+ }
1890
+ /**
1891
+ * DOM-based renderer for group headers. When provided, the component will
1892
+ * call this function for each group during rendering. The returned element
1893
+ * will receive `.group-header` and `part="group-header"` automatically.
1894
+ */
1895
+ get groupHeaderRenderer() {
1896
+ return this._groupHeaderRenderer;
1897
+ }
1898
+ set groupHeaderRenderer(renderer) {
1899
+ this._groupHeaderRenderer = renderer;
1900
+ this._setGlobalStylesMirroring(Boolean(this._optionRenderer || this._classMap || renderer));
1883
1901
  if (!this.isConnected)
1884
1902
  return;
1885
1903
  this._renderOptions();
@@ -1938,11 +1956,12 @@
1938
1956
  this._initializeObservers();
1939
1957
  }
1940
1958
  connectedCallback() {
1959
+ // register instance
1960
+ EnhancedSelect._instances.add(this);
1941
1961
  // WORKAROUND: Force display style on host element for Angular compatibility
1942
1962
  // Angular's rendering seems to not apply :host styles correctly in some cases
1943
1963
  // Must be done in connectedCallback when element is attached to DOM
1944
1964
  this.style.display = 'block';
1945
- this.style.width = '100%';
1946
1965
  if (this._optionRenderer) {
1947
1966
  this._setGlobalStylesMirroring(true);
1948
1967
  }
@@ -1956,6 +1975,8 @@
1956
1975
  }
1957
1976
  }
1958
1977
  disconnectedCallback() {
1978
+ // unregister instance
1979
+ EnhancedSelect._instances.delete(this);
1959
1980
  // Cleanup observers
1960
1981
  this._resizeObserver?.disconnect();
1961
1982
  this._intersectionObserver?.disconnect();
@@ -2183,7 +2204,8 @@
2183
2204
  :host {
2184
2205
  display: block;
2185
2206
  position: relative;
2186
- width: 100%;
2207
+ width: var(--select-width, 100%);
2208
+ height: var(--select-height, auto);
2187
2209
  }
2188
2210
 
2189
2211
  .select-container {
@@ -2199,6 +2221,7 @@
2199
2221
  flex-wrap: wrap;
2200
2222
  gap: var(--select-input-gap, 6px);
2201
2223
  padding: var(--select-input-padding, 6px 52px 6px 8px);
2224
+ height: var(--select-input-height, auto);
2202
2225
  min-height: var(--select-input-min-height, 44px);
2203
2226
  max-height: var(--select-input-max-height, 160px);
2204
2227
  overflow-y: var(--select-input-overflow-y, auto);
@@ -2220,17 +2243,17 @@
2220
2243
  content: '';
2221
2244
  position: absolute;
2222
2245
  top: 50%;
2223
- right: var(--select-separator-position, 40px);
2246
+ right: var(--select-separator-position, var(--select-seperator-position, 40px));
2224
2247
  transform: translateY(-50%);
2225
- width: var(--select-separator-width, 1px);
2226
- height: var(--select-separator-height, 60%);
2227
- background: var(--select-separator-bg, var(--select-separator-gradient, linear-gradient(
2248
+ width: var(--select-separator-width, var(--select-seperator-width, 1px));
2249
+ height: var(--select-separator-height, var(--select-seperator-height, 60%));
2250
+ background: var(--select-separator-bg, var(--select-seperator-bg, var(--select-separator-gradient, var(--select-seperator-gradient, linear-gradient(
2228
2251
  to bottom,
2229
2252
  transparent 0%,
2230
2253
  rgba(0, 0, 0, 0.1) 20%,
2231
2254
  rgba(0, 0, 0, 0.1) 80%,
2232
2255
  transparent 100%
2233
- )));
2256
+ ))));
2234
2257
  pointer-events: none;
2235
2258
  z-index: 1;
2236
2259
  }
@@ -2241,6 +2264,8 @@
2241
2264
  right: 0;
2242
2265
  bottom: 0;
2243
2266
  width: var(--select-arrow-width, 40px);
2267
+ /* allow explicit height override even though container normally stretches */
2268
+ height: var(--select-arrow-height, auto);
2244
2269
  display: flex;
2245
2270
  align-items: center;
2246
2271
  justify-content: center;
@@ -2255,7 +2280,7 @@
2255
2280
  }
2256
2281
 
2257
2282
  .input-container.has-clear-control::after {
2258
- right: var(--select-separator-position-with-clear, 72px);
2283
+ right: var(--select-separator-position-with-clear, var(--select-seperator-position-with-clear, 72px));
2259
2284
  }
2260
2285
 
2261
2286
  .dropdown-arrow-container.with-clear-control {
@@ -2306,9 +2331,14 @@
2306
2331
  background-color: var(--select-arrow-hover-bg, rgba(102, 126, 234, 0.08));
2307
2332
  }
2308
2333
 
2334
+ .dropdown-arrow:hover {
2335
+ /* legacy alias --select-arrow-hover for icon color */
2336
+ color: var(--select-arrow-hover, var(--select-arrow-hover-color, #667eea));
2337
+ }
2338
+
2309
2339
  .dropdown-arrow {
2310
- width: var(--select-arrow-size, 16px);
2311
- height: var(--select-arrow-size, 16px);
2340
+ width: var(--select-arrow-width, var(--select-arrow-size, 16px));
2341
+ height: var(--select-arrow-height, var(--select-arrow-size, 16px));
2312
2342
  color: var(--select-arrow-color, #667eea);
2313
2343
  transition: transform 0.2s ease, color 0.2s ease;
2314
2344
  transform: translateY(0);
@@ -2328,6 +2358,7 @@
2328
2358
 
2329
2359
  .select-input {
2330
2360
  flex: 1;
2361
+ width: var(--select-input-width, auto);
2331
2362
  min-width: var(--select-input-min-width, 120px);
2332
2363
  padding: var(--select-input-field-padding, 4px);
2333
2364
  border: none;
@@ -2422,6 +2453,7 @@
2422
2453
  font-weight: var(--select-group-header-weight, 600);
2423
2454
  color: var(--select-group-header-color, #6b7280);
2424
2455
  background-color: var(--select-group-header-bg, #f3f4f6);
2456
+ text-align: var(--select-group-header-text-align, left);
2425
2457
  font-size: var(--select-group-header-font-size, 12px);
2426
2458
  text-transform: var(--select-group-header-text-transform, uppercase);
2427
2459
  letter-spacing: var(--select-group-header-letter-spacing, 0.05em);
@@ -2606,10 +2638,25 @@
2606
2638
 
2607
2639
  /* Dark mode - Opt-in via class, data attribute, or ancestor context */
2608
2640
  :host(.dark-mode),
2641
+ :host([dark-mode]),
2642
+ :host([darkmode]),
2609
2643
  :host([data-theme="dark"]),
2644
+ :host([theme="dark"]),
2610
2645
  :host-context(.dark-mode),
2611
2646
  :host-context(.dark),
2612
- :host-context([data-theme="dark"]) {
2647
+ :host-context([dark-mode]),
2648
+ :host-context([darkmode]),
2649
+ :host-context([data-theme="dark"]),
2650
+ :host-context([theme="dark"]) {
2651
+ /* map dark tokens to base option tokens so nested <select-option>
2652
+ components also pick up dark mode via inherited CSS variables */
2653
+ --select-option-bg: var(--select-dark-option-bg, #1f2937);
2654
+ --select-option-color: var(--select-dark-option-color, #f9fafb);
2655
+ --select-option-hover-bg: var(--select-dark-option-hover-bg, #374151);
2656
+ --select-option-hover-color: var(--select-dark-option-hover-color, #f9fafb);
2657
+ --select-option-selected-bg: var(--select-dark-option-selected-bg, #3730a3);
2658
+ --select-option-selected-color: var(--select-dark-option-selected-text, #e0e7ff);
2659
+
2613
2660
  .input-container {
2614
2661
  background: var(--select-dark-bg, #1f2937);
2615
2662
  border-color: var(--select-dark-border, #4b5563);
@@ -2666,6 +2713,12 @@
2666
2713
  outline: var(--select-dark-option-active-outline, 2px solid rgba(129, 140, 248, 0.55));
2667
2714
  }
2668
2715
 
2716
+ /* Group header in dark mode */
2717
+ .group-header {
2718
+ color: var(--select-dark-group-header-color, var(--select-group-header-color, #6b7280));
2719
+ background-color: var(--select-dark-group-header-bg, var(--select-group-header-bg, #374151));
2720
+ }
2721
+
2669
2722
  .option.selected.active {
2670
2723
  background-color: var(--select-dark-option-selected-active-bg, var(--select-dark-option-selected-bg, #3730a3));
2671
2724
  color: var(--select-dark-option-selected-active-color, var(--select-dark-option-selected-text, #e0e7ff));
@@ -2744,19 +2797,14 @@
2744
2797
  this._boundArrowClick = (e) => {
2745
2798
  e.stopPropagation();
2746
2799
  e.preventDefault();
2747
- const wasOpen = this._state.isOpen;
2748
- this._state.isOpen = !this._state.isOpen;
2749
- this._updateDropdownVisibility();
2750
- this._updateArrowRotation();
2751
- if (this._state.isOpen && this._config.callbacks.onOpen) {
2752
- this._config.callbacks.onOpen();
2753
- }
2754
- else if (!this._state.isOpen && this._config.callbacks.onClose) {
2755
- this._config.callbacks.onClose();
2800
+ // delegate to the existing open/close helpers so we don't accidentally
2801
+ // drift out of sync with the logic in those methods (focus, events,
2802
+ // scroll-to-selected, etc.)
2803
+ if (this._state.isOpen) {
2804
+ this._handleClose();
2756
2805
  }
2757
- // Scroll to selected when opening
2758
- if (!wasOpen && this._state.isOpen && this._state.selectedIndices.size > 0) {
2759
- setTimeout(() => this._scrollToSelected(), 50);
2806
+ else {
2807
+ this._handleOpen();
2760
2808
  }
2761
2809
  };
2762
2810
  this._arrowContainer.addEventListener('click', this._boundArrowClick);
@@ -2790,6 +2838,16 @@
2790
2838
  if (wasClosed) {
2791
2839
  this._handleOpen();
2792
2840
  }
2841
+ else {
2842
+ // Keep open while interacting directly with the input so users can
2843
+ // place cursor/type without accidental collapse.
2844
+ if (target === this._input) {
2845
+ this._input.focus();
2846
+ return;
2847
+ }
2848
+ // clicking other parts of the input container while open toggles close
2849
+ this._handleClose();
2850
+ }
2793
2851
  // Focus the input (do not prevent default behavior)
2794
2852
  this._input.focus();
2795
2853
  // If we just opened the dropdown, transfer pointer capture to the
@@ -2965,6 +3023,16 @@
2965
3023
  _handleOpen() {
2966
3024
  if (!this._config.enabled || this._state.isOpen)
2967
3025
  return;
3026
+ // close any other open selects before proceeding
3027
+ EnhancedSelect._instances.forEach(inst => {
3028
+ if (inst !== this)
3029
+ inst._handleClose();
3030
+ });
3031
+ // Always focus the input when opening so callers (arrow click,
3032
+ // programmatic `open()`, etc.) get the keyboard cursor. This was a
3033
+ // frequent source of confusion in #14 where people opened the dropdown
3034
+ // but the text field never received focus.
3035
+ this._input.focus();
2968
3036
  this._markOpenStart();
2969
3037
  this._state.isOpen = true;
2970
3038
  this._dropdown.style.display = 'block';
@@ -3938,10 +4006,25 @@
3938
4006
  const query = this._state.searchQuery.toLowerCase();
3939
4007
  // Handle Grouped Items Rendering (when no search query)
3940
4008
  if (this._state.groupedItems.length > 0 && !query) {
3941
- this._state.groupedItems.forEach(group => {
3942
- const header = document.createElement('div');
3943
- header.className = 'group-header';
3944
- header.textContent = group.label;
4009
+ this._state.groupedItems.forEach((group, groupIndex) => {
4010
+ let header;
4011
+ if (this.groupHeaderRenderer) {
4012
+ header = this.groupHeaderRenderer(group, groupIndex);
4013
+ // make sure the returned element has the correct semantics so
4014
+ // people can style it. we add the class/part even if the renderer
4015
+ // returned something else to ensure backward compatibility.
4016
+ if (!(header instanceof HTMLElement)) {
4017
+ // fall back to default if API is misused
4018
+ header = document.createElement('div');
4019
+ header.textContent = String(group.label);
4020
+ }
4021
+ }
4022
+ else {
4023
+ header = document.createElement('div');
4024
+ header.textContent = group.label;
4025
+ }
4026
+ header.classList.add('group-header');
4027
+ header.setAttribute('part', 'group-header');
3945
4028
  this._optionsContainer.appendChild(header);
3946
4029
  group.options.forEach(item => {
3947
4030
  // Find original index for correct ID generation and selection
@@ -4214,6 +4297,8 @@
4214
4297
  }
4215
4298
  }
4216
4299
  }
4300
+ /** live set of all connected instances; used to auto-close siblings */
4301
+ EnhancedSelect._instances = new Set();
4217
4302
  // Register custom element
4218
4303
  if (!customElements.get('enhanced-select')) {
4219
4304
  customElements.define('enhanced-select', EnhancedSelect);