@laser-ui/components 2.1.3 → 2.2.0

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/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
4
4
 
5
+ # [2.2.0](https://github.com/laser-ui/laser-ui/compare/v2.1.3...v2.2.0) (2025-06-23)
6
+
7
+ ### Bug Fixes
8
+
9
+ - **components:** update `listSize` of `VirtualScroll` ([a38d857](https://github.com/laser-ui/laser-ui/commit/a38d85779850d6f58aeffc55cd8c2be93a0f921c))
10
+
11
+ ### Features
12
+
13
+ - **components:** dropdown support virtual scroll ([3e64aac](https://github.com/laser-ui/laser-ui/commit/3e64aac4a5f2819122600c97e18fd37d2aee50f9))
14
+
5
15
  ## [2.1.3](https://github.com/laser-ui/laser-ui/compare/v2.1.2...v2.1.3) (2025-06-16)
6
16
 
7
17
  ### Bug Fixes
@@ -224,7 +224,7 @@ export function Cascader(props) {
224
224
  title: label,
225
225
  type: 'item',
226
226
  disabled: node === null || node === void 0 ? void 0 : node.origin.disabled,
227
- })), onClick: (id) => {
227
+ })), virtual: virtual, onClick: (id) => {
228
228
  changeSelected((draft) => {
229
229
  draft.splice(draft.findIndex((v) => v === id), 1);
230
230
  });
@@ -104,7 +104,7 @@ export function CascaderPanel(props) {
104
104
  return () => {
105
105
  vsRef.current = null;
106
106
  };
107
- }, enable: virtual !== false, listSize: 264, listPadding: 4, itemRender: (item, index, props) => {
107
+ }, enable: virtual !== false, listSize: 200 + 32 * 2, listPadding: 4, itemRender: (item, index, props) => {
108
108
  let isSelectedAncestry = false;
109
109
  if (item.children && itemSelected) {
110
110
  let node = itemSelected;
@@ -51,7 +51,7 @@ export function CascaderSearchPanel(props) {
51
51
  return () => {
52
52
  vsRef.current = null;
53
53
  };
54
- }, enable: virtual !== false, listSize: 264, listPadding: 4, itemRender: (item, index, props) => {
54
+ }, enable: virtual !== false, listSize: 200 + 32 * 2, listPadding: 4, itemRender: (item, index, props) => {
55
55
  const node = item[TREE_NODE_KEY];
56
56
  let inSelected = node.checked;
57
57
  if (!onlyLeafSelectable) {
@@ -1,26 +1,21 @@
1
1
  import { __rest } from "tslib";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useEventCallback, useRefExtra } from '@laser-ui/hooks';
4
- import { scrollIntoViewIfNeeded } from '@laser-ui/utils';
5
- import { isNumber, isUndefined, nth } from 'lodash';
6
- import { Fragment, useId, useImperativeHandle, useRef, useState } from 'react';
7
- import { DropdownGroup } from './internal/DropdownGroup';
8
- import { DropdownItem as DropdownItemFC } from './internal/DropdownItem';
9
- import { DropdownSub } from './internal/DropdownSub';
10
- import { checkEnableItem, getSameLevelEnableItems } from './utils';
4
+ import { isUndefined, nth } from 'lodash';
5
+ import { useId, useImperativeHandle, useRef, useState } from 'react';
6
+ import { DropdownList } from './internal/DropdownList';
7
+ import { checkEnableItem } from './utils';
11
8
  import { CLASSES } from './vars';
12
- import { useComponentProps, useContainerScrolling, useControlled, useFocusVisible, useNamespace, useNestedPopup, useStyled, useTranslation, useZIndex, } from '../hooks';
9
+ import { useComponentProps, useContainerScrolling, useControlled, useFocusVisible, useNamespace, useNestedPopup, useStyled, useZIndex, } from '../hooks';
13
10
  import { Popup } from '../internal/popup';
14
11
  import { Portal } from '../internal/portal';
15
- import { Separator } from '../separator';
16
12
  import { Transition } from '../transition';
17
13
  import { getVerticalSidePosition, mergeCS } from '../utils';
18
14
  import { TTANSITION_DURING_POPUP, WINDOW_SPACE } from '../vars';
19
15
  export function Dropdown(props) {
20
16
  var _a;
21
- const _b = useComponentProps('Dropdown', props), { ref, children, styleOverrides, styleProvider, list, visible: visibleProp, defaultVisible, trigger = 'hover', placement: placementProp = 'bottom-right', placementFixed = false, arrow = false, escClosable = true, zIndex: zIndexProp, popupRender, onVisibleChange, afterVisibleChange, onClick } = _b, restProps = __rest(_b, ["ref", "children", "styleOverrides", "styleProvider", "list", "visible", "defaultVisible", "trigger", "placement", "placementFixed", "arrow", "escClosable", "zIndex", "popupRender", "onVisibleChange", "afterVisibleChange", "onClick"]);
17
+ const _b = useComponentProps('Dropdown', props), { ref, children, styleOverrides, styleProvider, list, visible: visibleProp, defaultVisible, trigger = 'hover', placement: placementProp = 'bottom-right', placementFixed = false, arrow = false, virtual = false, escClosable = true, zIndex: zIndexProp, popupRender, onVisibleChange, afterVisibleChange, onClick } = _b, restProps = __rest(_b, ["ref", "children", "styleOverrides", "styleProvider", "list", "visible", "defaultVisible", "trigger", "placement", "placementFixed", "arrow", "virtual", "escClosable", "zIndex", "popupRender", "onVisibleChange", "afterVisibleChange", "onClick"]);
22
18
  const namespace = useNamespace();
23
- const { t } = useTranslation();
24
19
  const styled = useStyled(CLASSES, { dropdown: styleProvider === null || styleProvider === void 0 ? void 0 : styleProvider.dropdown, 'dropdown-popup': styleProvider === null || styleProvider === void 0 ? void 0 : styleProvider['dropdown-popup'] }, styleOverrides);
25
20
  const uniqueId = useId();
26
21
  const id = (_a = restProps.id) !== null && _a !== void 0 ? _a : `${namespace}-dropdown-${uniqueId}`;
@@ -28,7 +23,6 @@ export function Dropdown(props) {
28
23
  const getItemId = (id) => `${namespace}-dropdown-item-${id}-${uniqueId}`;
29
24
  const triggerRef = useRefExtra(() => document.getElementById(triggerId));
30
25
  const popupRef = useRef(null);
31
- const ulRef = useRef(null);
32
26
  const dropdownRef = useRef(null);
33
27
  const updateSubPosition = useRef(new Map());
34
28
  const [focusVisible, focusVisibleProps] = useFocusVisible((code) => code.startsWith('Arrow') || ['Home', 'End', 'Enter', 'Space'].includes(code));
@@ -111,139 +105,7 @@ export function Dropdown(props) {
111
105
  e.preventDefault();
112
106
  }
113
107
  };
114
- let handleKeyDown;
115
- const nodes = (() => {
116
- const getNodes = (arr, level, subParents) => arr.map((item) => {
117
- const { id: itemId, title: itemTitle, type: itemType, icon: itemIcon, disabled: itemDisabled = false, separator: itemSeparator, children, } = item;
118
- const newSubParents = itemType === 'sub' ? subParents.concat([item]) : subParents;
119
- const id = getItemId(itemId);
120
- const isFocus = itemId === focusId;
121
- const isEmpty = !(children && children.length > 0);
122
- const popupState = popupIds.find((v) => v.id === itemId);
123
- const handleItemClick = () => {
124
- const close = onClick === null || onClick === void 0 ? void 0 : onClick(itemId, item);
125
- setFocusIds(subParents.map((parentItem) => parentItem.id).concat([itemId]));
126
- if (close !== false) {
127
- changeVisible(false);
128
- }
129
- };
130
- if (isFocus) {
131
- handleKeyDown = (e) => {
132
- var _a, _b, _c, _d;
133
- const sameLevelItems = getSameLevelEnableItems((_b = (_a = nth(subParents, -1)) === null || _a === void 0 ? void 0 : _a.children) !== null && _b !== void 0 ? _b : list);
134
- const focusItem = (val) => {
135
- if (val) {
136
- setFocusIds(subParents.map((parentItem) => parentItem.id).concat([val.id]));
137
- }
138
- };
139
- const scrollToItem = (val) => {
140
- if (val) {
141
- const el = document.getElementById(getItemId(val.id));
142
- if (el && ulRef.current) {
143
- scrollIntoViewIfNeeded(el, ulRef.current);
144
- }
145
- }
146
- };
147
- switch (e.code) {
148
- case 'ArrowUp': {
149
- e.preventDefault();
150
- const index = sameLevelItems.findIndex((sameLevelItem) => sameLevelItem.id === itemId);
151
- const item = nth(sameLevelItems, index - 1);
152
- focusItem(item);
153
- scrollToItem(item);
154
- if (item && ((_c = nth(popupIds, -1)) === null || _c === void 0 ? void 0 : _c.id) === itemId) {
155
- setPopupIds(popupIds.slice(0, -1));
156
- }
157
- break;
158
- }
159
- case 'ArrowDown': {
160
- e.preventDefault();
161
- const index = sameLevelItems.findIndex((sameLevelItem) => sameLevelItem.id === itemId);
162
- const item = nth(sameLevelItems, (index + 1) % sameLevelItems.length);
163
- focusItem(item);
164
- scrollToItem(item);
165
- if (item && ((_d = nth(popupIds, -1)) === null || _d === void 0 ? void 0 : _d.id) === itemId) {
166
- setPopupIds(popupIds.slice(0, -1));
167
- }
168
- break;
169
- }
170
- case 'ArrowLeft': {
171
- e.preventDefault();
172
- setPopupIds(popupIds.slice(0, -1));
173
- const ids = subParents.map((item) => item.id);
174
- if (ids.length > 0) {
175
- setFocusIds(ids);
176
- }
177
- break;
178
- }
179
- case 'ArrowRight':
180
- e.preventDefault();
181
- if (itemType === 'sub') {
182
- addPopupId(itemId);
183
- if (children) {
184
- const newFocusItem = nth(getSameLevelEnableItems(children), 0);
185
- if (newFocusItem) {
186
- setFocusIds(newSubParents.map((parentItem) => parentItem.id).concat([newFocusItem.id]));
187
- }
188
- }
189
- }
190
- break;
191
- case 'Home':
192
- e.preventDefault();
193
- focusItem(nth(sameLevelItems, 0));
194
- if (ulRef.current) {
195
- ulRef.current.scrollTop = 0;
196
- }
197
- break;
198
- case 'End':
199
- e.preventDefault();
200
- focusItem(nth(sameLevelItems, -1));
201
- if (ulRef.current) {
202
- ulRef.current.scrollTop = ulRef.current.scrollHeight;
203
- }
204
- break;
205
- case 'Enter':
206
- case 'Space':
207
- e.preventDefault();
208
- if (itemType === 'item') {
209
- handleItemClick();
210
- }
211
- else if (itemType === 'sub') {
212
- addPopupId(itemId);
213
- }
214
- break;
215
- default:
216
- break;
217
- }
218
- };
219
- }
220
- return (_jsxs(Fragment, { children: [itemSeparator && _jsx(Separator, { style: { margin: '2px 0' } }), itemType === 'item' ? (_jsx(DropdownItemFC, { namespace: namespace, styled: styled, id: id, level: level, icon: itemIcon, focus: focusVisible && isFocus, disabled: itemDisabled, onClick: handleItemClick, children: itemTitle })) : itemType === 'group' ? (_jsx(DropdownGroup, { styled: styled, id: id, level: level, list: children && getNodes(children, level + 1, newSubParents), empty: isEmpty, children: itemTitle })) : (_jsx(DropdownSub, { ref: (instance) => {
221
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
222
- const fn = instance;
223
- updateSubPosition.current.set(itemId, fn);
224
- return () => {
225
- updateSubPosition.current.delete(itemId);
226
- };
227
- }, namespace: namespace, styled: styled, id: id, level: level, icon: itemIcon, list: children && getNodes(children, 0, newSubParents), popupState: popupState === null || popupState === void 0 ? void 0 : popupState.visible, trigger: trigger, empty: isEmpty, focus: focusVisible && isFocus, disabled: itemDisabled, zIndex: isUndefined(zIndex)
228
- ? zIndex
229
- : isNumber(zIndex)
230
- ? zIndex + 1 + subParents.length
231
- : `calc(${zIndex} + ${1 + subParents.length})`, onVisibleChange: (visible) => {
232
- if (visible) {
233
- if (subParents.length === 0) {
234
- setPopupIds([{ id: itemId, visible: true }]);
235
- }
236
- else {
237
- addPopupId(itemId);
238
- }
239
- }
240
- else {
241
- removePopupId(itemId);
242
- }
243
- }, children: itemTitle }))] }, itemId));
244
- });
245
- return getNodes(list, 0, []);
246
- })();
108
+ const handleKeyDown = useRef(undefined);
247
109
  const updateAllPosition = useEventCallback(() => {
248
110
  var _a;
249
111
  updatePosition();
@@ -281,6 +143,7 @@ export function Dropdown(props) {
281
143
  changeVisible(false);
282
144
  },
283
145
  onKeyDown: (e) => {
146
+ var _a;
284
147
  focusVisibleProps.onKeyDown(e);
285
148
  if (visible) {
286
149
  if (escClosable && e.code === 'Escape') {
@@ -289,7 +152,7 @@ export function Dropdown(props) {
289
152
  changeVisible(false);
290
153
  }
291
154
  else {
292
- handleKeyDown === null || handleKeyDown === void 0 ? void 0 : handleKeyDown(e);
155
+ (_a = handleKeyDown.current) === null || _a === void 0 ? void 0 : _a.call(handleKeyDown, e);
293
156
  }
294
157
  }
295
158
  else {
@@ -349,12 +212,17 @@ export function Dropdown(props) {
349
212
  transitionRef(null);
350
213
  };
351
214
  } }, popupProps.popup, { children: [(() => {
352
- const el = (_jsx("ul", Object.assign({}, styled('dropdown__list'), { ref: (instance) => {
353
- ulRef.current = instance;
354
- return () => {
355
- ulRef.current = null;
356
- };
357
- }, id: id, tabIndex: -1, role: "menu", "aria-labelledby": triggerId, "aria-activedescendant": isUndefined(focusId) ? undefined : getItemId(focusId), children: list.length === 0 ? _jsx("div", Object.assign({}, styled('dropdown__empty'), { children: t('No data') })) : nodes })));
358
- return popupRender ? popupRender(el) : el;
215
+ const node = (_jsx(DropdownList, { namespace: namespace, styled: styled, ulProps: {
216
+ id,
217
+ 'aria-labelledby': triggerId,
218
+ 'aria-activedescendant': isUndefined(focusId) ? undefined : getItemId(focusId),
219
+ }, list: list, virtual: virtual, focusVisible: focusVisible, focusId: focusId, popupIds: popupIds, updateSubPosition: updateSubPosition, trigger: trigger, zIndex: zIndex, handleKeyDown: handleKeyDown, getItemId: getItemId, onClick: onClick, onFocusIdsChange: (ids) => {
220
+ setFocusIds(ids);
221
+ }, onPopupIdsChange: (ids) => {
222
+ setPopupIds(ids);
223
+ }, addPopupId: addPopupId, removePopupId: removePopupId, onVisibleChange: (visible) => {
224
+ changeVisible(visible);
225
+ } }));
226
+ return popupRender ? popupRender(node) : node;
359
227
  })(), arrow && _jsx("div", Object.assign({}, styled('dropdown__arrow')))] })) }))) }) })] })) }));
