@rainbow-oh/yee-c 0.6.2 → 0.6.4
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/cjs/Checkbox/style/index.less +1 -1
- package/dist/cjs/Select/demo/orphan.d.ts +3 -0
- package/dist/cjs/Select/demo/orphan.js +78 -0
- package/dist/cjs/Select/interface.d.ts +11 -0
- package/dist/cjs/Select/select.js +18 -5
- package/dist/cjs/Selector/interface.d.ts +11 -0
- package/dist/cjs/Selector/selector.js +51 -16
- package/dist/cjs/Table/style/index.less +1 -1
- package/dist/cjs/Table/tbody/index.js +2 -2
- package/dist/cjs/Tag/style/index.less +9 -9
- package/dist/cjs/style/themes.css +1 -1
- package/dist/cjs/style/variables.css +1 -1
- package/dist/cjs/utils/isEqual.d.ts +1 -1
- package/dist/cjs/utils/isEqual.js +26 -13
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/Checkbox/style/index.less +1 -1
- package/dist/esm/Select/demo/orphan.d.ts +3 -0
- package/dist/esm/Select/demo/orphan.js +77 -0
- package/dist/esm/Select/interface.d.ts +11 -0
- package/dist/esm/Select/select.js +22 -6
- package/dist/esm/Selector/interface.d.ts +11 -0
- package/dist/esm/Selector/selector.js +34 -10
- package/dist/esm/Table/style/index.less +1 -1
- package/dist/esm/Table/tbody/index.js +3 -2
- package/dist/esm/Tag/style/index.less +9 -9
- package/dist/esm/style/themes.css +1 -1
- package/dist/esm/style/variables.css +1 -1
- package/dist/esm/utils/isEqual.d.ts +1 -1
- package/dist/esm/utils/isEqual.js +33 -15
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/umd/yee-c.min.css +1 -1
- package/dist/umd/yee-c.min.js +1 -1
- package/package.json +1 -1
- package/src/Checkbox/style/index.less +1 -1
- package/src/Select/demo/orphan.tsx +61 -0
- package/src/Select/index.en-US.md +3 -0
- package/src/Select/index.zh-CN.md +3 -0
- package/src/Select/interface.ts +11 -0
- package/src/Select/select.tsx +19 -3
- package/src/Selector/interface.ts +11 -0
- package/src/Selector/selector.tsx +56 -25
- package/src/Table/style/index.less +1 -1
- package/src/Table/tbody/index.tsx +5 -3
- package/src/Tag/style/index.less +9 -9
- package/src/utils/isEqual.ts +37 -14
- package/src/version.ts +1 -1
package/package.json
CHANGED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Select, Space } from '@rainbow-oh/yee-c';
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
|
+
|
|
4
|
+
const options = [
|
|
5
|
+
{ label: 'Apple', value: 'apple' },
|
|
6
|
+
{ label: 'Banana', value: 'banana' },
|
|
7
|
+
{ label: 'Orange', value: 'orange' },
|
|
8
|
+
{ label: 'Pineapple', value: 'pineapple' },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
// Inline style applied to an orphan value (a value not present in options)
|
|
12
|
+
const orphanStyle: React.CSSProperties = {
|
|
13
|
+
color: '#fa8c16',
|
|
14
|
+
fontStyle: 'italic',
|
|
15
|
+
textDecoration: 'underline dotted',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default () => {
|
|
19
|
+
// 'mangox' is not in options → rendered as an orphan value
|
|
20
|
+
const [single, setSingle] = useState('mangox');
|
|
21
|
+
// 'durian' is not in options → rendered as an orphan tag alongside normal ones
|
|
22
|
+
const [multi, setMulti] = useState(['apple', 'durian', 'banana']);
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<Space direction="vertical" style={{ width: '100%' }}>
|
|
26
|
+
<div>
|
|
27
|
+
<div style={{ marginBottom: 8 }}>
|
|
28
|
+
Single — orphan value styled via <code>orphanStyle</code>
|
|
29
|
+
</div>
|
|
30
|
+
<Select
|
|
31
|
+
value={single}
|
|
32
|
+
onChange={(val) => setSingle(val as string)}
|
|
33
|
+
options={options}
|
|
34
|
+
orphanStyle={orphanStyle}
|
|
35
|
+
style={{ width: 300 }}
|
|
36
|
+
placeholder="Please select"
|
|
37
|
+
/>
|
|
38
|
+
</div>
|
|
39
|
+
<div>
|
|
40
|
+
<div style={{ marginBottom: 8 }}>
|
|
41
|
+
Multiple — orphan tag styled via <code>orphanClassName</code>
|
|
42
|
+
</div>
|
|
43
|
+
<Select
|
|
44
|
+
mode="multiple"
|
|
45
|
+
value={multi}
|
|
46
|
+
onChange={(val) => setMulti(val as string[])}
|
|
47
|
+
options={options}
|
|
48
|
+
orphanClassName="demo-orphan-tag"
|
|
49
|
+
style={{ width: 300 }}
|
|
50
|
+
placeholder="Please select"
|
|
51
|
+
/>
|
|
52
|
+
</div>
|
|
53
|
+
<style>{`
|
|
54
|
+
.demo-orphan-tag {
|
|
55
|
+
color: #fa541c;
|
|
56
|
+
font-style: italic;
|
|
57
|
+
}
|
|
58
|
+
`}</style>
|
|
59
|
+
</Space>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
@@ -21,6 +21,7 @@ Select component for choosing from a set of options.
|
|
|
21
21
|
<code src="./demo/multiple.tsx" title="Multiple" description="Multiple selection mode"></code>
|
|
22
22
|
<code src="./demo/search.tsx" title="Search" description="Searchable Select"></code>
|
|
23
23
|
<code src="./demo/tags.tsx" title="Tags" description="Tags mode Select"></code>
|
|
24
|
+
<code src="./demo/orphan.tsx" title="Orphan Value" description="Display and custom styling when the value is not in options"></code>
|
|
24
25
|
<code src="./demo/virtual.tsx" title="Virtual Scroll" description="Virtual scrolling for large datasets (thousands of options)"></code>
|
|
25
26
|
|
|
26
27
|
## API
|
|
@@ -45,6 +46,8 @@ Select component for choosing from a set of options.
|
|
|
45
46
|
| codeTableName | `string` | Code table name | - |
|
|
46
47
|
| optionFilterProp | `string` | Property to filter by when searching | `label` |
|
|
47
48
|
| optionLabelProp | `string \| (() => string)` | Property to display in the input box | - |
|
|
49
|
+
| orphanClassName | `string` | Extra class name for the displayed value when it is not in `options` (orphan value). An orphan value is shown as the raw value itself by default | - |
|
|
50
|
+
| orphanStyle | `React.CSSProperties` | Extra inline style for the displayed value when it is not in `options` (orphan value) | - |
|
|
48
51
|
| onChange | `(value: string \| number \| Array<string \| number>, options?: Option \| undefined \| Option[]) => void` | Value change callback | - |
|
|
49
52
|
| onFilter | `(value: string, options: Array<Option>) => Array<Option>` | Search callback | - |
|
|
50
53
|
| virtual | `boolean` | Enable virtual scrolling. Only the options in the visible window are rendered, so the DOM node count stays constant regardless of the total. Recommended for hundreds or thousands of options | `false` |
|
|
@@ -21,6 +21,7 @@ toc: 'content'
|
|
|
21
21
|
<code src="./demo/multiple.tsx" title="多选" description="多选模式"></code>
|
|
22
22
|
<code src="./demo/search.tsx" title="可搜索" description="可搜索的Select"></code>
|
|
23
23
|
<code src="./demo/tags.tsx" title="标签模式" description="标签模式的Select"></code>
|
|
24
|
+
<code src="./demo/orphan.tsx" title="孤儿值" description="value 不在 options 中时的显示与自定义样式"></code>
|
|
24
25
|
<code src="./demo/virtual.tsx" title="虚拟滚动" description="大数据量(几千条)下的虚拟滚动"></code>
|
|
25
26
|
|
|
26
27
|
## API
|
|
@@ -45,6 +46,8 @@ toc: 'content'
|
|
|
45
46
|
| codeTableName | `string` | 代码表 | - |
|
|
46
47
|
| optionFilterProp | `string` | 搜索时对比的属性 | `label` |
|
|
47
48
|
| optionLabelProp | `string \| (() => string)` | 回填到选择框里的属性 | - |
|
|
49
|
+
| orphanClassName | `string` | value 不在 options 中(孤儿值)时,给显示值追加的类名。孤儿值默认直接显示 value 本身 | - |
|
|
50
|
+
| orphanStyle | `React.CSSProperties` | value 不在 options 中(孤儿值)时,给显示值追加的行内样式 | - |
|
|
48
51
|
| onChange | `(value: string \| number \| Array<string \| number>, options?: Option \| undefined \| Option[]) => void` | 值改变时的回调函数 | - |
|
|
49
52
|
| onFilter | `(value: string, options: Array<Option>) => Array<Option>` | 搜索时的回调函数 | - |
|
|
50
53
|
| virtual | `boolean` | 是否开启虚拟滚动。开启后仅渲染可视区内的选项,DOM 节点数与数据总量无关,适合成百上千条数据 | `false` |
|
package/src/Select/interface.ts
CHANGED
|
@@ -95,6 +95,17 @@ export interface SelectProps
|
|
|
95
95
|
* Property to backfill into the select box
|
|
96
96
|
*/
|
|
97
97
|
optionLabelProp?: 'string' | (() => string);
|
|
98
|
+
/**
|
|
99
|
+
* Extra class name applied to the displayed value when it is not found
|
|
100
|
+
* in `options` (an "orphan" value). An orphan value is shown as the raw
|
|
101
|
+
* value itself; this lets you style it differently (e.g. to flag stale data).
|
|
102
|
+
*/
|
|
103
|
+
orphanClassName?: string;
|
|
104
|
+
/**
|
|
105
|
+
* Extra inline style applied to the displayed value when it is not found
|
|
106
|
+
* in `options` (an "orphan" value).
|
|
107
|
+
*/
|
|
108
|
+
orphanStyle?: React.CSSProperties;
|
|
98
109
|
/**
|
|
99
110
|
* Callback when value changes
|
|
100
111
|
*/
|
package/src/Select/select.tsx
CHANGED
|
@@ -32,6 +32,8 @@ const Select = (baseprops: SelectProps) => {
|
|
|
32
32
|
virtual,
|
|
33
33
|
itemHeight,
|
|
34
34
|
listHeight,
|
|
35
|
+
orphanClassName,
|
|
36
|
+
orphanStyle,
|
|
35
37
|
...rest
|
|
36
38
|
} = props;
|
|
37
39
|
|
|
@@ -68,7 +70,9 @@ const Select = (baseprops: SelectProps) => {
|
|
|
68
70
|
virtualApiRef.current?.scrollToIndex(index);
|
|
69
71
|
}, []);
|
|
70
72
|
|
|
71
|
-
const getOptions = (
|
|
73
|
+
const getOptions = (
|
|
74
|
+
keys: string | number | Array<string | number>,
|
|
75
|
+
) => {
|
|
72
76
|
if (keys === '' || keys === null) {
|
|
73
77
|
return undefined;
|
|
74
78
|
}
|
|
@@ -76,10 +80,20 @@ const Select = (baseprops: SelectProps) => {
|
|
|
76
80
|
if (keys.length === 0) {
|
|
77
81
|
return [];
|
|
78
82
|
}
|
|
79
|
-
|
|
83
|
+
// 按 keys 顺序回填,找不到的 key 构造 label=value 的兜底项(孤儿值),
|
|
84
|
+
// 与 Selector 的显示保持一致,避免 onChange 第二参数丢值。
|
|
85
|
+
return keys.map((key) => {
|
|
86
|
+
const opt = options.find((o) => o.value === key);
|
|
87
|
+
return opt ?? { value: key, label: String(key) };
|
|
88
|
+
});
|
|
80
89
|
}
|
|
81
90
|
|
|
82
|
-
return
|
|
91
|
+
return (
|
|
92
|
+
options.find((opt) => opt.value === keys) ?? {
|
|
93
|
+
value: keys,
|
|
94
|
+
label: String(keys),
|
|
95
|
+
}
|
|
96
|
+
);
|
|
83
97
|
};
|
|
84
98
|
|
|
85
99
|
const handleSelect = (
|
|
@@ -193,6 +207,8 @@ const Select = (baseprops: SelectProps) => {
|
|
|
193
207
|
onRemove={handleRemove as any}
|
|
194
208
|
onSearch={handleSearch}
|
|
195
209
|
onKeyDown={onKeyDown}
|
|
210
|
+
orphanClassName={orphanClassName}
|
|
211
|
+
orphanStyle={orphanStyle}
|
|
196
212
|
/>
|
|
197
213
|
</Trigger>
|
|
198
214
|
);
|
|
@@ -79,6 +79,17 @@ export interface PureSelectorProps {
|
|
|
79
79
|
* Custom text displayed in the input
|
|
80
80
|
*/
|
|
81
81
|
optionLabelProp?: string | ((option: TagType) => string);
|
|
82
|
+
/**
|
|
83
|
+
* Extra class name applied to the displayed value/tag when its key is not
|
|
84
|
+
* found in `options` (an "orphan" value). The orphan value is shown as the
|
|
85
|
+
* raw key itself; this lets you style it differently (e.g. to flag stale data).
|
|
86
|
+
*/
|
|
87
|
+
orphanClassName?: string;
|
|
88
|
+
/**
|
|
89
|
+
* Extra inline style applied to the displayed value/tag when its key is not
|
|
90
|
+
* found in `options` (an "orphan" value).
|
|
91
|
+
*/
|
|
92
|
+
orphanStyle?: React.CSSProperties;
|
|
82
93
|
|
|
83
94
|
/**
|
|
84
95
|
* Control the open state
|
|
@@ -37,6 +37,8 @@ const Selector = React.forwardRef(
|
|
|
37
37
|
disabled,
|
|
38
38
|
options = [],
|
|
39
39
|
selectedKeys,
|
|
40
|
+
orphanClassName,
|
|
41
|
+
orphanStyle,
|
|
40
42
|
allowClear = true,
|
|
41
43
|
placeholder,
|
|
42
44
|
closable = true,
|
|
@@ -60,17 +62,29 @@ const Selector = React.forwardRef(
|
|
|
60
62
|
});
|
|
61
63
|
|
|
62
64
|
const tags = useMemo(() => {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
65
|
+
if (!Array.isArray(options)) {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
// 按 selectedKeys 顺序渲染;找不到的 key 作为孤儿值回退显示 value 本身
|
|
69
|
+
return selectedKeys.map((key) => {
|
|
70
|
+
const opt = options.find((o) => o.value === key);
|
|
71
|
+
if (opt) {
|
|
72
|
+
return {
|
|
73
|
+
...opt,
|
|
74
|
+
children:
|
|
75
|
+
typeof optionLabelProp === 'function'
|
|
76
|
+
? optionLabelProp(opt)
|
|
77
|
+
: opt[optionLabelProp],
|
|
78
|
+
_orphan: false,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
value: key,
|
|
83
|
+
label: String(key),
|
|
84
|
+
children: String(key),
|
|
85
|
+
_orphan: true,
|
|
86
|
+
};
|
|
87
|
+
});
|
|
74
88
|
}, [options, selectedKeys, optionLabelProp]);
|
|
75
89
|
|
|
76
90
|
const handleClick = () => {
|
|
@@ -148,37 +162,54 @@ const Selector = React.forwardRef(
|
|
|
148
162
|
|
|
149
163
|
const renderContent = () => {
|
|
150
164
|
if (multi) {
|
|
151
|
-
return tags.map(
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
165
|
+
return tags.map(
|
|
166
|
+
(item: TagType & { children: any; _orphan?: boolean }, index) => {
|
|
167
|
+
const { _orphan, ...tagRest } = item;
|
|
168
|
+
const _closable =
|
|
169
|
+
typeof closable === 'function' ? closable(item) : closable;
|
|
170
|
+
return (
|
|
171
|
+
<Tag
|
|
172
|
+
{...tagRest}
|
|
173
|
+
className={_orphan ? orphanClassName : undefined}
|
|
174
|
+
style={_orphan ? orphanStyle : undefined}
|
|
175
|
+
closable={_closable}
|
|
176
|
+
onClose={(e) => handleRemove(item, e)}
|
|
177
|
+
key={item.value || index}
|
|
178
|
+
/>
|
|
179
|
+
);
|
|
180
|
+
},
|
|
181
|
+
);
|
|
163
182
|
}
|
|
164
183
|
|
|
165
184
|
const key = selectedKeys.length ? selectedKeys[0] : '';
|
|
166
185
|
const opt = Array.isArray(options)
|
|
167
186
|
? options.find((opt) => opt.value === key)
|
|
168
187
|
: null;
|
|
188
|
+
// 孤儿值:key 非空但在 options 中找不到时,回退显示 value 本身
|
|
189
|
+
const isOrphan = !opt && key !== '';
|
|
169
190
|
const label = (
|
|
170
191
|
opt
|
|
171
192
|
? typeof optionLabelProp === 'function'
|
|
172
193
|
? optionLabelProp(opt)
|
|
173
194
|
: opt[optionLabelProp]
|
|
174
|
-
:
|
|
195
|
+
: isOrphan
|
|
196
|
+
? String(key)
|
|
197
|
+
: null
|
|
175
198
|
) as React.ReactNode;
|
|
176
199
|
|
|
177
200
|
return (
|
|
178
201
|
<>
|
|
179
202
|
{renderSearch()}
|
|
180
203
|
{label && (
|
|
181
|
-
<div
|
|
204
|
+
<div
|
|
205
|
+
className={clsx(
|
|
206
|
+
`${prefixCls}-selection-item`,
|
|
207
|
+
isOrphan && orphanClassName,
|
|
208
|
+
)}
|
|
209
|
+
style={isOrphan ? orphanStyle : undefined}
|
|
210
|
+
>
|
|
211
|
+
{label}
|
|
212
|
+
</div>
|
|
182
213
|
)}
|
|
183
214
|
</>
|
|
184
215
|
);
|
|
@@ -6,7 +6,7 @@ import LazyRow from './lazy-row';
|
|
|
6
6
|
import TableRow from './row';
|
|
7
7
|
|
|
8
8
|
const TableBody: React.FC<TableBodyProps> = (props) => {
|
|
9
|
-
const { columns, pageData, lazyLoad, ...rest } = props;
|
|
9
|
+
const { columns, pageData, lazyLoad, noData, ...rest } = props;
|
|
10
10
|
|
|
11
11
|
const { prefixCls, components, rowKey } = React.useContext(TableCtx);
|
|
12
12
|
const { locale } = useLocale();
|
|
@@ -15,7 +15,7 @@ const TableBody: React.FC<TableBodyProps> = (props) => {
|
|
|
15
15
|
const empty = (
|
|
16
16
|
<tbody className={`${prefixCls}-tbody`}>
|
|
17
17
|
<tr className={`${prefixCls}-no-record`}>
|
|
18
|
-
<td colSpan={999}>{tableLocale.emptyText}</td>
|
|
18
|
+
<td colSpan={999}>{noData ?? tableLocale.emptyText}</td>
|
|
19
19
|
</tr>
|
|
20
20
|
</tbody>
|
|
21
21
|
);
|
|
@@ -60,7 +60,9 @@ const TableBody: React.FC<TableBodyProps> = (props) => {
|
|
|
60
60
|
};
|
|
61
61
|
|
|
62
62
|
const tbody = (
|
|
63
|
-
<BodyWrapper className={`${prefixCls}-tbody`}>
|
|
63
|
+
<BodyWrapper className={`${prefixCls}-tbody`}>
|
|
64
|
+
{getRenderRows()}
|
|
65
|
+
</BodyWrapper>
|
|
64
66
|
);
|
|
65
67
|
|
|
66
68
|
return tbody;
|
package/src/Tag/style/index.less
CHANGED
|
@@ -149,15 +149,6 @@
|
|
|
149
149
|
line-height: var(--yee-tag-large-height);
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
&-close {
|
|
153
|
-
display: inline-flex;
|
|
154
|
-
padding: 0;
|
|
155
|
-
width: 16px;
|
|
156
|
-
height: 16px;
|
|
157
|
-
margin-left: var(--yee-tag-close-margin-left);
|
|
158
|
-
cursor: pointer;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
152
|
&-checkable {
|
|
162
153
|
margin: var(--yee-tag-checkable-margin);
|
|
163
154
|
color: var(--yee-tag-item-not-check-color);
|
|
@@ -177,4 +168,13 @@
|
|
|
177
168
|
background-color: var(--yee-tag-item-check-active-bg-color);
|
|
178
169
|
border-color: var(--yee-tag-item-check-active-border-color);
|
|
179
170
|
}
|
|
171
|
+
|
|
172
|
+
& &-close {
|
|
173
|
+
display: inline-flex;
|
|
174
|
+
padding: 0;
|
|
175
|
+
width: 16px;
|
|
176
|
+
height: 16px;
|
|
177
|
+
margin-left: var(--yee-tag-close-margin-left);
|
|
178
|
+
cursor: pointer;
|
|
179
|
+
}
|
|
180
180
|
}
|
package/src/utils/isEqual.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
export default function isEqual(
|
|
1
|
+
export default function isEqual(
|
|
2
|
+
a: unknown,
|
|
3
|
+
b: unknown,
|
|
4
|
+
seen?: Map<object, Set<object>>,
|
|
5
|
+
): boolean {
|
|
2
6
|
// Compare reference types
|
|
3
7
|
if (a === b) return true;
|
|
4
8
|
|
|
@@ -18,17 +22,40 @@ export default function isEqual(a: unknown, b: unknown) {
|
|
|
18
22
|
return a.source === b.source && a.flags === b.flags;
|
|
19
23
|
}
|
|
20
24
|
|
|
21
|
-
// Compare
|
|
22
|
-
if (
|
|
23
|
-
|
|
24
|
-
for (let i = 0; i < a.length; i++) {
|
|
25
|
-
if (!isEqual(a[i], b[i])) return false;
|
|
26
|
-
}
|
|
27
|
-
return true;
|
|
25
|
+
// Compare functions
|
|
26
|
+
if (typeof a === 'function' && typeof b === 'function') {
|
|
27
|
+
return a.toString() === b.toString();
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
//
|
|
30
|
+
// For objects and arrays, detect circular references
|
|
31
31
|
if (typeof a === 'object' && typeof b === 'object') {
|
|
32
|
+
if (!seen) {
|
|
33
|
+
seen = new Map();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// If we've already started comparing this pair, assume equal to break the cycle
|
|
37
|
+
const seenForA = seen.get(a as object);
|
|
38
|
+
if (seenForA && seenForA.has(b as object)) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Mark this pair as being compared
|
|
43
|
+
if (!seenForA) {
|
|
44
|
+
seen.set(a as object, new Set([b as object]));
|
|
45
|
+
} else {
|
|
46
|
+
seenForA.add(b as object);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Compare arrays
|
|
50
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
51
|
+
if (a.length !== b.length) return false;
|
|
52
|
+
for (let i = 0; i < a.length; i++) {
|
|
53
|
+
if (!isEqual(a[i], b[i], seen)) return false;
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Compare objects
|
|
32
59
|
const keysA = Object.keys(a);
|
|
33
60
|
const keysB = Object.keys(b);
|
|
34
61
|
if (keysA.length !== keysB.length) return false;
|
|
@@ -38,6 +65,7 @@ export default function isEqual(a: unknown, b: unknown) {
|
|
|
38
65
|
!isEqual(
|
|
39
66
|
(a as Record<string, unknown>)[key],
|
|
40
67
|
(b as Record<string, unknown>)[key],
|
|
68
|
+
seen,
|
|
41
69
|
)
|
|
42
70
|
)
|
|
43
71
|
return false;
|
|
@@ -45,11 +73,6 @@ export default function isEqual(a: unknown, b: unknown) {
|
|
|
45
73
|
return true;
|
|
46
74
|
}
|
|
47
75
|
|
|
48
|
-
// Compare functions
|
|
49
|
-
if (typeof a === 'function' && typeof b === 'function') {
|
|
50
|
-
return a.toString() === b.toString();
|
|
51
|
-
}
|
|
52
|
-
|
|
53
76
|
// Other cases
|
|
54
77
|
return false;
|
|
55
78
|
}
|