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