@jetbrains/ring-ui 7.0.50 → 7.0.51

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.
@@ -1,6 +1,7 @@
1
1
  @import "../global/variables.css";
2
2
 
3
3
  @value header, scrollableWrapper from "../island/island.css";
4
+ @value panel from "../panel/panel.css";
4
5
 
5
6
  .container {
6
7
  position: fixed;
@@ -1,8 +1,6 @@
1
1
  @import "../global/variables.css";
2
2
 
3
3
  .panel {
4
- composes: panel from "../dialog/dialog.css"; /* TODO: Invert dependency using :externals CSS Modules selector */
5
-
6
4
  position: relative;
7
5
 
8
6
  margin-top: calc(var(--ring-unit) * 2);
@@ -138,7 +138,7 @@ export interface SelectState<T = unknown> {
138
138
  data: SelectItem<T>[];
139
139
  shownData: SelectItem<T>[];
140
140
  selected: SelectItem<T> | SelectItem<T>[] | null | undefined;
141
- selectedIndex: number | null;
141
+ lastInteractedKey: string | number | null;
142
142
  filterValue: string;
143
143
  shortcutsEnabled: boolean;
144
144
  popupShortcuts: boolean;
@@ -226,7 +226,7 @@ export default class Select<T = unknown> extends Component<SelectProps<T>, Selec
226
226
  private _onEsc;
227
227
  _inputShortcutHandler: () => void;
228
228
  getValueForFilter(selected: SelectItem<T> | readonly SelectItem<T>[] | null | undefined): string;
229
- _getSelectedIndex(selected: SelectItem<T> | readonly SelectItem<T>[] | null | undefined, data: readonly SelectItem<T>[]): number | null;
229
+ private _getActiveIndex;
230
230
  popupRef: (el: SelectPopup<SelectItemData<T>> | null) => void;
231
231
  _getResetOption(): SelectItem<T> | null;
232
232
  _prependResetOption(shownData: SelectItem<T>[]): SelectItem<T>[];
@@ -135,22 +135,6 @@ function getListItems(props, state, rawFilterString, data = props.data) {
135
135
  }
136
136
  return { filteredData, addButton };
137
137
  }
138
- function getSelectedIndex(selected, data) {
139
- const firstSelected = Array.isArray(selected) ? selected[0] : selected;
140
- if (firstSelected == null) {
141
- return null;
142
- }
143
- for (let i = 0; i < data.length; i++) {
144
- const item = data[i];
145
- if (item.key === undefined) {
146
- continue;
147
- }
148
- if (item.key === firstSelected.key) {
149
- return i;
150
- }
151
- }
152
- return null;
153
- }
154
138
  const getItemLabel = ({ selectedLabel, label }) => {
155
139
  if (selectedLabel != null) {
156
140
  return selectedLabel;
@@ -158,16 +142,6 @@ const getItemLabel = ({ selectedLabel, label }) => {
158
142
  return typeof label === 'string' ? label : '';
159
143
  };
160
144
  const getValueForFilter = (selected, type, filterValue) => (selected && !isArray(selected) && isInputMode(type) ? getItemLabel(selected) : filterValue);
161
- function isSameSelected(prevSelected, selected) {
162
- if (!prevSelected || !selected || prevSelected.length !== selected.length) {
163
- return false;
164
- }
165
- const keysMap = selected.reduce((result, item) => {
166
- result[item.key] = true;
167
- return result;
168
- }, {});
169
- return prevSelected.every(it => keysMap[it.key]);
170
- }
171
145
  /**
172
146
  * @name Select
173
147
  * @constructor
@@ -239,21 +213,16 @@ export default class Select extends Component {
239
213
  Object.assign(nextState, { shownData: filteredData, addButton });
240
214
  if (prevState.selected) {
241
215
  Object.assign(nextState, {
242
- selectedIndex: getSelectedIndex(prevState.selected, data),
243
216
  filterValue: getValueForFilter(prevState.selected, type, filterValue),
244
217
  });
245
218
  }
246
219
  }
247
220
  if ('selected' in nextProps && nextProps.selected !== prevSelected) {
248
221
  const selected = nextProps.selected || (multiple ? [] : null);
249
- const selectedIndex = getSelectedIndex(selected, data || prevData);
250
222
  Object.assign(nextState, {
251
223
  selected,
252
224
  filterValue: getValueForFilter(selected, type, filterValue),
253
225
  });
254
- if (!Array.isArray(prevSelected) || !Array.isArray(selected) || !isSameSelected(prevSelected, selected)) {
255
- Object.assign(nextState, { selectedIndex });
256
- }
257
226
  }
258
227
  if (prevMultiple !== multiple && !dequal(prevMultiple, multiple)) {
259
228
  nextState.selected = multiple ? [] : null;
@@ -267,13 +236,17 @@ export default class Select extends Component {
267
236
  const { filteredData, addButton } = getListItems(nextProps, nextState, filterValue, data);
268
237
  Object.assign(nextState, { shownData: filteredData, addButton });
269
238
  }
239
+ const isSelectionEmpty = nextProps.selected == null || (Array.isArray(nextProps.selected) && nextProps.selected.length === 0);
240
+ if (isSelectionEmpty) {
241
+ nextState.lastInteractedKey = null;
242
+ }
270
243
  return nextState;
271
244
  }
272
245
  state = {
273
246
  data: [],
274
247
  shownData: [],
275
248
  selected: this.props.multiple ? [] : null,
276
- selectedIndex: null,
249
+ lastInteractedKey: null,
277
250
  filterValue: (this.props.filter && typeof this.props.filter === 'object' && this.props.filter.value) || '',
278
251
  shortcutsEnabled: false,
279
252
  popupShortcuts: false,
@@ -384,8 +357,28 @@ export default class Select extends Component {
384
357
  getValueForFilter(selected) {
385
358
  return getValueForFilter(selected, this.props.type, this.state.filterValue);
386
359
  }
387
- _getSelectedIndex(selected, data) {
388
- return getSelectedIndex(selected, data);
360
+ _getActiveIndex(items) {
361
+ const { selected, lastInteractedKey } = this.state;
362
+ const isNonOptionItem = (item) => item.isResetItem || List.isItemType(List.ListProps.Type.SEPARATOR, item);
363
+ if (lastInteractedKey != null) {
364
+ const index = items.findIndex(item => item.key === lastInteractedKey && !isNonOptionItem(item));
365
+ if (index >= 0)
366
+ return index;
367
+ }
368
+ let selectedItems = [];
369
+ if (Array.isArray(selected)) {
370
+ selectedItems = selected;
371
+ }
372
+ else if (selected) {
373
+ selectedItems = [selected];
374
+ }
375
+ if (selectedItems.length > 0) {
376
+ const lastSelected = selectedItems[selectedItems.length - 1];
377
+ const index = items.findIndex(item => item.key === lastSelected.key);
378
+ if (index >= 0)
379
+ return index;
380
+ }
381
+ return items.findIndex(item => !isNonOptionItem(item));
389
382
  }
390
383
  popupRef = (el) => {
391
384
  this._popup = el;
@@ -433,6 +426,7 @@ export default class Select extends Component {
433
426
  const anchorElement = this.props.targetElement || this.node;
434
427
  const { showPopup, shownData } = this.state;
435
428
  const _shownData = this._prependResetOption(shownData);
429
+ const activeIndex = this._getActiveIndex(_shownData);
436
430
  return (_jsx(I18nContext.Consumer, { children: ({ translate }) => {
437
431
  let message;
438
432
  if (this.props.loading) {
@@ -441,7 +435,7 @@ export default class Select extends Component {
441
435
  else if (!shownData.length) {
442
436
  message = this.props.notFoundMessage ?? translate('noOptionsFound');
443
437
  }
444
- return (_jsx(SelectPopup, { data: _shownData, message: message, toolbar: showPopup && this.getToolbar(), topbar: this.getTopbar(), loading: this.props.loading, activeIndex: this.state.selectedIndex, hidden: !showPopup, ref: this.popupRef, maxHeight: this.props.maxHeight, minWidth: this.props.minWidth, directions: this.props.directions, className: this.props.popupClassName, style: this.props.popupStyle, top: this.props.top, left: this.props.left, offset: this.props.offset, filter: this.isInputMode() ? false : this.props.filter, filterIcon: this.props.filterIcon, filterRef: this.props.filterRef, multiple: this.props.multiple, filterValue: this.state.filterValue, anchorElement: anchorElement, onCloseAttempt: this._onCloseAttempt, onOutsideClick: this.props.onOutsideClick, onSelect: this._listSelectHandler, onSelectAll: this._listSelectAllHandler, onFilter: this._filterChangeHandler, onClear: this.clearFilter, onLoadMore: this.props.onLoadMore, isInputMode: this.isInputMode(), selected: this.state.selected, tags: this.props.tags, compact: this.props.compact, renderOptimization: this.props.renderOptimization, ringPopupTarget: this.props.ringPopupTarget, disableMoveOverflow: this.props.disableMoveOverflow, disableScrollToActive: this.props.disableScrollToActive, dir: this.props.dir, onEmptyPopupEnter: this.onEmptyPopupEnter, listId: this.listId, preventListOverscroll: this.props.preventListOverscroll }));
438
+ return (_jsx(SelectPopup, { data: _shownData, message: message, toolbar: showPopup && this.getToolbar(), topbar: this.getTopbar(), loading: this.props.loading, activeIndex: activeIndex, hidden: !showPopup, ref: this.popupRef, maxHeight: this.props.maxHeight, minWidth: this.props.minWidth, directions: this.props.directions, className: this.props.popupClassName, style: this.props.popupStyle, top: this.props.top, left: this.props.left, offset: this.props.offset, filter: this.isInputMode() ? false : this.props.filter, filterIcon: this.props.filterIcon, filterRef: this.props.filterRef, multiple: this.props.multiple, filterValue: this.state.filterValue, anchorElement: anchorElement, onCloseAttempt: this._onCloseAttempt, onOutsideClick: this.props.onOutsideClick, onSelect: this._listSelectHandler, onSelectAll: this._listSelectAllHandler, onFilter: this._filterChangeHandler, onClear: this.clearFilter, onLoadMore: this.props.onLoadMore, isInputMode: this.isInputMode(), selected: this.state.selected, tags: this.props.tags, compact: this.props.compact, renderOptimization: this.props.renderOptimization, ringPopupTarget: this.props.ringPopupTarget, disableMoveOverflow: this.props.disableMoveOverflow, disableScrollToActive: this.props.disableScrollToActive, dir: this.props.dir, onEmptyPopupEnter: this.onEmptyPopupEnter, listId: this.listId, preventListOverscroll: this.props.preventListOverscroll }));
445
439
  } }));
446
440
  }
447
441
  _showPopup = () => {
@@ -459,6 +453,7 @@ export default class Select extends Component {
459
453
  this.setState(prevState => ({
460
454
  showPopup: false,
461
455
  filterValue: this.props.allowAny ? prevState.filterValue : '',
456
+ lastInteractedKey: null,
462
457
  }));
463
458
  if (tryFocusAnchor) {
464
459
  this.focus();
@@ -543,7 +538,6 @@ export default class Select extends Component {
543
538
  };
544
539
  this.setState({
545
540
  selected: filterValue === '' ? null : fakeSelected,
546
- selectedIndex: null,
547
541
  }, () => {
548
542
  this.props.onSelect(fakeSelected, event);
549
543
  this.props.onChange(fakeSelected, event);
@@ -582,6 +576,7 @@ export default class Select extends Component {
582
576
  if ((!isItem(selected) && !isCustomItem(selected)) || selected.disabled || selected.isResetItem) {
583
577
  return;
584
578
  }
579
+ const lastInteractedKey = selected?.key ?? null;
585
580
  const tryKeepOpen = this.props.tryKeepOpen ?? opts.tryKeepOpen;
586
581
  if (!this.props.multiple) {
587
582
  if (!tryKeepOpen) {
@@ -589,7 +584,7 @@ export default class Select extends Component {
589
584
  }
590
585
  this.setState({
591
586
  selected,
592
- selectedIndex: this._getSelectedIndex(selected, this.props.data),
587
+ lastInteractedKey: selected?.key ?? null,
593
588
  }, () => {
594
589
  const newFilterValue = this.isInputMode() && !this.props.hideSelected ? getItemLabel(selected) : '';
595
590
  this.filterValue(newFilterValue);
@@ -617,7 +612,7 @@ export default class Select extends Component {
617
612
  const nextState = {
618
613
  filterValue: '',
619
614
  selected: nextSelection,
620
- selectedIndex: this._getSelectedIndex(selected, this.props.data),
615
+ lastInteractedKey: tryKeepOpen ? lastInteractedKey : null,
621
616
  };
622
617
  if (typeof this.props.multiple === 'object' &&
623
618
  this.props.multiple.limit &&
@@ -670,7 +665,6 @@ export default class Select extends Component {
670
665
  return {
671
666
  filterValue: '',
672
667
  selected: nextSelection,
673
- selectedIndex: isSelectAll ? this._getSelectedIndex(nextSelection, this.props.data) : null,
674
668
  shownData: prevState.shownData.map(item => ({ ...item, checkbox: isSelectAll })),
675
669
  multipleMap: isSelectAll ? buildMultipleMap(this.props.data.filter(item => !item.disabled)) : {},
676
670
  };
@@ -706,7 +700,7 @@ export default class Select extends Component {
706
700
  const empty = this.props.multiple ? [] : null;
707
701
  this.setState({
708
702
  selected: empty,
709
- selectedIndex: null,
703
+ lastInteractedKey: null,
710
704
  filterValue: '',
711
705
  }, () => {
712
706
  if (this.props.onChange) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jetbrains/ring-ui",
3
- "version": "7.0.50",
3
+ "version": "7.0.51",
4
4
  "description": "JetBrains UI library",
5
5
  "author": {
6
6
  "name": "JetBrains"
@@ -67,7 +67,7 @@
67
67
  "figma-connect-unpublish": "npx figma connect unpublish --token=$FIGMA_CODE_CONNECT_TOKEN",
68
68
  "figma-connect-unpublish-local": "dotenv -- npm run figma-connect-unpublish",
69
69
  "prebuild": "rimraf components && tsc --project tsconfig-build.json && cpy './**/*' '!**/*.stories.*' '!**/*.ts' '!**/*.tsx' '!**/__mocks__/**' ../components --parents --cwd=src/",
70
- "build": "./node_modules/.bin/rollup -c --bundleConfigAsCjs",
70
+ "build": "BROWSERSLIST_ENV=dist ./node_modules/.bin/rollup -c --bundleConfigAsCjs",
71
71
  "postbuild": "cpy './**/*.d.ts' ../dist --parents --cwd=components/",
72
72
  "serve": "http-server storybook-dist/ -p 9999",
73
73
  "start": "storybook dev -p 9999",
@@ -114,12 +114,12 @@
114
114
  "@rollup/plugin-json": "^6.1.0",
115
115
  "@rollup/plugin-node-resolve": "^16.0.1",
116
116
  "@rollup/plugin-replace": "^6.0.2",
117
- "@storybook/addon-a11y": "9.0.6",
118
- "@storybook/addon-docs": "^9.0.6",
119
- "@storybook/addon-themes": "^9.0.6",
117
+ "@storybook/addon-a11y": "9.0.9",
118
+ "@storybook/addon-docs": "^9.0.9",
119
+ "@storybook/addon-themes": "^9.0.9",
120
120
  "@storybook/csf": "^0.1.13",
121
- "@storybook/react-webpack5": "9.0.6",
122
- "@storybook/test-runner": "^0.22.1",
121
+ "@storybook/react-webpack5": "9.0.9",
122
+ "@storybook/test-runner": "^0.23.0",
123
123
  "@testing-library/dom": "^10.4.0",
124
124
  "@testing-library/react": "^16.3.0",
125
125
  "@testing-library/user-event": "^14.6.1",
@@ -132,11 +132,11 @@
132
132
  "@types/react-dom": "^19.1.6",
133
133
  "@types/webpack-env": "^1.18.8",
134
134
  "@vitejs/plugin-react": "^4.5.2",
135
- "@vitest/eslint-plugin": "^1.2.2",
135
+ "@vitest/eslint-plugin": "^1.2.4",
136
136
  "acorn": "^8.15.0",
137
137
  "axe-playwright": "^2.1.0",
138
138
  "babel-plugin-require-context-hook": "^1.0.0",
139
- "caniuse-lite": "^1.0.30001720",
139
+ "caniuse-lite": "^1.0.30001723",
140
140
  "chai": "^5.2.0",
141
141
  "chai-as-promised": "^8.0.1",
142
142
  "chai-dom": "^1.12.1",
@@ -155,15 +155,15 @@
155
155
  "eslint-plugin-prettier": "^5.4.1",
156
156
  "eslint-plugin-react": "^7.37.5",
157
157
  "eslint-plugin-react-hooks": "^5.2.0",
158
- "eslint-plugin-storybook": "^9.0.8",
158
+ "eslint-plugin-storybook": "^9.0.9",
159
159
  "events": "^3.3.0",
160
- "glob": "^11.0.2",
160
+ "glob": "^11.0.3",
161
161
  "globals": "^16.2.0",
162
162
  "html-webpack-plugin": "^5.6.3",
163
163
  "http-server": "^14.1.1",
164
164
  "husky": "^9.1.7",
165
165
  "identity-obj-proxy": "^3.0.0",
166
- "jest": "~29.7.0",
166
+ "jest": "~30.0.0",
167
167
  "jest-environment-jsdom": "^30.0.0",
168
168
  "jest-teamcity": "^1.12.0",
169
169
  "lint-staged": "^16.1.0",
@@ -179,7 +179,7 @@
179
179
  "rollup": "^4.43.0",
180
180
  "rollup-plugin-clear": "^2.0.7",
181
181
  "storage-mock": "^2.1.0",
182
- "storybook": "9.0.6",
182
+ "storybook": "9.0.9",
183
183
  "stylelint": "^16.20.0",
184
184
  "svg-inline-loader": "^0.8.2",
185
185
  "teamcity-service-messages": "^0.1.14",