360
228
  }
@@ -0,0 +1,34 @@
1
+ /// <reference types="react" />
2
+ import type { Styled } from '../../hooks/useStyled';
3
+ import type { DropdownItem } from '../types';
4
+ import type { CLASSES } from '../vars';
5
+ interface DropdownListProps<ID extends React.Key, T extends DropdownItem<ID>> {
6
+ namespace: string;
7
+ styled: Styled<typeof CLASSES>;
8
+ ulProps: any;
9
+ list: T[];
10
+ ancestryOfSub?: T[];
11
+ virtual?: boolean | number;
12
+ focusVisible: boolean;
13
+ focusId: ID | undefined;
14
+ popupIds: {
15
+ id: ID;
16
+ visible: boolean;
17
+ }[];
18
+ updateSubPosition: React.RefObject<Map<ID, () => void>>;
19
+ trigger: 'hover' | 'click';
20
+ zIndex: string | number;
21
+ handleKeyDown: React.RefObject<React.KeyboardEventHandler<HTMLElement> | undefined>;
22
+ getItemId: (id: ID) => string;
23
+ onClick?: (id: ID, origin: T) => void | false;
24
+ onFocusIdsChange: (ids: ID[]) => void;
25
+ onPopupIdsChange: (ids: {
26
+ id: ID;
27
+ visible: boolean;
28
+ }[]) => void;
29
+ addPopupId: (id: ID) => void;
30
+ removePopupId: (id: ID) => void;
31
+ onVisibleChange: (visible: boolean) => void;
32
+ }
33
+ export declare function DropdownList<ID extends React.Key, T extends DropdownItem<ID>>(props: DropdownListProps<ID, T>): React.ReactElement | null;
34
+ export {};
@@ -0,0 +1,178 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { checkNodeExist } from '@laser-ui/utils';
3
+ import { isNumber, isUndefined, nth } from 'lodash';
4
+ import { Fragment, useCallback, useMemo, useRef } from 'react';
5
+ import { DropdownSub } from './DropdownSub';
6
+ import { useTranslation } from '../../hooks';
7
+ import { Separator } from '../../separator';
8
+ import { mergeCS } from '../../utils';
9
+ import { VirtualScroll } from '../../virtual-scroll';
10
+ import { getSameLevelEnableItems } from '../utils';
11
+ export function DropdownList(props) {
12
+ const { namespace, styled, ulProps, list, ancestryOfSub = [], virtual, focusVisible, focusId, popupIds, updateSubPosition, trigger, zIndex, handleKeyDown, getItemId, onClick, onFocusIdsChange, onPopupIdsChange, addPopupId, removePopupId, onVisibleChange, } = props;
13
+ const ulRef = useRef(null);
14
+ const vsRef = useRef(null);
15
+ const { t } = useTranslation();
16
+ const canSelectedItem = useCallback((item) => !item.disabled && item.type !== 'group', []);
17
+ const ancestryOfSubIds = ancestryOfSub.map((item) => item.id);
18
+ const vsProps = useMemo(() => ({
19
+ list,
20
+ itemKey: (item) => item.id,
21
+ itemSize: (item) => (item.separator ? 3 : 0) + (isNumber(virtual) ? virtual : 32),
22
+ itemEmptySize: 32,
23
+ itemNested: (item) => (item.type === 'group' ? item.children : undefined),
24
+ itemFocusable: canSelectedItem,
25
+ }), [canSelectedItem, list, virtual]);
26
+ return (_jsx(VirtualScroll, Object.assign({}, vsProps, { ref: (instance) => {
27
+ vsRef.current = instance;
28
+ return () => {
29
+ vsRef.current = null;
30
+ };
31
+ }, enable: virtual !== false, listSize: 200 + 32 * 2, listPadding: 4, itemRender: (item, index, props, ancestry, childrenNode) => {
32
+ const { id: itemId, title: itemTitle, type: itemType, icon: itemIcon, disabled: itemDisabled = false, separator: itemSeparator, children, } = item;
33
+ const id = getItemId(itemId);
34
+ const isFocus = itemId === focusId;
35
+ const popupState = popupIds.find((v) => v.id === itemId);
36
+ const currentAncestryOfSub = ancestryOfSub.concat(itemType === 'sub' ? [item] : []);
37
+ const handleItemClick = () => {
38
+ const close = onClick === null || onClick === void 0 ? void 0 : onClick(itemId, item);
39
+ onFocusIdsChange(ancestryOfSubIds.concat([itemId]));
40
+ if (close !== false) {
41
+ onVisibleChange(false);
42
+ }
43
+ };
44
+ if (isFocus) {
45
+ handleKeyDown.current = (e) => {
46
+ var _a, _b, _c, _d;
47
+ const sameLevelItems = getSameLevelEnableItems((_b = (_a = nth(ancestryOfSub, -1)) === null || _a === void 0 ? void 0 : _a.children) !== null && _b !== void 0 ? _b : list);
48
+ const focusItem = (val) => {
49
+ if (val) {
50
+ onFocusIdsChange(ancestryOfSub.map((item) => item.id).concat([val.id]));
51
+ }
52
+ };
53
+ const scrollToItem = (val) => {
54
+ var _a;
55
+ if (ulRef.current && val) {
56
+ (_a = vsRef.current) === null || _a === void 0 ? void 0 : _a.scrollToItem(ulRef.current, val.id);
57
+ }
58
+ };
59
+ switch (e.code) {
60
+ case 'ArrowUp': {
61
+ e.preventDefault();
62
+ const index = sameLevelItems.findIndex((sameLevelItem) => sameLevelItem.id === itemId);
63
+ const item = nth(sameLevelItems, index - 1);
64
+ focusItem(item);
65
+ scrollToItem(item);
66
+ if (item && ((_c = nth(popupIds, -1)) === null || _c === void 0 ? void 0 : _c.id) === itemId) {
67
+ onPopupIdsChange(popupIds.slice(0, -1));
68
+ }
69
+ break;
70
+ }
71
+ case 'ArrowDown': {
72
+ e.preventDefault();
73
+ const index = sameLevelItems.findIndex((sameLevelItem) => sameLevelItem.id === itemId);
74
+ const item = nth(sameLevelItems, (index + 1) % sameLevelItems.length);
75
+ focusItem(item);
76
+ scrollToItem(item);
77
+ if (item && ((_d = nth(popupIds, -1)) === null || _d === void 0 ? void 0 : _d.id) === itemId) {
78
+ onPopupIdsChange(popupIds.slice(0, -1));
79
+ }
80
+ break;
81
+ }
82
+ case 'ArrowLeft': {
83
+ e.preventDefault();
84
+ onPopupIdsChange(popupIds.slice(0, -1));
85
+ if (ancestryOfSubIds.length > 0) {
86
+ onFocusIdsChange(ancestryOfSubIds);
87
+ }
88
+ break;
89
+ }
90
+ case 'ArrowRight':
91
+ e.preventDefault();
92
+ if (itemType === 'sub') {
93
+ addPopupId(itemId);
94
+ if (children) {
95
+ const newFocusItem = nth(getSameLevelEnableItems(children), 0);
96
+ if (newFocusItem) {
97
+ onFocusIdsChange(currentAncestryOfSub.map((item) => item.id).concat([newFocusItem.id]));
98
+ }
99
+ }
100
+ }
101
+ break;
102
+ case 'Home':
103
+ e.preventDefault();
104
+ focusItem(nth(sameLevelItems, 0));
105
+ if (ulRef.current) {
106
+ ulRef.current.scrollTop = 0;
107
+ }
108
+ break;
109
+ case 'End':
110
+ e.preventDefault();
111
+ focusItem(nth(sameLevelItems, -1));
112
+ if (ulRef.current) {
113
+ ulRef.current.scrollTop = ulRef.current.scrollHeight;
114
+ }
115
+ break;
116
+ case 'Enter':
117
+ case 'Space':
118
+ e.preventDefault();
119
+ if (itemType === 'item') {
120
+ handleItemClick();
121
+ }
122
+ else if (itemType === 'sub') {
123
+ addPopupId(itemId);
124
+ }
125
+ break;
126
+ default:
127
+ break;
128
+ }
129
+ };
130
+ }
131
+ return (_jsxs(Fragment, { children: [itemSeparator && _jsx(Separator, { style: { margin: '2px 0' } }), itemType === 'item' ? (_jsxs("li", Object.assign({}, styled('dropdown__item', 'dropdown__item--item', {
132
+ 'dropdown__item.is-disabled': itemDisabled,
133
+ }), { id: id, role: "menuitem", "aria-disabled": itemDisabled, onClick: handleItemClick, children: [focusVisible && isFocus && _jsx("div", { className: `${namespace}-focus-outline` }), checkNodeExist(itemIcon) && _jsx("div", Object.assign({}, styled('dropdown__item-icon'), { children: itemIcon })), _jsx("div", Object.assign({}, styled('dropdown__item-content'), { children: itemTitle }))] }))) : itemType === 'group' ? (_jsxs(_Fragment, { children: [_jsx("li", Object.assign({}, styled('dropdown__group-title'), { id: id, role: "presentation", children: itemTitle })), _jsx("ul", Object.assign({}, mergeCS(styled('dropdown__group-list'), {
134
+ style: {
135
+ '--level': (() => {
136
+ let val = 1;
137
+ for (let index = ancestry.length - 1; index > -1; index--) {
138
+ if (ancestry[index].type === 'group') {
139
+ val += 1;
140
+ }
141
+ if (ancestry[index].type === 'sub') {
142
+ break;
143
+ }
144
+ }
145
+ return val;
146
+ })(),
147
+ },
148
+ }), { role: "group", "aria-labelledby": id, children: childrenNode }))] })) : (_jsx(DropdownSub, { ref: (instance) => {
149
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
150
+ const fn = instance;
151
+ updateSubPosition.current.set(itemId, fn);
152
+ return () => {
153
+ updateSubPosition.current.delete(itemId);
154
+ };
155
+ }, namespace: namespace, styled: styled, id: id, icon: itemIcon, list: _jsx(DropdownList, { namespace: namespace, styled: styled, ulProps: { 'aria-labelledby': id }, list: (children !== null && children !== void 0 ? children : []), ancestryOfSub: currentAncestryOfSub, virtual: virtual, focusVisible: focusVisible, focusId: focusId, popupIds: popupIds, updateSubPosition: updateSubPosition, trigger: trigger, zIndex: zIndex, handleKeyDown: handleKeyDown, getItemId: getItemId, onClick: onClick, onFocusIdsChange: onFocusIdsChange, onPopupIdsChange: onPopupIdsChange, addPopupId: addPopupId, removePopupId: removePopupId, onVisibleChange: onVisibleChange }), popupState: popupState === null || popupState === void 0 ? void 0 : popupState.visible, trigger: trigger, focus: focusVisible && isFocus, disabled: itemDisabled, zIndex: isUndefined(zIndex)
156
+ ? zIndex
157
+ : isNumber(zIndex)
158
+ ? zIndex + 1 + ancestryOfSub.length
159
+ : `calc(${zIndex} + ${1 + ancestryOfSub.length})`, onVisibleChange: (visible) => {
160
+ if (visible) {
161
+ if (ancestryOfSub.length === 0) {
162
+ onPopupIdsChange([{ id: itemId, visible: true }]);
163
+ }
164
+ else {
165
+ addPopupId(itemId);
166
+ }
167
+ }
168
+ else {
169
+ removePopupId(itemId);
170
+ }
171
+ }, children: itemTitle }))] }, itemId));
172
+ }, itemFocused: focusId, itemEmptyRender: () => (_jsx("li", Object.assign({}, styled('dropdown__empty'), { children: _jsx("div", Object.assign({}, styled('dropdown__item-content'), { children: t('No data') })) }))), itemInAriaSetsize: (item) => item.type === 'item', placeholder: "li", children: (vsList, onScroll) => (_jsx("ul", Object.assign({}, styled('dropdown__list'), ulProps, { ref: (instance) => {
173
+ ulRef.current = instance;
174
+ return () => {
175
+ ulRef.current = null;
176
+ };
177
+ }, tabIndex: -1, role: "menu", onScroll: onScroll, children: list.length === 0 ? (_jsx("li", Object.assign({}, styled('dropdown__empty'), { children: _jsx("div", Object.assign({}, styled('dropdown__item-content'), { children: t('No data') })) }))) : (vsList) }))) })));
178
+ }
@@ -7,12 +7,10 @@ interface DropdownSubProps {
7
7
  namespace: string;
8
8
  styled: Styled<typeof CLASSES>;
9
9
  id: string;
10
- level: number;
11
10
  icon: React.ReactNode | undefined;
12
11
  list: React.ReactNode;
13
12
  popupState: boolean | undefined;
14
13
  trigger: 'hover' | 'click';
15
- empty: boolean;
16
14
  focus: boolean;
17
15
  disabled: boolean;
18
16
  zIndex: number | string | undefined;
@@ -4,7 +4,6 @@ import { checkNodeExist } from '@laser-ui/utils';
4
4
  import KeyboardArrowRightOutlined from '@material-design-icons/svg/outlined/keyboard_arrow_right.svg?react';
5
5
  import { isUndefined } from 'lodash';
6
6
  import { useImperativeHandle, useRef } from 'react';
7
- import { useTranslation } from '../../hooks';
8
7
  import { Icon } from '../../icon';
9
8
  import { Popup } from '../../internal/popup';
10
9
  import { Portal } from '../../internal/portal';
@@ -12,10 +11,9 @@ import { Transition } from '../../transition';
12
11
  import { getHorizontalSidePosition, mergeCS } from '../../utils';
13
12
  import { TTANSITION_DURING_POPUP, WINDOW_SPACE } from '../../vars';
14
13
  export function DropdownSub(props) {
15
- const { ref, children, namespace, styled, id, level, icon, list, popupState, trigger, empty, focus, disabled, zIndex, onVisibleChange } = props;
14
+ const { ref, children, namespace, styled, id, icon, list, popupState, trigger, focus, disabled, zIndex, onVisibleChange } = props;
16
15
  const triggerRef = useRef(null);
17
16
  const popupRef = useRef(null);
18
- const { t } = useTranslation();
19
17
  const visible = !isUndefined(popupState);
20
18
  const updatePosition = useEventCallback(() => {
21
19
  if (!disabled && visible && popupRef.current && triggerRef.current) {
@@ -35,10 +33,10 @@ export function DropdownSub(props) {
35
33
  triggerRef,
36
34
  popupRef,
37
35
  scroll: false,
38
- }, onVisibleChange: onVisibleChange, children: (popupProps) => (_jsxs(_Fragment, { children: [_jsxs("li", Object.assign({}, mergeCS(styled('dropdown__item', 'dropdown__item--sub', {
36
+ }, onVisibleChange: onVisibleChange, children: (popupProps) => (_jsxs(_Fragment, { children: [_jsxs("li", Object.assign({}, styled('dropdown__item', 'dropdown__item--sub', {
39
37
  'dropdown__item.is-expand': visible,
40
38
  'dropdown__item.is-disabled': disabled,
41
- }), { style: { paddingLeft: 12 + level * 16 } }), { ref: (instance) => {
39
+ }), { ref: (instance) => {
42
40
  triggerRef.current = instance;
43
41
  return () => {
44
42
  triggerRef.current = null;
@@ -60,5 +58,5 @@ export function DropdownSub(props) {
60
58
  popupRef.current = null;
61
59
  transitionRef(null);
62
60
  };
63
- } }, popupProps.popup, { children: _jsx("ul", Object.assign({}, styled('dropdown__list'), { role: "menu", "aria-labelledby": id, children: empty ? (_jsx("div", Object.assign({}, mergeCS(styled('dropdown__empty'), { style: { paddingLeft: 12 + level * 16 } }), { children: t('No data') }))) : (list) })) }))) }) })] })) }));
61
+ } }, popupProps.popup, { children: list }))) }) })] })) }));
64
62
  }
