diff --git a/lib/list/src/component/list.tsx b/lib/list/src/component/list.tsx index fdbc757022..5c74dafe38 100644 --- a/lib/list/src/component/list.tsx +++ b/lib/list/src/component/list.tsx @@ -1,4 +1,4 @@ -import {$, HElement, classes, fetchData, mergeProps, removeUndefinedProps} from '@zui/core'; +import {$, Computed, HElement, classes, fetchData, mergeProps, removeUndefinedProps} from '@zui/core'; import {CommonList} from '@zui/common-list/src/component'; import {Listitem} from './listitem'; @@ -24,10 +24,26 @@ export class List

>(() => { + const map = new Set(); + const {active} = this.props; + if (Array.isArray(active)) { + active.forEach(x => map.add(x)); + } else if (typeof active === 'string') { + map.add(active); + } else if (active) { + Object.keys(active).forEach(x => active[x] && map.add(x)); + } + const {activeMap} = this.state; + Object.keys(activeMap).forEach(x => activeMap[x] ? map.add(x) : map.delete(x)); + return map; + }, () => [this.state.activeMap, this.props.active]); + constructor(props: P) { super(props); this.state = { checked: {}, + activeMap: {}, } as S; } @@ -86,14 +102,14 @@ export class List

this.isChecked(key!, index) === true); } - toggleAllChecked(checked?: boolean): void { + toggleAllChecked(checked?: boolean) { if (checked === undefined) { checked = !this.isAllChecked(); } - this.toggleChecked(this._renderedItems.map(x => x.key!), checked); + return this.toggleChecked(this._renderedItems.map(x => x.key!), checked); } - toggleChecked(keyOrChange: ItemKey | ItemKey[] | Record, checked?: boolean): void { + async toggleChecked(keyOrChange: ItemKey | ItemKey[] | Record, checked?: boolean) { let change: Record; if (Array.isArray(keyOrChange)) { if (!keyOrChange.length) { @@ -119,12 +135,12 @@ export class List

({ + await this.changeState(prevState => ({ checked: { ...prevState.checked, ...change, }, - }), () => { + } as Partial), () => { const checkState = this.state.checked; this.props.onCheck?.call(this, change, Object.keys(checkState).filter(x => checkState[x] === true)); }); @@ -139,6 +155,77 @@ export class List

{ + const activeMap = this.props.multipleActive ? (keys as string[]).reduce>((map, key) => { + map[key] = active!; + return map; + }, {...prevState.activeMap}) : {[keys[0]]: active!}; + return {activeMap} as Partial; + }, () => { + this.props.onActive?.call(this, keys as string[], active!); + }); + } + + getNextItem(key: string | undefined, condition?: (item: Item, index: number) => boolean, step = 1, items: Item[] | undefined = undefined): Item | undefined { + items = items || this._renderedItems; + if (key === undefined) { + return items.at(step ? 0 : -1); + } + const count = items.length; + let index = items.findIndex(x => x.key === key); + if (index < 0 || count < 2) { + return items.at(step ? 0 : -1); + } + let checkCount = 0; + condition = condition || ((x) => x.type === 'item' && !x.disabled); + while (checkCount < count) { + index = (index + step + count) % count; + const nextItem = items[index]; + if (nextItem && condition.call(this, nextItem, index)) { + return nextItem; + } + checkCount++; + } + } + + getPrevItem(key: string | undefined, condition?: (item: Item, index: number) => boolean): Item | undefined { + return this.getNextItem(key, condition, -1); + } + + activeNext(condition?: (item: Item, index: number) => boolean, step = 1) { + const nextItem = this.getNextItem(this.getActiveKey(), condition, step); + if (nextItem) { + this.toggleActive(nextItem.key!); + } + } + + activePrev(condition?: (item: Item, index: number) => boolean) { + this.activeNext(condition, -1); + } + protected _afterRender(firstRender: boolean) { this.props.afterRender?.call(this, firstRender); } @@ -168,10 +255,13 @@ export class List

): ComponentChildren { + this._hasIcons = false; + this._hasCheckbox = false; + this._activeSet.compute(); const children = super._getChildren(props) as ComponentChild[]; const {loadFailed} = this.state; if (loadFailed) { diff --git a/lib/list/src/component/nested-list.tsx b/lib/list/src/component/nested-list.tsx index a44ae2a5e6..889ca9cfad 100644 --- a/lib/list/src/component/nested-list.tsx +++ b/lib/list/src/component/nested-list.tsx @@ -58,8 +58,9 @@ function parentKeys(keyPath: string) { }, []); } -function initItemMap(items: Item[], itemKey: string | undefined, map: Map = new Map(), level = 0, parent?: ItemInfo) { - items.forEach((item, index) => { + +function reduceNestedItems(items: Item[], itemKey: string | undefined, reducer: (previousValue: T, info: ItemInfo) => T, initialValue: T, level = 0, parent?: ItemInfo): T { + return items.reduce((currentValue, item, index) => { const key = String((itemKey ? item[itemKey] : item.key) ?? (item.key ?? index)); const keyPath = parent ? `${parent.keyPath}:${key}` : key; const itemInfo = { @@ -74,12 +75,19 @@ function initItemMap(items: Item[], itemKey: string | undefined, map: Map = new Map()) { + return reduceNestedItems(items, itemKey, (currentMap, info) => { + currentMap.set(info.keyPath, info); + return currentMap; + }, map); } export class NestedList

extends List { @@ -191,7 +199,7 @@ export class NestedList

{ + await this.changeState(prevState => { const newNestedShow: Record = { ...prevState.nestedShow, [keyPath]: toggle!, @@ -227,7 +235,7 @@ export class NestedList

; }, this._preserveState); } @@ -255,7 +263,7 @@ export class NestedList

, checked?: CheckedType): void { + async toggleChecked(keyOrChange: ItemKey | ItemKey[] | Record, checked?: CheckedType) { let change: Record; if (Array.isArray(keyOrChange)) { if (!keyOrChange.length) { @@ -281,7 +289,7 @@ export class NestedList

{ + await this.changeState(({checked: prevChecked}) => { const isChecked = (item: ItemInfo) => { return change[item.keyPath] ?? prevChecked[item.keyPath] ?? item.data.checked ?? false; }; @@ -313,19 +321,76 @@ export class NestedList

; }, () => { const checkState = this.state.checked; this.props.onCheck?.call(this, change, Object.keys(checkState).filter(x => checkState[x] === true)); }); - } else { - const {parentKey, onCheck} = this.props; - const nestedChange = Object.keys(change).reduce>((map, key) => { - map[`${parentKey !== undefined ? `${parentKey}:` : ''}${key}`] = change[key]; - return map; - }, {}); - onCheck!.call(this, nestedChange, []); + return; + } + + const {parentKey, onCheck} = this.props; + const nestedChange = Object.keys(change).reduce>((map, key) => { + map[`${parentKey !== undefined ? `${parentKey}:` : ''}${key}`] = change[key]; + return map; + }, {}); + onCheck!.call(this, nestedChange, []); + } + + isActive(keyPath: string | Item) { + if (typeof keyPath === 'object') { + const keyOrKeyPath = (keyPath._keyPath ?? keyPath.key) as (string | undefined); + if (keyOrKeyPath === undefined) { + return false; + } + keyPath = keyOrKeyPath; } + const parentKey = this.props.parentKey!; + if (!this.isRoot && !keyPath.startsWith(parentKey + ':')) { + keyPath = `${parentKey}:${keyPath}`; + } + return this._activeSet.cache.has(keyPath); + } + + async toggleActive(keys: string | string[], active?: boolean) { + if (typeof keys === 'string') { + keys = [keys]; + } + if (this.isRoot) { + await super.toggleActive(keys, active); + if (this.props.toggleOnActive) { + (keys as string[]).forEach(key => { + console.log('> active', key, this.isExpanded(key)); + if (this.isActive(key) && !this.isExpanded(key)) { + this.toggle(key, true); + } + }); + return; + } + } + + this.props.onActive?.call(this, keys, active ?? !this.isActive(keys[0])); + } + + activeNext(condition?: (item: Item, index: number) => boolean, step = 1) { + const nextItem = this.getNextItem(this.getActiveKey(), condition, step); + if (nextItem) { + this.toggleActive(nextItem._keyPath as string); + } + } + + getNextItem(key: string | undefined, condition?: (item: Item, index: number) => boolean, step = 1, items: Item[] | undefined = undefined): Item | undefined { + items = items || reduceNestedItems(this._items, this.props.itemKey, (list, info) => { + list.push({ + _keyPath: info.keyPath, + type: 'item', + ...info.data, + ...this._renderedItemMap.get(info.keyPath), + key: info.keyPath, + }); + return list; + }, []); + return super.getNextItem(key, condition, step, items); } protected _afterRender(firstRender: boolean): void { @@ -358,6 +423,7 @@ export class NestedList

>((propMap, key) => { propMap[key] = props[key as keyof P]; return propMap; @@ -370,9 +436,10 @@ export class NestedList

extends CommonListProps checkbox?: boolean | CheckboxProps; checkOnClick?: boolean | 'any' | (string & {}); activeOnChecked?: boolean; + active?: string | string[] | Record; + multipleActive?: boolean; + activeOnHover?: boolean; + onActive?: (keys: string[], active: boolean) => void; onCheck?: (change: Record, checks: ItemKey[]) => void; onLoad?: (items: T[]) => void | T[]; onLoadFail?: CustomContentType | ((error: Error) => CustomContentType | void); diff --git a/lib/list/src/types/list-state.ts b/lib/list/src/types/list-state.ts index 9c8c6daec6..c640cea9e7 100644 --- a/lib/list/src/types/list-state.ts +++ b/lib/list/src/types/list-state.ts @@ -8,4 +8,5 @@ export interface ListState { loading?: boolean; loadFailed?: CustomContentType; checked: Record; + activeMap: Record; }