• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

atinc / ngx-tethys / 88a8ea7d-7a63-4af9-a0c3-eae327277eaa

28 May 2025 08:26AM UTC coverage: 90.316% (+0.03%) from 90.291%
88a8ea7d-7a63-4af9-a0c3-eae327277eaa

push

circleci

web-flow
refactor(transfer): migrate to signal #TINFR-1783 (#3458)

5561 of 6830 branches covered (81.42%)

Branch coverage included in aggregate %.

24 of 25 new or added lines in 2 files covered. (96.0%)

3 existing lines in 2 files now uncovered.

13735 of 14535 relevant lines covered (94.5%)

899.41 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.9
/src/transfer/transfer.component.ts
1
import {
2
    Component,
3
    OnInit,
4
    TemplateRef,
5
    ViewEncapsulation,
6
    computed,
7
    input,
8
    output,
9
    contentChild,
10
    numberAttribute,
11
    effect
12
} from '@angular/core';
13

14
import {
15
    Direction,
16
    InnerTransferDragEvent,
1✔
17
    ThyTransferChangeEvent,
18
    ThyTransferDragEvent,
19✔
19
    ThyTransferItem,
19✔
20
    ThyTransferSelectEvent,
19✔
21
    TransferDirection
19✔
22
} from './transfer.interface';
19✔
23
import { ThyFlexibleText } from 'ngx-tethys/flexible-text';
19✔
24
import { ThyIcon } from 'ngx-tethys/icon';
19✔
25
import { NgClass, NgTemplateOutlet } from '@angular/common';
19✔
26
import { ThyTransferList } from './transfer-list.component';
19✔
27
import { coerceBooleanProperty, ThyBooleanInput } from 'ngx-tethys/util';
19✔
28

19✔
29
/**
19✔
30
 * 穿梭框组件
19✔
31
 * @name thy-transfer
19✔
32
 * @order 10
19✔
33
 */