@@ -36,6 +36,7 @@ export interface DropdownProps<ID extends React.Key, T extends DropdownItem<ID>>
36
36
  placement?: VerticalSidePlacement;
37
37
  placementFixed?: boolean;
38
38
  arrow?: boolean;
39
+ virtual?: boolean | number;
39
40
  escClosable?: boolean;
40
41
  zIndex?: number | string;
41
42
  popupRender?: (el: React.ReactElement) => React.ReactNode;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@laser-ui/components",
3
- "version": "2.1.3",
3
+ "version": "2.2.0",
4
4
  "description": "React components.",
5
5
  "keywords": [
6
6
  "ui",
@@ -37,5 +37,5 @@
37
37
  "access": "public",
38
38
  "directory": "../../dist/libs/components"
39
39
  },
40
- "gitHead": "3d3767a17d1774693ecfd268b59d1c46d764cd97"
40
+ "gitHead": "be091d302eaa1c5a3fa73cebbcdab245ec8b1e1f"
41
41
  }
package/select/Select.js CHANGED
@@ -9,7 +9,7 @@ import CloseOutlined from '@material-design-icons/svg/outlined/close.svg?react';
9
9
  import KeyboardArrowDownOutlined from '@material-design-icons/svg/outlined/keyboard_arrow_down.svg?react';
10
10
  import SearchOutlined from '@material-design-icons/svg/outlined/search.svg?react';
11
11
  import { isNull, isNumber, isUndefined } from 'lodash';
12
- import { useCallback, useId, useImperativeHandle, useMemo, useRef, useState } from 'react';
12
+ import { Fragment, useCallback, useId, useImperativeHandle, useMemo, useRef, useState } from 'react';
13
13
  import { CLASSES, IS_CREATED } from './vars';
14
14
  import { BaseInput } from '../base-input';
15
15
  import { Checkbox } from '../checkbox';
@@ -238,7 +238,7 @@ export function Select(props) {
238
238
  title: label,
239
239
  type: 'item',
240
240
  disabled: item === null || item === void 0 ? void 0 : item.disabled,
241
- })), onClick: (id) => {
241
+ })), virtual: virtual, onClick: (id) => {
242
242
  changeSelectedByClick(id);
243
243
  return false;
244
244
  }, children: (dropdownProps) => (_jsx(Tag, Object.assign({}, dropdownProps, { tabIndex: -1, size: size, children: selected.size }))) }));
@@ -491,14 +491,20 @@ export function Select(props) {
491
491
  return () => {
492
492
  vsRef.current = null;
493
493
  };
494
- }, enable: virtual !== false, listSize: 264, listPadding: 4, itemRender: (item, index, props, ancestry, children) => {
494
+ }, enable: virtual !== false, listSize: 200 + 32 * 2, listPadding: 4, itemRender: (item, index, props, ancestry, children) => {
495
495
  const { label: itemLabel, value: itemValue, disabled: itemDisabled } = item;
496
496
  const node = customItem ? customItem(item) : itemLabel;
497
497
  if (children) {
498
- return (_createElement("ul", Object.assign({}, styled('select__option-group'), { key: itemValue, role: "group", "aria-labelledby": getItemId(itemValue) }),
499
- _createElement("li", Object.assign({}, styled('select__option-group-label'), { key: itemValue, id: getItemId(itemValue), role: "presentation" }),
500
- _jsx("div", Object.assign({}, styled('select__option-content'), { children: node }))),
501
- children));
498
+ const level = (() => {
499
+ let val = 1;
500
+ for (let index = ancestry.length - 1; index > -1; index--) {
501
+ if (ancestry[index].children) {
502
+ val += 1;
503
+ }
504
+ }
505
+ return val;
506
+ })();
507
+ return (_jsxs(Fragment, { children: [_jsx("li", Object.assign({}, styled('select__option-group-label'), { id: getItemId(itemValue), role: "presentation", children: _jsx("div", Object.assign({}, styled('select__option-content'), { children: node })) })), _jsx("ul", Object.assign({}, mergeCS(styled('select__option-group'), { style: { '--level': level } }), { role: "group", "aria-labelledby": getItemId(itemValue), children: children }))] }, itemValue));
502
508
  }
503
509
  let isSelected = false;
504
510
  if (multiple) {
@@ -507,10 +513,10 @@ export function Select(props) {
507
513
  else {
508
514
  isSelected = selected === itemValue;
509
515
  }
510
- return (_createElement("li", Object.assign({}, mergeCS(styled('select__option', {
516
+ return (_createElement("li", Object.assign({}, styled('select__option', {
511
517
  'select__option.is-selected': !multiple && isSelected,
512
518
  'select__option.is-disabled': itemDisabled,
513
- }), { style: { paddingLeft: ancestry.length === 0 ? undefined : 12 + 8 } }), props, { key: itemValue, id: getItemId(itemValue), title: (item[IS_CREATED] ? t('Create') + ' ' : '') + itemLabel, role: "option", "aria-selected": isSelected, "aria-disabled": itemDisabled, onClick: () => {
519
+ }), props, { key: itemValue, id: getItemId(itemValue), title: (item[IS_CREATED] ? t('Create') + ' ' : '') + itemLabel, role: "option", "aria-selected": isSelected, "aria-disabled": itemDisabled, onClick: () => {
514
520
  if (item[IS_CREATED]) {
515
521
  handleCreateItem(item);
516
522
  }
@@ -32,7 +32,7 @@ export function TransferPanel(props) {
32
32
  return () => {
33
33
  vsRef.current = null;
34
34
  };
35
- }, enable: virtual !== false, listSize: 192, listPadding: 0, itemRender: (item, index, props) => (_createElement("li", Object.assign({}, styled('transfer__option', {
35
+ }, enable: virtual !== false, listSize: 192 + 32 * 2, listPadding: 0, itemRender: (item, index, props) => (_createElement("li", Object.assign({}, styled('transfer__option', {
36
36
  'transfer__option.is-disabled': item.disabled,
37
37
  }), props, { key: item.value, id: getItemId(item.value), title: item.label, onClick: () => {
38
38
  if (!item.disabled) {
@@ -244,7 +244,7 @@ export function TreeSelect(props) {
244
244
  title: label,
245
245
  type: 'item',
246
246
  disabled: node === null || node === void 0 ? void 0 : node.origin.disabled,
247
- })), onClick: (id) => {
247
+ })), virtual: virtual, onClick: (id) => {
248
248
  changeSelected((draft) => {
249
249
  draft.splice(draft.findIndex((v) => v === id), 1);
250
250
  });
@@ -505,7 +505,13 @@ export function TreeSelect(props) {
505
505
  return () => {
506
506
  focusRef.current = null;
507
507
  };
508
- }, id: listId, namespace: namespace, styled: styled, list: nodes, itemId: getItemId, itemSelected: !multiple && hasSelected ? nodesMap.get(selected) : undefined, itemFocused: itemFocusedWithoutSearch, expands: expands, customItem: customItem, showLine: showLine, multiple: multiple, onlyLeafSelectable: onlyLeafSelectable, disabled: false, virtual: virtual === false ? undefined : { listSize: 264, listPadding: 4, itemSize: isNumber(virtual) ? virtual : 32 }, focusVisible: focusVisible, onNodeFocus: setItemFocusedWithoutSearch, onNodeExpand: handleExpand, onNodeClick: changeSelectedByClickWithoutSearch, onScrollBottom: onScrollBottom }))] })));
508
+ }, id: listId, namespace: namespace, styled: styled, list: nodes, itemId: getItemId, itemSelected: !multiple && hasSelected ? nodesMap.get(selected) : undefined, itemFocused: itemFocusedWithoutSearch, expands: expands, customItem: customItem, showLine: showLine, multiple: multiple, onlyLeafSelectable: onlyLeafSelectable, disabled: false, virtual: virtual === false
509
+ ? undefined
510
+ : {
511
+ listSize: 200 + 32 * 2,
512
+ listPadding: 4,
513
+ itemSize: isNumber(virtual) ? virtual : 32,
514
+ }, focusVisible: focusVisible, onNodeFocus: setItemFocusedWithoutSearch, onNodeExpand: handleExpand, onNodeClick: changeSelectedByClickWithoutSearch, onScrollBottom: onScrollBottom }))] })));
509
515
  return popupRender ? popupRender(el) : el;
510
516
  })() }))) }) })] }));
511
517
  }
@@ -51,7 +51,7 @@ export function TreeSelectSearchPanel(props) {
51
51
  return () => {
52
52
  vsRef.current = null;
53
53
  };
54
- }, enable: virtual !== false, listSize: 264, listPadding: 4, itemRender: (item, index, props) => {
54
+ }, enable: virtual !== false, listSize: 200 + 32 * 2, listPadding: 4, itemRender: (item, index, props) => {
55
55
  const node = item[TREE_NODE_KEY];
56
56
  let inSelected = node.checked;
57
57
  if (!onlyLeafSelectable) {
@@ -1,13 +0,0 @@
1
- /// <reference types="react" />
2
- import type { Styled } from '../../hooks/useStyled';
3
- import type { CLASSES } from '../vars';
4
- interface DropdownGroupProps {
5
- children: React.ReactNode;
6
- styled: Styled<typeof CLASSES>;
7
- id: string;
8
- level: number;
9
- list: React.ReactNode;
10
- empty: boolean;
11
- }
12
- export declare function DropdownGroup(props: DropdownGroupProps): React.ReactElement | null;
13
- export {};
@@ -1,8 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useTranslation } from '../../hooks';
3
- import { mergeCS } from '../../utils';
4
- export function DropdownGroup(props) {
5
- const { children, styled, id, level, list, empty } = props;
6
- const { t } = useTranslation();
7
- return (_jsxs("ul", Object.assign({}, styled('dropdown__group-list'), { role: "group", "aria-labelledby": id, children: [_jsx("li", Object.assign({}, mergeCS(styled('dropdown__group-title'), { style: { paddingLeft: 12 + level * 16 } }), { id: id, role: "presentation", children: children })), empty ? _jsx("div", Object.assign({}, mergeCS(styled('dropdown__empty'), { style: { paddingLeft: 12 + (level + 1) * 16 } }), { children: t('No data') })) : list] })));
8
- }
@@ -1,16 +0,0 @@
1
- /// <reference types="react" />
2
- import type { Styled } from '../../hooks/useStyled';
3
- import type { CLASSES } from '../vars';
4
- interface DropdownItemProps {
5
- children: React.ReactNode;
6
- namespace: string;
7
- styled: Styled<typeof CLASSES>;
8
- id: string;
9
- level: number;
10
- icon: React.ReactNode;
11
- focus: boolean;
12
- disabled: boolean;
13
- onClick: () => void;
14
- }
15
- export declare function DropdownItem(props: DropdownItemProps): React.ReactElement | null;
16
- export {};
@@ -1,9 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { checkNodeExist } from '@laser-ui/utils';
3
- import { mergeCS } from '../../utils';
4
- export function DropdownItem(props) {
5
- const { children, namespace, styled, id, level, icon, focus, disabled, onClick } = props;
6
- return (_jsxs("li", Object.assign({}, mergeCS(styled('dropdown__item', 'dropdown__item--item', {
7
- 'dropdown__item.is-disabled': disabled,
8
- }), { style: { paddingLeft: 12 + level * 16 } }), { id: id, role: "menuitem", "aria-disabled": disabled, onClick: onClick, children: [focus && _jsx("div", { className: `${namespace}-focus-outline` }), checkNodeExist(icon) && _jsx("div", Object.assign({}, styled('dropdown__item-icon'), { children: icon })), _jsx("div", Object.assign({}, styled('dropdown__item-content'), { children: children }))] })));
9
- }