@zidian-primitive/cascader 0.0.0-next-20260204023823
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/README.md +436 -0
- package/esm/components/node/CascaderNode.js +41 -0
- package/esm/components/node/CascaderNodeCheck.js +7 -0
- package/esm/components/panel/CascaderPanel.js +40 -0
- package/esm/components/panel/CascaderPanelHeader.js +20 -0
- package/esm/components/panel/CascaderPanelIndicator.js +17 -0
- package/esm/components/root/CascaderRoot.js +21 -0
- package/esm/context/index.js +15 -0
- package/esm/hooks/useCascader.js +61 -0
- package/esm/index.js +9 -0
- package/esm/utils/data.js +102 -0
- package/esm/utils/logic.js +79 -0
- package/lib/components/index.d.ts +6 -0
- package/lib/components/node/CascaderNode.d.ts +10 -0
- package/lib/components/node/CascaderNodeCheck.d.ts +1 -0
- package/lib/components/panel/CascaderPanel.d.ts +7 -0
- package/lib/components/panel/CascaderPanelHeader.d.ts +8 -0
- package/lib/components/panel/CascaderPanelIndicator.d.ts +6 -0
- package/lib/components/root/CascaderRoot.d.ts +7 -0
- package/lib/context/index.d.ts +12 -0
- package/lib/hooks/useCascader.d.ts +14 -0
- package/lib/index.d.ts +4 -0
- package/lib/types/index.d.ts +59 -0
- package/lib/utils/data.d.ts +5 -0
- package/lib/utils/index.d.ts +2 -0
- package/lib/utils/logic.d.ts +3 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
# @zidian-base/cascader
|
|
2
|
+
|
|
3
|
+
A flexible cascader component library built with component composition pattern, providing complete UI and logic for hierarchical selection.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🎯 **Component-Based Architecture**: Modular components for maximum flexibility
|
|
8
|
+
- 🔄 **Flexible Data Sources**: Support both hierarchical and flat data structures
|
|
9
|
+
- 🎨 **Fully Customizable**: Extensive slots for custom rendering
|
|
10
|
+
- ♿ **Accessibility**: Built with accessibility in mind
|
|
11
|
+
- 🔍 **Multiple Selection Modes**: Single and multiple selection support
|
|
12
|
+
- 📦 **TypeScript Support**: Full type safety
|
|
13
|
+
- 🚀 **Performance Optimized**: Efficient state management and rendering
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @zidian-base/cascader
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import React, { useState } from 'react'
|
|
25
|
+
import {
|
|
26
|
+
CascaderRoot,
|
|
27
|
+
CascaderPanel,
|
|
28
|
+
CascaderNode,
|
|
29
|
+
CascaderPanelHeader,
|
|
30
|
+
CascaderPanelIndicator,
|
|
31
|
+
useCascader,
|
|
32
|
+
type CascaderItem
|
|
33
|
+
} from '@zidian-base/cascader'
|
|
34
|
+
|
|
35
|
+
const data = [
|
|
36
|
+
{ category: 'Electronics', type: 'Phone', brand: 'iPhone' },
|
|
37
|
+
{ category: 'Electronics', type: 'Phone', brand: 'Samsung' },
|
|
38
|
+
{ category: 'Electronics', type: 'Laptop', brand: 'MacBook' },
|
|
39
|
+
{ category: 'Clothing', type: 'Shirt', brand: 'Nike' },
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
const handleDataConfig = {
|
|
43
|
+
levels: ['category', 'type', 'brand']
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default function App() {
|
|
47
|
+
const cascader = useCascader({
|
|
48
|
+
data,
|
|
49
|
+
handleDataConfig,
|
|
50
|
+
selectOption: {
|
|
51
|
+
defaultSelected: [],
|
|
52
|
+
selectType: 'single'
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const handleSelect = (nodeIdx: number) => {
|
|
57
|
+
cascader.handleChangeCheckStatus(nodeIdx)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<CascaderRoot
|
|
62
|
+
isSelected={true}
|
|
63
|
+
selectType="single"
|
|
64
|
+
onSelectChange={handleSelect}
|
|
65
|
+
>
|
|
66
|
+
<CascaderPanel>
|
|
67
|
+
<CascaderPanelHeader
|
|
68
|
+
column={cascader.columns[0]}
|
|
69
|
+
showNode={null}
|
|
70
|
+
/>
|
|
71
|
+
<CascaderPanelIndicator />
|
|
72
|
+
|
|
73
|
+
{cascader.columns.map((column, level) => (
|
|
74
|
+
<div key={level} className="cascader-column">
|
|
75
|
+
{column.map((node) => (
|
|
76
|
+
<CascaderNode
|
|
77
|
+
key={node.index}
|
|
78
|
+
node={node}
|
|
79
|
+
onSelectChange={() => handleSelect(node.indices)}
|
|
80
|
+
/>
|
|
81
|
+
))}
|
|
82
|
+
</div>
|
|
83
|
+
))}
|
|
84
|
+
</CascaderPanel>
|
|
85
|
+
</CascaderRoot>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## API Reference
|
|
91
|
+
|
|
92
|
+
### Hook
|
|
93
|
+
|
|
94
|
+
#### useCascader
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
const cascader = useCascader({
|
|
98
|
+
data: T[],
|
|
99
|
+
handleDataConfig: FlatDataConfig,
|
|
100
|
+
selectOption?: SelectOption
|
|
101
|
+
})
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Hook Configuration**
|
|
105
|
+
|
|
106
|
+
| Property | Type | Default | Description |
|
|
107
|
+
|----------|------|---------|-------------|
|
|
108
|
+
| data | `T[]` | - | Flat data array |
|
|
109
|
+
| handleDataConfig | `FlatDataConfig` | - | Configuration for converting flat data to hierarchical |
|
|
110
|
+
| selectOption | `SelectOption` | - | Selection configuration |
|
|
111
|
+
|
|
112
|
+
**Hook Return Values**
|
|
113
|
+
|
|
114
|
+
| Property | Type | Description |
|
|
115
|
+
|----------|------|-------------|
|
|
116
|
+
| nodes | `CascaderNodeType<T>[]` | All nodes in the cascader |
|
|
117
|
+
| columns | `CascaderItem<T>[][]` | Visible columns based on current selection |
|
|
118
|
+
| showIndices | `number[]` | Indices of currently shown nodes |
|
|
119
|
+
| checkStatusArray | `Uint8Array` | Check status of all nodes |
|
|
120
|
+
| handleShowIndices | `(depth: number, index: number) => void` | Handle column expansion |
|
|
121
|
+
| handleChangeCheckStatus | `(nodeIdx: number) => void` | Handle node selection |
|
|
122
|
+
|
|
123
|
+
### Components
|
|
124
|
+
|
|
125
|
+
#### CascaderRoot
|
|
126
|
+
|
|
127
|
+
The root component that provides cascader context.
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
<CascaderRoot
|
|
131
|
+
isSelected={boolean}
|
|
132
|
+
selectType={'single' | 'multiple'}
|
|
133
|
+
onSelectChange={(nodeIdx: number) => void}
|
|
134
|
+
{...divProps}
|
|
135
|
+
>
|
|
136
|
+
{children}
|
|
137
|
+
</CascaderRoot>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Props**
|
|
141
|
+
|
|
142
|
+
| Property | Type | Default | Description |
|
|
143
|
+
|----------|------|---------|-------------|
|
|
144
|
+
| isSelected | `boolean` | `false` | Whether selection is enabled |
|
|
145
|
+
| selectType | `'single' | 'multiple'` | - | Selection mode |
|
|
146
|
+
| onSelectChange | `(nodeIdx: number) => void` | - | Selection change callback |
|
|
147
|
+
|
|
148
|
+
#### CascaderPanel
|
|
149
|
+
|
|
150
|
+
Container for cascader columns and panels.
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
<CascaderPanel {...divProps}>
|
|
154
|
+
{children}
|
|
155
|
+
</CascaderPanel>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
#### CascaderPanelHeader
|
|
159
|
+
|
|
160
|
+
Header component for displaying column information.
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
<CascaderPanelHeader
|
|
164
|
+
column={CascaderNodeType<any>[]}
|
|
165
|
+
showNode={CascaderItem<any> | null}
|
|
166
|
+
/>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Props**
|
|
170
|
+
|
|
171
|
+
| Property | Type | Description |
|
|
172
|
+
|----------|------|-------------|
|
|
173
|
+
| column | `CascaderNodeType<any>[]` | Current column nodes |
|
|
174
|
+
| showNode | `CascaderItem<any> | null` | Currently shown node |
|
|
175
|
+
|
|
176
|
+
#### CascaderPanelIndicator
|
|
177
|
+
|
|
178
|
+
Visual indicator for panel navigation.
|
|
179
|
+
|
|
180
|
+
```tsx
|
|
181
|
+
<CascaderPanelIndicator />
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
#### CascaderNode
|
|
185
|
+
|
|
186
|
+
Individual node component in the cascader.
|
|
187
|
+
|
|
188
|
+
```tsx
|
|
189
|
+
<CascaderNode
|
|
190
|
+
node={CascaderItem<any>}
|
|
191
|
+
onSelectChange={(nodeIdx: number) => void}
|
|
192
|
+
{...divProps}
|
|
193
|
+
/>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Props**
|
|
197
|
+
|
|
198
|
+
| Property | Type | Description |
|
|
199
|
+
|----------|------|-------------|
|
|
200
|
+
| node | `CascaderItem<any>` | Node data |
|
|
201
|
+
| onSelectChange | `(nodeIdx: number) => void` | Selection change callback |
|
|
202
|
+
|
|
203
|
+
### Types
|
|
204
|
+
|
|
205
|
+
#### CascaderNodeType
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
interface CascaderNodeType<T = any> {
|
|
209
|
+
depth: number
|
|
210
|
+
index: number
|
|
211
|
+
parentIndex: number
|
|
212
|
+
range: [number, number]
|
|
213
|
+
childrenIndices: number[]
|
|
214
|
+
pathId: string
|
|
215
|
+
label: string
|
|
216
|
+
value: string | number
|
|
217
|
+
raw: T | null
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
#### CascaderItem
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
interface CascaderItem<T> {
|
|
225
|
+
indices: number
|
|
226
|
+
level: number
|
|
227
|
+
label: string
|
|
228
|
+
value: string | number
|
|
229
|
+
originData?: T
|
|
230
|
+
checkStatus?: CheckStatus
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
#### FlatDataConfig
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
interface FlatDataConfig {
|
|
238
|
+
levels: (string | [string, string])[]
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
#### SelectOption
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
interface SelectOption {
|
|
246
|
+
defaultSelected: string[]
|
|
247
|
+
selectType: 'single' | 'multiple'
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
#### CheckStatus
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
type CheckStatus = 0 | 1 | 2
|
|
255
|
+
// 0: unchecked, 1: partially checked, 2: fully checked
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Usage Examples
|
|
259
|
+
|
|
260
|
+
### Multiple Selection Mode
|
|
261
|
+
|
|
262
|
+
```tsx
|
|
263
|
+
const cascader = useCascader({
|
|
264
|
+
data,
|
|
265
|
+
handleDataConfig,
|
|
266
|
+
selectOption: {
|
|
267
|
+
defaultSelected: [],
|
|
268
|
+
selectType: 'multiple'
|
|
269
|
+
}
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
<CascaderRoot
|
|
273
|
+
isSelected={true}
|
|
274
|
+
selectType="multiple"
|
|
275
|
+
onSelectChange={cascader.handleChangeCheckStatus}
|
|
276
|
+
>
|
|
277
|
+
{/* Components */}
|
|
278
|
+
</CascaderRoot>
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Custom Node Rendering
|
|
282
|
+
|
|
283
|
+
```tsx
|
|
284
|
+
const CustomNode = ({ node, onSelectChange }) => {
|
|
285
|
+
return (
|
|
286
|
+
<CascaderNode
|
|
287
|
+
node={node}
|
|
288
|
+
onSelectChange={onSelectChange}
|
|
289
|
+
className="custom-node"
|
|
290
|
+
>
|
|
291
|
+
<div className="node-content">
|
|
292
|
+
<span className="node-label">{node.label}</span>
|
|
293
|
+
{node.checkStatus === 2 && <span>✓</span>}
|
|
294
|
+
</div>
|
|
295
|
+
</CascaderNode>
|
|
296
|
+
)
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Custom Header
|
|
301
|
+
|
|
302
|
+
```tsx
|
|
303
|
+
const CustomHeader = ({ column, showNode }) => {
|
|
304
|
+
return (
|
|
305
|
+
<CascaderPanelHeader column={column} showNode={showNode}>
|
|
306
|
+
<div className="custom-header">
|
|
307
|
+
{showNode ? `Selected: ${showNode.label}` : 'Please select'}
|
|
308
|
+
</div>
|
|
309
|
+
</CascaderPanelHeader>
|
|
310
|
+
)
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Async Data Loading
|
|
315
|
+
|
|
316
|
+
```tsx
|
|
317
|
+
const [data, setData] = useState([])
|
|
318
|
+
|
|
319
|
+
useEffect(() => {
|
|
320
|
+
fetchData().then(setData)
|
|
321
|
+
}, [])
|
|
322
|
+
|
|
323
|
+
const cascader = useCascader({
|
|
324
|
+
data,
|
|
325
|
+
handleDataConfig,
|
|
326
|
+
selectOption: {
|
|
327
|
+
defaultSelected: [],
|
|
328
|
+
selectType: 'single'
|
|
329
|
+
}
|
|
330
|
+
})
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Controlled Selection
|
|
334
|
+
|
|
335
|
+
```tsx
|
|
336
|
+
const [selectedValues, setSelectedValues] = useState([])
|
|
337
|
+
|
|
338
|
+
const handleSelect = useCallback((nodeIdx: number) => {
|
|
339
|
+
const node = cascader.nodes[nodeIdx]
|
|
340
|
+
const newValue = cascader.selectOption.selectType === 'single'
|
|
341
|
+
? [node.value]
|
|
342
|
+
: [...selectedValues, node.value]
|
|
343
|
+
|
|
344
|
+
setSelectedValues(newValue)
|
|
345
|
+
cascader.handleChangeCheckStatus(nodeIdx)
|
|
346
|
+
}, [cascader, selectedValues])
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## Advanced Patterns
|
|
350
|
+
|
|
351
|
+
### Search Functionality
|
|
352
|
+
|
|
353
|
+
```tsx
|
|
354
|
+
const [searchTerm, setSearchTerm] = useState('')
|
|
355
|
+
const [filteredData, setFilteredData] = useState(data)
|
|
356
|
+
|
|
357
|
+
useEffect(() => {
|
|
358
|
+
const filtered = data.filter(item =>
|
|
359
|
+
Object.values(item).some(value =>
|
|
360
|
+
String(value).toLowerCase().includes(searchTerm.toLowerCase())
|
|
361
|
+
)
|
|
362
|
+
)
|
|
363
|
+
setFilteredData(filtered)
|
|
364
|
+
}, [searchTerm])
|
|
365
|
+
|
|
366
|
+
const cascader = useCascader({
|
|
367
|
+
data: filteredData,
|
|
368
|
+
handleDataConfig
|
|
369
|
+
})
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Custom Animation
|
|
373
|
+
|
|
374
|
+
```tsx
|
|
375
|
+
import { motion } from 'framer-motion'
|
|
376
|
+
|
|
377
|
+
const AnimatedNode = ({ node, onSelectChange }) => {
|
|
378
|
+
return (
|
|
379
|
+
<motion.div
|
|
380
|
+
initial={{ opacity: 0, y: -10 }}
|
|
381
|
+
animate={{ opacity: 1, y: 0 }}
|
|
382
|
+
exit={{ opacity: 0, y: -10 }}
|
|
383
|
+
>
|
|
384
|
+
<CascaderNode node={node} onSelectChange={onSelectChange} />
|
|
385
|
+
</motion.div>
|
|
386
|
+
)
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
## Data Structure Examples
|
|
391
|
+
|
|
392
|
+
### Hierarchical Data (Converted from Flat)
|
|
393
|
+
|
|
394
|
+
```tsx
|
|
395
|
+
const flatData = [
|
|
396
|
+
{ level1: 'Electronics', level2: 'Phones', level3: 'iPhone' },
|
|
397
|
+
{ level1: 'Electronics', level2: 'Phones', level3: 'Samsung' },
|
|
398
|
+
{ level1: 'Electronics', level2: 'Laptops', level3: 'MacBook' },
|
|
399
|
+
]
|
|
400
|
+
|
|
401
|
+
const config = {
|
|
402
|
+
levels: ['level1', 'level2', 'level3']
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Complex Field Mapping
|
|
407
|
+
|
|
408
|
+
```tsx
|
|
409
|
+
const complexData = [
|
|
410
|
+
{
|
|
411
|
+
category: { name: 'Electronics', id: 1 },
|
|
412
|
+
product: { name: 'iPhone', type: 'Phone' },
|
|
413
|
+
brand: 'Apple'
|
|
414
|
+
}
|
|
415
|
+
]
|
|
416
|
+
|
|
417
|
+
const complexConfig = {
|
|
418
|
+
levels: [
|
|
419
|
+
['category', 'name'], // Use category.name as label
|
|
420
|
+
['product', 'name'], // Use product.name as label
|
|
421
|
+
'brand' // Use brand directly
|
|
422
|
+
]
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
## Best Practices
|
|
427
|
+
|
|
428
|
+
1. **Performance**: Use `useMemo` for data transformations and filtering
|
|
429
|
+
2. **Accessibility**: Ensure keyboard navigation and screen reader support
|
|
430
|
+
3. **State Management**: Keep selection state in parent component for controlled behavior
|
|
431
|
+
4. **Customization**: Use slots for custom rendering while maintaining core functionality
|
|
432
|
+
5. **Data Structure**: Keep data flat for better performance and easier manipulation
|
|
433
|
+
|
|
434
|
+
## License
|
|
435
|
+
|
|
436
|
+
MIT
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
+
import { useContext } from 'react';
|
|
3
|
+
import { CascaderContext, CascaderPanelContext } from '../../context/index.js';
|
|
4
|
+
|
|
5
|
+
const CascaderNode = (props) => {
|
|
6
|
+
const {
|
|
7
|
+
node,
|
|
8
|
+
isLeaf,
|
|
9
|
+
onClick,
|
|
10
|
+
onTrigger,
|
|
11
|
+
render,
|
|
12
|
+
...elementProps
|
|
13
|
+
} = props;
|
|
14
|
+
const { onSelectChange } = useContext(CascaderContext);
|
|
15
|
+
const { showIndicator, registerNode } = useContext(CascaderPanelContext);
|
|
16
|
+
const handleClick = (event) => {
|
|
17
|
+
if (isLeaf) {
|
|
18
|
+
if (onClick) onClick(node);
|
|
19
|
+
} else {
|
|
20
|
+
if (onTrigger) onTrigger(node.level, node.indices);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
return /* @__PURE__ */ jsx("div", { ref: showIndicator ? (el) => registerNode(node.indices, el) : void 0, onClick: handleClick, ...elementProps, children: render ? render({ node, onSelectChange }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
24
|
+
/* @__PURE__ */ jsx("span", { style: { flex: 1 }, children: node.label }),
|
|
25
|
+
!isLeaf && /* @__PURE__ */ jsx(
|
|
26
|
+
"svg",
|
|
27
|
+
{
|
|
28
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
29
|
+
width: "18",
|
|
30
|
+
height: "18",
|
|
31
|
+
viewBox: "0 0 24 24",
|
|
32
|
+
fill: "none",
|
|
33
|
+
stroke: "currentColor",
|
|
34
|
+
children: /* @__PURE__ */ jsx("path", { d: "m9 18 6-6-6-6" })
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
] }) });
|
|
38
|
+
};
|
|
39
|
+
CascaderNode.displayName = "CascaderNode";
|
|
40
|
+
|
|
41
|
+
export { CascaderNode };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { forwardRef, useRef, useState, useCallback, useLayoutEffect } from 'react';
|
|
3
|
+
import { CascaderPanelContext } from '../../context/index.js';
|
|
4
|
+
|
|
5
|
+
const CascaderPanel = forwardRef((props, ref) => {
|
|
6
|
+
const {
|
|
7
|
+
depth,
|
|
8
|
+
children,
|
|
9
|
+
showIndicator = false,
|
|
10
|
+
activeIndices,
|
|
11
|
+
...elementProps
|
|
12
|
+
} = props;
|
|
13
|
+
const nodeRefs = useRef(/* @__PURE__ */ new Map());
|
|
14
|
+
const [indicatorStyle, setIndicatorStyle] = useState();
|
|
15
|
+
const registerNode = useCallback((index, el) => {
|
|
16
|
+
if (el) nodeRefs.current.set(index, el);
|
|
17
|
+
else nodeRefs.current.delete(index);
|
|
18
|
+
}, []);
|
|
19
|
+
useLayoutEffect(() => {
|
|
20
|
+
if (activeIndices === void 0) return;
|
|
21
|
+
const activeEl = nodeRefs.current.get(activeIndices);
|
|
22
|
+
if (activeEl) {
|
|
23
|
+
const measure = () => {
|
|
24
|
+
setIndicatorStyle({
|
|
25
|
+
["--indicator-offset-top"]: `${activeEl.offsetTop}px`,
|
|
26
|
+
["--indicator-height"]: `${activeEl.offsetHeight}px`,
|
|
27
|
+
transform: `translateY(var(--indicator-offset-top))`,
|
|
28
|
+
height: `${activeEl.offsetHeight}px`
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
measure();
|
|
32
|
+
const resizeObserver = new ResizeObserver(measure);
|
|
33
|
+
resizeObserver.observe(activeEl);
|
|
34
|
+
}
|
|
35
|
+
}, [activeIndices]);
|
|
36
|
+
return /* @__PURE__ */ jsx(CascaderPanelContext.Provider, { value: { showIndicator, activeIndices, registerNode, indicatorStyle }, children: /* @__PURE__ */ jsx("div", { ref, "data-depth": depth, "data-active-indices": activeIndices, ...elementProps, children }) });
|
|
37
|
+
});
|
|
38
|
+
CascaderPanel.displayName = "CascaderPanel";
|
|
39
|
+
|
|
40
|
+
export { CascaderPanel };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useContext } from 'react';
|
|
3
|
+
import { CascaderContext } from '../../context/index.js';
|
|
4
|
+
|
|
5
|
+
const CascaderPanelHeader = (props) => {
|
|
6
|
+
const {
|
|
7
|
+
column,
|
|
8
|
+
showNode,
|
|
9
|
+
render,
|
|
10
|
+
...elementProps
|
|
11
|
+
} = props;
|
|
12
|
+
const { selectType } = useContext(CascaderContext);
|
|
13
|
+
if (render) render({ column, showNode });
|
|
14
|
+
else return /* @__PURE__ */ jsxs("div", { ...elementProps, children: [
|
|
15
|
+
/* @__PURE__ */ jsx("span", { children: showNode?.level }),
|
|
16
|
+
/* @__PURE__ */ jsx("span", { children: column.length })
|
|
17
|
+
] });
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export { CascaderPanelHeader };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useContext } from 'react';
|
|
3
|
+
import { CascaderPanelContext } from '../../context/index.js';
|
|
4
|
+
|
|
5
|
+
const CascaderPanelIndicator = (props) => {
|
|
6
|
+
const {
|
|
7
|
+
render,
|
|
8
|
+
...elementProps
|
|
9
|
+
} = props;
|
|
10
|
+
const { indicatorStyle, activeIndices } = useContext(CascaderPanelContext);
|
|
11
|
+
if (render) return render();
|
|
12
|
+
if (activeIndices === void 0) return null;
|
|
13
|
+
return /* @__PURE__ */ jsx("div", { style: indicatorStyle, ...elementProps });
|
|
14
|
+
};
|
|
15
|
+
CascaderPanelIndicator.displayName = "CascaderPanelIndicator";
|
|
16
|
+
|
|
17
|
+
export { CascaderPanelIndicator };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { forwardRef } from 'react';
|
|
3
|
+
import { CascaderContext } from '../../context/index.js';
|
|
4
|
+
|
|
5
|
+
const CascaderRoot = forwardRef((props, ref) => {
|
|
6
|
+
const {
|
|
7
|
+
isSelected = false,
|
|
8
|
+
selectType,
|
|
9
|
+
onSelectChange,
|
|
10
|
+
children,
|
|
11
|
+
...elementProps
|
|
12
|
+
} = props;
|
|
13
|
+
return /* @__PURE__ */ jsx(CascaderContext.Provider, { value: {
|
|
14
|
+
isSelected,
|
|
15
|
+
selectType,
|
|
16
|
+
onSelectChange
|
|
17
|
+
}, children: /* @__PURE__ */ jsx("div", { ref, ...elementProps, children }) });
|
|
18
|
+
});
|
|
19
|
+
CascaderRoot.displayName = "CascaderRoot";
|
|
20
|
+
|
|
21
|
+
export { CascaderRoot };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createContext } from 'react';
|
|
2
|
+
|
|
3
|
+
const BLANK_FUNCTION = () => {
|
|
4
|
+
};
|
|
5
|
+
const CascaderContext = createContext({
|
|
6
|
+
isSelected: false,
|
|
7
|
+
selectType: "single",
|
|
8
|
+
onSelectChange: void 0
|
|
9
|
+
});
|
|
10
|
+
const CascaderPanelContext = createContext({
|
|
11
|
+
showIndicator: false,
|
|
12
|
+
registerNode: BLANK_FUNCTION
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export { CascaderContext, CascaderPanelContext };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { useMemo, useState } from 'react';
|
|
2
|
+
import { buildCascaderEngine, getDefaultIndices, getCascadeColumns, getShowIndices } from '../utils/data.js';
|
|
3
|
+
import { setMultipleNodeStatus, setNodeCheckStatus } from '../utils/logic.js';
|
|
4
|
+
|
|
5
|
+
function useCascader(options) {
|
|
6
|
+
const { data, handleDataConfig, selectOption } = options;
|
|
7
|
+
const sortedData = useMemo(() => {
|
|
8
|
+
return data.sort((a, b) => {
|
|
9
|
+
for (const field of handleDataConfig.levels) {
|
|
10
|
+
const f = Array.isArray(field) ? field[0] : field;
|
|
11
|
+
if (a[f] !== b[f]) return String(a[f]).localeCompare(String(b[f]));
|
|
12
|
+
}
|
|
13
|
+
return 0;
|
|
14
|
+
});
|
|
15
|
+
}, [data, handleDataConfig]);
|
|
16
|
+
const defaultNodes = useMemo(() => {
|
|
17
|
+
return buildCascaderEngine(sortedData, handleDataConfig);
|
|
18
|
+
}, [sortedData, handleDataConfig]);
|
|
19
|
+
const defaultStatusArray = useMemo(() => {
|
|
20
|
+
const statusArray = new Uint8Array(defaultNodes.length);
|
|
21
|
+
if (selectOption?.defaultSelected) {
|
|
22
|
+
const defaultIndices = defaultNodes.filter(
|
|
23
|
+
(node) => selectOption.defaultSelected.find(
|
|
24
|
+
(selected) => node.raw?.value === selected
|
|
25
|
+
)
|
|
26
|
+
).map((node) => node.index);
|
|
27
|
+
return setMultipleNodeStatus(defaultNodes, statusArray, defaultIndices, 1);
|
|
28
|
+
}
|
|
29
|
+
return statusArray;
|
|
30
|
+
}, [defaultNodes, selectOption?.defaultSelected]);
|
|
31
|
+
useMemo(() => Boolean(selectOption), [selectOption]);
|
|
32
|
+
const [checkStatusArray, setCheckStatusArray] = useState(defaultStatusArray);
|
|
33
|
+
const [showIndices, setShowIndices] = useState(getDefaultIndices(defaultNodes));
|
|
34
|
+
const columns = useMemo(() => getCascadeColumns(defaultNodes, showIndices), [defaultNodes, showIndices]);
|
|
35
|
+
const handleShowIndices = (depth, index) => {
|
|
36
|
+
setShowIndices(getShowIndices(
|
|
37
|
+
defaultNodes,
|
|
38
|
+
{
|
|
39
|
+
oldShowIndices: showIndices,
|
|
40
|
+
actionIndices: {
|
|
41
|
+
depth,
|
|
42
|
+
nodeIdx: index
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
));
|
|
46
|
+
};
|
|
47
|
+
const handleChangeCheckStatus = (nodeIdx) => {
|
|
48
|
+
const newStatusArray = setNodeCheckStatus(defaultNodes, checkStatusArray, nodeIdx);
|
|
49
|
+
setCheckStatusArray(newStatusArray);
|
|
50
|
+
};
|
|
51
|
+
return {
|
|
52
|
+
nodes: defaultNodes,
|
|
53
|
+
columns,
|
|
54
|
+
showIndices,
|
|
55
|
+
checkStatusArray,
|
|
56
|
+
handleShowIndices,
|
|
57
|
+
handleChangeCheckStatus
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export { useCascader };
|
package/esm/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { CascaderRoot } from './components/root/CascaderRoot.js';
|
|
2
|
+
export { CascaderNode } from './components/node/CascaderNode.js';
|
|
3
|
+
export { CascaderNodeCheck } from './components/node/CascaderNodeCheck.js';
|
|
4
|
+
export { CascaderPanel } from './components/panel/CascaderPanel.js';
|
|
5
|
+
export { CascaderPanelHeader } from './components/panel/CascaderPanelHeader.js';
|
|
6
|
+
export { CascaderPanelIndicator } from './components/panel/CascaderPanelIndicator.js';
|
|
7
|
+
export { useCascader } from './hooks/useCascader.js';
|
|
8
|
+
export { buildCascaderEngine, getCascadeColumns, getDefaultIndices, getShowIndices } from './utils/data.js';
|
|
9
|
+
export { setMultipleNodeStatus, setNodeCheckStatus } from './utils/logic.js';
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
function createCascaderNode(option) {
|
|
2
|
+
const { depth, currentIndex, parentIndex, row, label, value, pathId } = option;
|
|
3
|
+
return {
|
|
4
|
+
depth,
|
|
5
|
+
index: currentIndex,
|
|
6
|
+
parentIndex,
|
|
7
|
+
range: [currentIndex, currentIndex],
|
|
8
|
+
childrenIndices: [],
|
|
9
|
+
pathId,
|
|
10
|
+
label,
|
|
11
|
+
value,
|
|
12
|
+
raw: row
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function buildCascaderEngine(data, config) {
|
|
16
|
+
let currentIndex = 0;
|
|
17
|
+
const currentConfig = config;
|
|
18
|
+
const nodes = [];
|
|
19
|
+
const nodeMap = /* @__PURE__ */ new Map();
|
|
20
|
+
const levelConfigs = currentConfig.levels;
|
|
21
|
+
data.forEach((item, itemIndex) => {
|
|
22
|
+
let parentIndex = -1;
|
|
23
|
+
let currentPathId = "";
|
|
24
|
+
for (let levelIndex = 0; levelIndex < levelConfigs.length; levelIndex++) {
|
|
25
|
+
const field = levelConfigs[levelIndex];
|
|
26
|
+
let labelField, valueField = "";
|
|
27
|
+
if (Array.isArray(field)) {
|
|
28
|
+
labelField = field[0];
|
|
29
|
+
valueField = field[1];
|
|
30
|
+
} else {
|
|
31
|
+
labelField = valueField = field;
|
|
32
|
+
}
|
|
33
|
+
const label = item[labelField];
|
|
34
|
+
const value = item[valueField];
|
|
35
|
+
if (label === void 0 || label === null) break;
|
|
36
|
+
const labelStr = typeof item[labelField] === "string" ? item[labelField] : `${itemIndex}-${levelIndex}`;
|
|
37
|
+
currentPathId = levelIndex == 0 ? labelStr : `${currentPathId}${labelStr}`;
|
|
38
|
+
let nodeIdx = nodeMap.get(currentPathId);
|
|
39
|
+
if (nodeIdx === void 0) {
|
|
40
|
+
nodeIdx = currentIndex++;
|
|
41
|
+
nodeMap.set(currentPathId, nodeIdx);
|
|
42
|
+
levelIndex === levelConfigs.length - 1;
|
|
43
|
+
nodes[nodeIdx] = createCascaderNode({
|
|
44
|
+
parentIndex,
|
|
45
|
+
currentIndex: nodeIdx,
|
|
46
|
+
pathId: currentPathId,
|
|
47
|
+
depth: levelIndex,
|
|
48
|
+
label,
|
|
49
|
+
value,
|
|
50
|
+
row: item
|
|
51
|
+
});
|
|
52
|
+
if (parentIndex !== -1) {
|
|
53
|
+
nodes[parentIndex].childrenIndices.push(nodeIdx);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
parentIndex = nodeIdx;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
for (let i = nodes.length - 1; i >= 0; i--) {
|
|
60
|
+
const node = nodes[i];
|
|
61
|
+
if (node.childrenIndices.length > 0) {
|
|
62
|
+
const lastChildIdx = node.childrenIndices[node.childrenIndices.length - 1];
|
|
63
|
+
node.range[1] = nodes[lastChildIdx].range[1];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return nodes;
|
|
67
|
+
}
|
|
68
|
+
function getCascadeColumns(nodes, showIndices) {
|
|
69
|
+
const columns = [];
|
|
70
|
+
const rootColumn = nodes.filter((node) => node.parentIndex === -1);
|
|
71
|
+
columns.push(rootColumn);
|
|
72
|
+
showIndices.forEach((selectedIndex) => {
|
|
73
|
+
const parentNode = nodes[selectedIndex];
|
|
74
|
+
if (parentNode && parentNode.childrenIndices.length > 0) {
|
|
75
|
+
const nextColumn = parentNode.childrenIndices.map((idx) => nodes[idx]);
|
|
76
|
+
columns.push(nextColumn);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
return columns;
|
|
80
|
+
}
|
|
81
|
+
function getShowIndices(nodes, option) {
|
|
82
|
+
const { oldShowIndices, actionIndices } = option;
|
|
83
|
+
let targetPath = [];
|
|
84
|
+
const { depth, nodeIdx } = actionIndices;
|
|
85
|
+
let currentNode = nodes[nodeIdx];
|
|
86
|
+
while (currentNode.childrenIndices.length > 0) {
|
|
87
|
+
targetPath.push(currentNode.index);
|
|
88
|
+
currentNode = nodes[currentNode.childrenIndices[0]];
|
|
89
|
+
}
|
|
90
|
+
return oldShowIndices.slice(0, depth).concat(targetPath);
|
|
91
|
+
}
|
|
92
|
+
function getDefaultIndices(nodes) {
|
|
93
|
+
let defaultIndices = [];
|
|
94
|
+
let currentNode = nodes[0];
|
|
95
|
+
while (currentNode.childrenIndices.length > 0) {
|
|
96
|
+
defaultIndices.push(currentNode.index);
|
|
97
|
+
currentNode = nodes[currentNode.childrenIndices[0]];
|
|
98
|
+
}
|
|
99
|
+
return defaultIndices;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export { buildCascaderEngine, getCascadeColumns, getDefaultIndices, getShowIndices };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const CHECK_NONE = 0;
|
|
2
|
+
const CHECK_ALL = 1;
|
|
3
|
+
const CHECK_INDETERMINATE = 2;
|
|
4
|
+
function propagateToChildren(statusArray, range, status) {
|
|
5
|
+
const [start, end] = range;
|
|
6
|
+
statusArray.fill(status, start, end + 1);
|
|
7
|
+
}
|
|
8
|
+
function propagateToParents(nodes, statusArray, childIndex) {
|
|
9
|
+
const childNode = nodes[childIndex];
|
|
10
|
+
if (!childNode || childNode.parentIndex === -1) return;
|
|
11
|
+
const parentIdx = childNode.parentIndex;
|
|
12
|
+
const parentNode = nodes[parentIdx];
|
|
13
|
+
const children = parentNode.childrenIndices;
|
|
14
|
+
let checkedCount = 0;
|
|
15
|
+
let hasIndeterminate = false;
|
|
16
|
+
for (const idx of children) {
|
|
17
|
+
const s = statusArray[idx];
|
|
18
|
+
if (s === CHECK_ALL) checkedCount++;
|
|
19
|
+
else if (s === CHECK_INDETERMINATE) hasIndeterminate = true;
|
|
20
|
+
}
|
|
21
|
+
let parentStatus = CHECK_NONE;
|
|
22
|
+
if (checkedCount === children.length) {
|
|
23
|
+
parentStatus = CHECK_ALL;
|
|
24
|
+
} else if (checkedCount > 0 || hasIndeterminate) {
|
|
25
|
+
parentStatus = CHECK_INDETERMINATE;
|
|
26
|
+
}
|
|
27
|
+
if (statusArray[parentIdx] !== parentStatus) {
|
|
28
|
+
statusArray[parentIdx] = parentStatus;
|
|
29
|
+
propagateToParents(nodes, statusArray, parentIdx);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function setNodeCheckStatus(nodes, currentStatusArray, nodeIndex) {
|
|
33
|
+
const nextStatus = new Uint8Array(currentStatusArray);
|
|
34
|
+
const targetNode = nodes[nodeIndex];
|
|
35
|
+
const targetValue = nextStatus[nodeIndex] === CHECK_ALL ? CHECK_NONE : nextStatus[nodeIndex] === CHECK_INDETERMINATE ? CHECK_NONE : CHECK_ALL;
|
|
36
|
+
propagateToChildren(nextStatus, targetNode.range, targetValue);
|
|
37
|
+
propagateToParents(nodes, nextStatus, nodeIndex);
|
|
38
|
+
return nextStatus;
|
|
39
|
+
}
|
|
40
|
+
function syncUpPath(nodes, statusArray, parentIdx) {
|
|
41
|
+
let currIdx = parentIdx;
|
|
42
|
+
while (currIdx !== -1) {
|
|
43
|
+
const node = nodes[currIdx];
|
|
44
|
+
const children = node.childrenIndices;
|
|
45
|
+
let checkedCount = 0;
|
|
46
|
+
let hasIndeterminate = false;
|
|
47
|
+
for (const childIdx of children) {
|
|
48
|
+
const s = statusArray[childIdx];
|
|
49
|
+
if (s === 1) checkedCount++;
|
|
50
|
+
else if (s === 2) hasIndeterminate = true;
|
|
51
|
+
}
|
|
52
|
+
let nextS = 0;
|
|
53
|
+
if (checkedCount === children.length) nextS = 1;
|
|
54
|
+
else if (checkedCount > 0 || hasIndeterminate) nextS = 2;
|
|
55
|
+
if (statusArray[currIdx] === nextS) break;
|
|
56
|
+
statusArray[currIdx] = nextS;
|
|
57
|
+
currIdx = node.parentIndex;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function setMultipleNodeStatus(nodes, currentStatusArray, indices, targetStatus) {
|
|
61
|
+
const nextStatus = new Uint8Array(currentStatusArray);
|
|
62
|
+
const parentsToSync = /* @__PURE__ */ new Set();
|
|
63
|
+
indices.forEach((index) => {
|
|
64
|
+
const node = nodes[index];
|
|
65
|
+
if (!node) return;
|
|
66
|
+
const [start, end] = node.range;
|
|
67
|
+
nextStatus.fill(targetStatus, start, end + 1);
|
|
68
|
+
if (node.parentIndex !== -1) {
|
|
69
|
+
parentsToSync.add(node.parentIndex);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
const sortedParents = Array.from(parentsToSync).sort((a, b) => b - a);
|
|
73
|
+
sortedParents.forEach((parentIdx) => {
|
|
74
|
+
syncUpPath(nodes, nextStatus, parentIdx);
|
|
75
|
+
});
|
|
76
|
+
return nextStatus;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export { setMultipleNodeStatus, setNodeCheckStatus };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ComponentProps } from "react";
|
|
2
|
+
import { CascaderItem, NodeSlotType } from "../../types";
|
|
3
|
+
export interface CascaderNodeProps extends Omit<ComponentProps<'div'>, "onClick"> {
|
|
4
|
+
node: CascaderItem<any>;
|
|
5
|
+
isLeaf: boolean;
|
|
6
|
+
onClick?: (node: CascaderItem<any>) => void;
|
|
7
|
+
onTrigger?: (depth: number, nodeIdx: number) => void;
|
|
8
|
+
render?: NodeSlotType;
|
|
9
|
+
}
|
|
10
|
+
export declare const CascaderNode: React.FC<CascaderNodeProps>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const CascaderNodeCheck: () => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ComponentProps } from 'react';
|
|
2
|
+
export interface CascaderPanelProps extends ComponentProps<'div'> {
|
|
3
|
+
depth: number;
|
|
4
|
+
showIndicator?: boolean;
|
|
5
|
+
activeIndices?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare const CascaderPanel: import("react").ForwardRefExoticComponent<Omit<CascaderPanelProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ComponentProps, FC } from "react";
|
|
2
|
+
import { CascaderItem, CascaderNodeType, PanelHeaderSlot } from "../../types";
|
|
3
|
+
export interface CascaderPanelHeaderProps extends ComponentProps<'div'> {
|
|
4
|
+
column: CascaderNodeType<any>[];
|
|
5
|
+
showNode: CascaderItem<any> | null;
|
|
6
|
+
render?: PanelHeaderSlot;
|
|
7
|
+
}
|
|
8
|
+
export declare const CascaderPanelHeader: FC<CascaderPanelHeaderProps>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ComponentProps, FC } from "react";
|
|
2
|
+
import { PanelIndicatorSlot } from "../../types";
|
|
3
|
+
export interface CascaderPanelIndicatorProps extends ComponentProps<'div'> {
|
|
4
|
+
render?: PanelIndicatorSlot;
|
|
5
|
+
}
|
|
6
|
+
export declare const CascaderPanelIndicator: FC<CascaderPanelIndicatorProps>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ComponentProps } from "react";
|
|
2
|
+
export interface CascaderRootProps extends ComponentProps<'div'> {
|
|
3
|
+
isSelected?: boolean;
|
|
4
|
+
selectType?: "single" | "multiple";
|
|
5
|
+
onSelectChange?: (nodeIdx: number) => void;
|
|
6
|
+
}
|
|
7
|
+
export declare const CascaderRoot: import("react").ForwardRefExoticComponent<Omit<CascaderRootProps, "ref"> & import("react").RefAttributes<unknown>>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { CSSProperties } from "react";
|
|
2
|
+
export declare const CascaderContext: import("react").Context<{
|
|
3
|
+
isSelected: boolean;
|
|
4
|
+
selectType: "single" | "multiple" | undefined;
|
|
5
|
+
onSelectChange: ((nodeIdx: number) => void) | undefined;
|
|
6
|
+
}>;
|
|
7
|
+
export declare const CascaderPanelContext: import("react").Context<{
|
|
8
|
+
showIndicator: boolean;
|
|
9
|
+
indicatorStyle?: CSSProperties;
|
|
10
|
+
activeIndices?: number;
|
|
11
|
+
registerNode: (index: number, el: HTMLElement | null) => void;
|
|
12
|
+
}>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { CascaderNodeType, SelectOption, FlatDataConfig } from "../types";
|
|
2
|
+
export type CascaderHookOption<T> = {
|
|
3
|
+
data: T[];
|
|
4
|
+
handleDataConfig: FlatDataConfig;
|
|
5
|
+
selectOption?: SelectOption;
|
|
6
|
+
};
|
|
7
|
+
export declare function useCascader<T extends Record<string, any>>(options: CascaderHookOption<T>): {
|
|
8
|
+
nodes: CascaderNodeType<T>[];
|
|
9
|
+
columns: CascaderNodeType<T>[][];
|
|
10
|
+
showIndices: number[];
|
|
11
|
+
checkStatusArray: Uint8Array<ArrayBufferLike>;
|
|
12
|
+
handleShowIndices: (depth: number, index: number) => void;
|
|
13
|
+
handleChangeCheckStatus: (nodeIdx: number) => void;
|
|
14
|
+
};
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
export type CheckStatus = 0 | 1 | 2;
|
|
3
|
+
/**============================================
|
|
4
|
+
** Data
|
|
5
|
+
*=============================================**/
|
|
6
|
+
export interface CascaderNodeType<T = any> {
|
|
7
|
+
depth: number;
|
|
8
|
+
index: number;
|
|
9
|
+
parentIndex: number;
|
|
10
|
+
range: [number, number];
|
|
11
|
+
childrenIndices: number[];
|
|
12
|
+
pathId: string;
|
|
13
|
+
label: string;
|
|
14
|
+
value: string | number;
|
|
15
|
+
raw: T | null;
|
|
16
|
+
}
|
|
17
|
+
export interface CascaderItem<T> {
|
|
18
|
+
indices: number;
|
|
19
|
+
level: number;
|
|
20
|
+
label: string;
|
|
21
|
+
value: string | number;
|
|
22
|
+
originData?: T;
|
|
23
|
+
checkStatus?: CheckStatus;
|
|
24
|
+
}
|
|
25
|
+
/**============================================
|
|
26
|
+
** Utils
|
|
27
|
+
*=============================================**/
|
|
28
|
+
export type FlatDataConfig = {
|
|
29
|
+
levels: (string | [string, string])[];
|
|
30
|
+
};
|
|
31
|
+
export type GetShowIndicesOption = {
|
|
32
|
+
oldShowIndices: number[];
|
|
33
|
+
actionIndices: {
|
|
34
|
+
depth: number;
|
|
35
|
+
nodeIdx: number;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
export type GetCascaderColumnsOption = {
|
|
39
|
+
checkStatusArray?: Uint8Array<ArrayBufferLike>;
|
|
40
|
+
};
|
|
41
|
+
/**============================================
|
|
42
|
+
** Others
|
|
43
|
+
*=============================================**/
|
|
44
|
+
export type SelectOption = {
|
|
45
|
+
defaultSelected: string[];
|
|
46
|
+
selectType: "single" | "multiple";
|
|
47
|
+
};
|
|
48
|
+
/**============================================
|
|
49
|
+
** Slot
|
|
50
|
+
*=============================================**/
|
|
51
|
+
export type PanelHeaderSlot = (props: {
|
|
52
|
+
column: CascaderNodeType<any>[];
|
|
53
|
+
showNode: CascaderItem<any> | null;
|
|
54
|
+
}) => ReactNode;
|
|
55
|
+
export type PanelIndicatorSlot = (props?: any) => ReactNode;
|
|
56
|
+
export type NodeSlotType = (props: {
|
|
57
|
+
node: CascaderItem<any>;
|
|
58
|
+
onSelectChange?: (nodeIdx: number) => void;
|
|
59
|
+
}) => ReactNode;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { CascaderNodeType, FlatDataConfig, GetShowIndicesOption } from '../types/index.js';
|
|
2
|
+
export declare function buildCascaderEngine<T extends Record<string, any>>(data: T[], config: FlatDataConfig): CascaderNodeType<T>[];
|
|
3
|
+
export declare function getCascadeColumns<T>(nodes: CascaderNodeType<T>[], showIndices: number[]): CascaderNodeType<T>[][];
|
|
4
|
+
export declare function getShowIndices<T>(nodes: CascaderNodeType<T>[], option: GetShowIndicesOption): number[];
|
|
5
|
+
export declare function getDefaultIndices<T>(nodes: CascaderNodeType<T>[]): number[];
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { CascaderNodeType } from "src/types";
|
|
2
|
+
export declare function setNodeCheckStatus<T>(nodes: CascaderNodeType<T>[], currentStatusArray: Uint8Array, nodeIndex: number): Uint8Array;
|
|
3
|
+
export declare function setMultipleNodeStatus<T>(nodes: CascaderNodeType<T>[], currentStatusArray: Uint8Array, indices: number[], targetStatus: number): Uint8Array;
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zidian-primitive/cascader",
|
|
3
|
+
"version": "0.0.0-next-20260204023823",
|
|
4
|
+
"main": "./src/index.ts",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/zidcn/component-lib.git",
|
|
10
|
+
"directory": "packages/@zidian-primitive/cascader"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./lib/index.d.ts",
|
|
15
|
+
"import": "./esm/index.js",
|
|
16
|
+
"default": "./esm/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"esm",
|
|
21
|
+
"lib",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
24
|
+
"author": "GWR",
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"react": "^18.x || ^19.x",
|
|
27
|
+
"react-dom": "^18.x || ^19.x"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"react": "19.0.0",
|
|
31
|
+
"react-dom": "19.0.0"
|
|
32
|
+
}
|
|
33
|
+
}
|