19✔
34
@Component({
19✔
35
    selector: 'thy-transfer',
19!
36
    templateUrl: './transfer.component.html',
19!
37
    host: {
19✔
38
        class: 'thy-transfer'
19✔
39
    },
40
    encapsulation: ViewEncapsulation.None,
41
    imports: [ThyTransferList, ThyIcon, NgClass, NgTemplateOutlet, ThyFlexibleText]
42
})
19✔
43
export class ThyTransfer implements OnInit {
19✔
44
    public leftDataSource: ThyTransferItem[] = [];
19✔
45

19✔
46
    public rightDataSource: ThyTransferItem[] = [];
171✔
47
    /**
76✔
48
     * 数据源
49
     * @type ThyTransferItem[]
171✔
50
     */
95✔
51
    readonly thyData = input<ThyTransferItem[]>([]);
52

53
    readonly thyRenderLeftTemplateRef = input<TemplateRef<any>>();
54

55
    readonly thyRenderRightTemplateRef = input<TemplateRef<any>>();
13!
UNCOV
56

×
57
    /**
58
     * title集合,title[0]为左标题,title[1]为右标题
13✔
59
     * @type string[]
2✔
60
     */
61
    readonly thyTitles = input<string[]>([]);
11✔
62

11✔
63
    /**
11!
64
     * 右侧列表是否可以锁定
11✔
65
     * @default false
66
     */
67
    readonly thyRightCanLock = input<boolean, ThyBooleanInput>(false, { transform: coerceBooleanProperty });
68

1✔
69
    /**
70
     * 右侧锁定最大数量
71
     */
2✔
72
    readonly thyRightLockMax = input<number, unknown>(undefined, { transform: numberAttribute });
73

×
74
    /**
26✔
75
     * 右侧选择最大数量
26✔
76
     */
115✔
77
    readonly thyRightMax = input<number, unknown>(undefined, { transform: numberAttribute });
11✔
78

79
    /**
80
     * 设置是否自动移动
104✔
81
     * @description.en-us Currently not implemented, in order to support the selections move
82
     * @default true
83
     */
26✔
84
    readonly thyAutoMove = input<boolean, ThyBooleanInput>(true, { transform: coerceBooleanProperty });
85

86
    /**
11✔
87
     * 左侧列表是否拖动
11✔
88
     * @default false
51✔
89
     */
11✔
90
    readonly thyLeftDraggable = input<boolean, ThyBooleanInput>(false, { transform: coerceBooleanProperty });
11✔
91

92
    /**
93
     * 右侧列表是否拖动
94
     * @default false
11✔
95
     */
11✔
96
    readonly thyRightDraggable = input<boolean, ThyBooleanInput>(false, { transform: coerceBooleanProperty });
11✔
97

11✔
98
    /**
11✔
99
     * @type EventEmitter<ThyTransferDragEvent>
11✔
100
     */
101
    thyDraggableUpdate = output<ThyTransferDragEvent>();
11✔
102

103
    /**
104
     * Transfer变化的回调事件
105
     * @type EventEmitter<ThyTransferChangeEvent>
106
     */
107
    thyChange = output<ThyTransferChangeEvent>();
108

4!
109
    /**
4✔
110
     * 设置自定义Item渲染数据模板
4✔
111
     * @type TemplateRef
112
     */
4!
113
    readonly templateRef = contentChild<TemplateRef<any>>('renderTemplate');
4!
114

115
    /**
4✔
116
     * 设置自定义左侧内容模板
4!
117
     * @type TemplateRef
118
     */
119
    readonly leftContentRef = contentChild<TemplateRef<any>>('renderLeftTemplate');
120

1✔
121
    /**
1✔
122
     * 设置自定义右侧内容模板
123
     * @type TemplateRef
124
     */
125
    readonly rightContentRef = contentChild<TemplateRef<any>>('renderRightTemplate');
126

127
    readonly leftTitle = computed(() => this.thyTitles()[0] || '');
128

129
    readonly rightTitle = computed(() => this.thyTitles()[1] || '');
130

131
    constructor() {
132
        effect(() => {
133
            this.initializeTransferData();
134
        });
135
    }
136

137
    ngOnInit() {}
138

139
    initializeTransferData(data: ThyTransferItem[] = []) {
1✔
140
        this.leftDataSource = [];
141
        this.rightDataSource = [];
142
        this.thyData().forEach(item => {
143
            if (item.direction === TransferDirection.left) {
144
                this.leftDataSource.push(item);
145
            }
146
            if (item.direction === TransferDirection.right) {
147
                this.rightDataSource.push(item);
148
            }
149
        });
150
    }
151

152
    onSelect(from: Direction, event: ThyTransferSelectEvent) {
153
        if (event.item.isFixed) {
154
            return;
155
        }
156
        if (this.thyRightMax() <= this.rightDataSource.length && from === TransferDirection.left) {
157
            return;
158
        }
159
        const to = from === TransferDirection.left ? TransferDirection.right : TransferDirection.left;
160
        event.item.checked = !event.item.checked;
161
        if (this.thyAutoMove()) {
162
            this.onMove(to);
163
        }
164
    }
165

166
    selectItem(event: ThyTransferSelectEvent) {
167
        this.onSelect(TransferDirection.left, event);
168
    }
169

170
    unselectItem(event: ThyTransferSelectEvent) {
171
        this.onSelect(TransferDirection.right, event);
172
    }
173

174
    private groupListByIsLock(list: ThyTransferItem[] = []) {
175
        const lock: ThyTransferItem[] = [],
176
            unlock: ThyTransferItem[] = [];
177
        list.forEach(item => {
178
            if (item.isLock) {
179
                lock.push(item);
180
            } else {
181
                unlock.push(item);
182
            }
183
        });
184
        return { lock: lock, unlock: unlock };
185
    }
186

187
    onMove(to: Direction) {
188
        const fromDataSource = to === TransferDirection.right ? this.leftDataSource : this.rightDataSource;
189
        const toDataSource = to === TransferDirection.right ? this.rightDataSource : this.leftDataSource;
190
        const selections = fromDataSource.filter(item => item.checked);
191
        const changeEvent: ThyTransferChangeEvent = {
192
            from: to === TransferDirection.right ? TransferDirection.left : TransferDirection.right,
193
            to: to,
194
            items: [...selections]
195
        };
196
        selections.forEach(item => {
197
            const index = fromDataSource.indexOf(item);
198
            const removed = fromDataSource.splice(index, 1)[0];
199
            removed.checked = !removed.checked;
200
            removed.direction = to;
201
            toDataSource.push(removed);
202
        });
203
        this.thyChange.emit({
204
            ...changeEvent,
205
            left: this.groupListByIsLock(this.leftDataSource),
206
            right: this.groupListByIsLock(this.rightDataSource)
207
        });
208
    }
209

210
    onDragUpdate(direction: Direction, event: InnerTransferDragEvent) {
211
        const otherDirectionData = direction === TransferDirection.left ? this.rightDataSource : this.leftDataSource;
212
        const otherListData = this.groupListByIsLock(otherDirectionData);
213
        this.thyDraggableUpdate.emit({
214
            ...event.dragEvent,
215
            left: direction === TransferDirection.left ? event.listData : otherListData,
216
            right: direction === TransferDirection.right ? event.listData : otherListData
217
        });
218

219
        this.rightDataSource =
220
            direction === TransferDirection.right
221
                ? [...event.listData.lock, ...event.listData.unlock]
222
                : [...otherListData.lock, ...otherListData.unlock];
223
    }
224
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc