@smilodon/core 1.4.11 → 1.4.12

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.cjs CHANGED
@@ -1403,6 +1403,18 @@ const defaultConfig = {
1403
1403
  icon: '×',
1404
1404
  },
1405
1405
  callbacks: {},
1406
+ tracking: {
1407
+ enabled: false,
1408
+ events: true,
1409
+ styling: true,
1410
+ limitations: true,
1411
+ emitDiagnostics: false,
1412
+ maxEntries: 200,
1413
+ },
1414
+ limitations: {
1415
+ policies: {},
1416
+ autoMitigateRuntimeModeSwitch: true,
1417
+ },
1406
1418
  enabled: true,
1407
1419
  searchable: false,
1408
1420
  placeholder: 'Select an option...',
@@ -1879,6 +1891,10 @@ class EnhancedSelect extends HTMLElement {
1879
1891
  set classMap(map) {
1880
1892
  this._classMap = map;
1881
1893
  this._setGlobalStylesMirroring(Boolean(this._optionRenderer || map || this._groupHeaderRenderer));
1894
+ this._track('style', 'classMapChanged', {
1895
+ hasClassMap: Boolean(map),
1896
+ keys: map ? Object.keys(map) : [],
1897
+ });
1882
1898
  if (!this.isConnected)
1883
1899
  return;
1884
1900
  this._renderOptions();
@@ -1894,6 +1910,7 @@ class EnhancedSelect extends HTMLElement {
1894
1910
  set groupHeaderRenderer(renderer) {
1895
1911
  this._groupHeaderRenderer = renderer;
1896
1912
  this._setGlobalStylesMirroring(Boolean(this._optionRenderer || this._classMap || renderer));
1913
+ this._track('style', 'groupHeaderRendererChanged', { enabled: Boolean(renderer) });
1897
1914
  if (!this.isConnected)
1898
1915
  return;
1899
1916
  this._renderOptions();
@@ -1912,6 +1929,7 @@ class EnhancedSelect extends HTMLElement {
1912
1929
  this._mirrorGlobalStylesForCustomOptions = false;
1913
1930
  this._globalStylesObserver = null;
1914
1931
  this._globalStylesContainer = null;
1932
+ this._tracking = { events: [], styles: [], limitations: [] };
1915
1933
  this._shadow = this.attachShadow({ mode: 'open' });
1916
1934
  this._uniqueId = `enhanced-select-${Math.random().toString(36).substr(2, 9)}`;
1917
1935
  this._rendererHelpers = this._buildRendererHelpers();
@@ -1996,6 +2014,7 @@ class EnhancedSelect extends HTMLElement {
1996
2014
  return;
1997
2015
  }
1998
2016
  this._mirrorGlobalStylesForCustomOptions = enabled;
2017
+ this._track('style', 'globalStylesMirroringChanged', { enabled });
1999
2018
  if (enabled) {
2000
2019
  this._setupGlobalStylesMirroring();
2001
2020
  }
@@ -3676,6 +3695,162 @@ class EnhancedSelect extends HTMLElement {
3676
3695
  }
3677
3696
  _emit(name, detail) {
3678
3697
  this.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true }));
3698
+ if (name !== 'diagnostic') {
3699
+ this._track('event', String(name), detail);
3700
+ }
3701
+ }
3702
+ _track(source, name, detail) {
3703
+ const cfg = this._config.tracking;
3704
+ if (!cfg?.enabled)
3705
+ return;
3706
+ if (source === 'event' && !cfg.events)
3707
+ return;
3708
+ if (source === 'style' && !cfg.styling)
3709
+ return;
3710
+ if (source === 'limitation' && !cfg.limitations)
3711
+ return;
3712
+ const entry = {
3713
+ timestamp: Date.now(),
3714
+ source,
3715
+ name,
3716
+ detail,
3717
+ };
3718
+ const bucket = source === 'event'
3719
+ ? this._tracking.events
3720
+ : source === 'style'
3721
+ ? this._tracking.styles
3722
+ : this._tracking.limitations;
3723
+ bucket.push(entry);
3724
+ const maxEntries = Math.max(10, cfg.maxEntries || 200);
3725
+ if (bucket.length > maxEntries) {
3726
+ bucket.splice(0, bucket.length - maxEntries);
3727
+ }
3728
+ if (cfg.emitDiagnostics) {
3729
+ this.dispatchEvent(new CustomEvent('diagnostic', {
3730
+ detail: entry,
3731
+ bubbles: true,
3732
+ composed: true,
3733
+ }));
3734
+ }
3735
+ }
3736
+ _getKnownLimitationDefinitions() {
3737
+ return [
3738
+ {
3739
+ id: 'variableItemHeight',
3740
+ title: 'Variable item height',
3741
+ description: 'Virtualization assumes fixed or estimated item heights; fully dynamic heights are not yet supported.',
3742
+ workaround: 'Use consistent item heights or set estimatedItemHeight to your dominant row size.',
3743
+ },
3744
+ {
3745
+ id: 'builtInFetchPaginationApi',
3746
+ title: 'Built-in fetch/pagination API',
3747
+ description: 'Core does not include a built-in fetchUrl/searchUrl pagination transport.',
3748
+ workaround: 'Use onSearch/onLoadMore callbacks and update data via setItems().',
3749
+ },
3750
+ {
3751
+ id: 'virtualizationOverheadSmallLists',
3752
+ title: 'Virtualization overhead for small lists',
3753
+ description: 'Virtualization can add slight overhead on very small lists.',
3754
+ workaround: 'Disable virtualization for tiny datasets when micro-latency is critical.',
3755
+ },
3756
+ {
3757
+ id: 'runtimeModeSwitching',
3758
+ title: 'Runtime single/multi mode switching',
3759
+ description: 'Switching between single and multi mode can require state reset for consistency.',
3760
+ workaround: 'Enable autoMitigateRuntimeModeSwitch or recreate/reset component state when toggling modes.',
3761
+ },
3762
+ {
3763
+ id: 'legacyBrowserSupport',
3764
+ title: 'Legacy browser support',
3765
+ description: 'Official support targets modern evergreen browsers.',
3766
+ },
3767
+ {
3768
+ id: 'webkitArchLinux',
3769
+ title: 'Playwright WebKit on Arch-based Linux',
3770
+ description: 'Native WebKit Playwright bundle depends on unavailable legacy system libraries on Arch-based distros.',
3771
+ workaround: 'Run WebKit E2E tests via Playwright Docker image.',
3772
+ },
3773
+ ];
3774
+ }
3775
+ _evaluateLimitationStatus(id) {
3776
+ const policyMode = this._config.limitations?.policies?.[id]?.mode ?? 'default';
3777
+ if (policyMode === 'suppress')
3778
+ return 'suppressed';
3779
+ if (id === 'runtimeModeSwitching' && this._config.limitations?.autoMitigateRuntimeModeSwitch) {
3780
+ return 'mitigated';
3781
+ }
3782
+ return 'active';
3783
+ }
3784
+ getKnownLimitations() {
3785
+ return this._getKnownLimitationDefinitions().map((limitation) => {
3786
+ const mode = this._config.limitations?.policies?.[limitation.id]?.mode ?? 'default';
3787
+ return {
3788
+ ...limitation,
3789
+ mode,
3790
+ status: this._evaluateLimitationStatus(limitation.id),
3791
+ };
3792
+ });
3793
+ }
3794
+ setLimitationPolicies(policies) {
3795
+ const next = {
3796
+ ...(this._config.limitations?.policies || {}),
3797
+ ...policies,
3798
+ };
3799
+ this.updateConfig({
3800
+ limitations: {
3801
+ ...(this._config.limitations || { autoMitigateRuntimeModeSwitch: true, policies: {} }),
3802
+ policies: next,
3803
+ },
3804
+ });
3805
+ this._track('limitation', 'policiesUpdated', { policies: next });
3806
+ }
3807
+ getTrackingSnapshot() {
3808
+ return {
3809
+ events: [...this._tracking.events],
3810
+ styles: [...this._tracking.styles],
3811
+ limitations: [...this._tracking.limitations],
3812
+ };
3813
+ }
3814
+ clearTracking(source) {
3815
+ if (!source || source === 'all') {
3816
+ this._tracking.events = [];
3817
+ this._tracking.styles = [];
3818
+ this._tracking.limitations = [];
3819
+ return;
3820
+ }
3821
+ if (source === 'event')
3822
+ this._tracking.events = [];
3823
+ if (source === 'style')
3824
+ this._tracking.styles = [];
3825
+ if (source === 'limitation')
3826
+ this._tracking.limitations = [];
3827
+ }
3828
+ getCapabilities() {
3829
+ return {
3830
+ styling: {
3831
+ classMap: true,
3832
+ optionRenderer: true,
3833
+ groupHeaderRenderer: true,
3834
+ cssCustomProperties: true,
3835
+ shadowParts: true,
3836
+ globalStyleMirroring: true,
3837
+ },
3838
+ events: {
3839
+ emitted: ['select', 'open', 'close', 'search', 'change', 'loadMore', 'remove', 'clear', 'error', 'diagnostic'],
3840
+ diagnosticEvent: true,
3841
+ },
3842
+ functionality: {
3843
+ multiSelect: true,
3844
+ searchable: true,
3845
+ infiniteScroll: true,
3846
+ loadMore: true,
3847
+ clearControl: true,
3848
+ groupedItems: true,
3849
+ serverSideSelection: true,
3850
+ runtimeModeSwitchMitigation: Boolean(this._config.limitations?.autoMitigateRuntimeModeSwitch),
3851
+ },
3852
+ limitations: this.getKnownLimitations(),
3853
+ };
3679
3854
  }
3680
3855
  _emitChange() {
3681
3856
  const selectedItems = Array.from(this._state.selectedItems.values());
@@ -3693,6 +3868,7 @@ class EnhancedSelect extends HTMLElement {
3693
3868
  set optionRenderer(renderer) {
3694
3869
  this._optionRenderer = renderer;
3695
3870
  this._setGlobalStylesMirroring(Boolean(renderer || this._classMap));
3871
+ this._track('style', 'optionRendererChanged', { enabled: Boolean(renderer) });
3696
3872
  this._renderOptions();
3697
3873
  }
3698
3874
  /**
@@ -3857,7 +4033,22 @@ class EnhancedSelect extends HTMLElement {
3857
4033
  * Update component configuration
3858
4034
  */
3859
4035
  updateConfig(config) {
3860
- this._config = selectConfig.mergeWithComponentConfig(config);
4036
+ const previousMode = this._config.selection.mode;
4037
+ this._config = this._mergeConfig(this._config, config);
4038
+ if (previousMode !== this._config.selection.mode &&
4039
+ this._config.limitations?.autoMitigateRuntimeModeSwitch) {
4040
+ this.clear();
4041
+ this._track('limitation', 'runtimeModeSwitchMitigated', {
4042
+ from: previousMode,
4043
+ to: this._config.selection.mode,
4044
+ });
4045
+ }
4046
+ else if (previousMode !== this._config.selection.mode) {
4047
+ this._track('limitation', 'runtimeModeSwitchDetected', {
4048
+ from: previousMode,
4049
+ to: this._config.selection.mode,
4050
+ });
4051
+ }
3861
4052
  // Update input state based on new config
3862
4053
  if (this._input) {
3863
4054
  this._input.readOnly = !this._config.searchable;
@@ -3885,6 +4076,22 @@ class EnhancedSelect extends HTMLElement {
3885
4076
  this._syncClearControlState();
3886
4077
  this._renderOptions();
3887
4078
  }
4079
+ _mergeConfig(target, source) {
4080
+ const result = { ...target };
4081
+ for (const key in source) {
4082
+ if (!Object.prototype.hasOwnProperty.call(source, key))
4083
+ continue;
4084
+ const sourceValue = source[key];
4085
+ const targetValue = result[key];
4086
+ if (sourceValue && typeof sourceValue === 'object' && !Array.isArray(sourceValue)) {
4087
+ result[key] = this._mergeConfig(targetValue && typeof targetValue === 'object' ? targetValue : {}, sourceValue);
4088
+ }
4089
+ else {
4090
+ result[key] = sourceValue;
4091
+ }
4092
+ }
4093
+ return result;
4094
+ }
3888
4095
  _handleClearControlClick() {
3889
4096
  const shouldClearSelection = this._config.clearControl.clearSelection !== false;
3890
4097
  const shouldClearSearch = this._config.clearControl.clearSearch !== false;