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

atinc / ngx-tethys / #102

26 May 2026 08:11AM UTC coverage: 91.111% (+0.7%) from 90.407%
#102

push

web-flow
build: bump docgeni to 2.8.0-next.5 (#3809)

4571 of 5491 branches covered (83.25%)

Branch coverage included in aggregate %.

13141 of 13949 relevant lines covered (94.21%)

966.75 hits per line

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

90.22
/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,
17
    ThyTransferChangeEvent,
18
    ThyTransferDragEvent,
19
    ThyTransferItem,
20
    ThyTransferSelectEvent,
21
    TransferDirection
22
} from './transfer.interface';
23
import { ThyFlexibleText } from 'ngx-tethys/flexible-text';
24
import { ThyIcon } from 'ngx-tethys/icon';
25
import { NgClass, NgTemplateOutlet } from '@angular/common';
26
import { ThyTransferList } from './transfer-list.component';
27
import { coerceBooleanProperty, isUndefinedOrNull, ThyBooleanInput } from 'ngx-tethys/util';
28

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

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

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

55
    readonly thyRenderRightTemplateRef = input<TemplateRef<any>>();
19✔
56

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

137
    ngOnInit() {}
138

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

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

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

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

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

187
    onMove(to: Direction) {
188
        const fromDataSource = to === TransferDirection.right ? this.leftDataSource : this.rightDataSource;
11✔
189
        const toDataSource = to === TransferDirection.right ? this.rightDataSource : this.leftDataSource;
11✔
190
        const selections = fromDataSource.filter(item => item.checked);
51✔
191
        const changeEvent: ThyTransferChangeEvent = {
11✔
192
            from: to === TransferDirection.right ? TransferDirection.left : TransferDirection.right,
11✔
193
            to: to,
194
            items: [...selections]
195
        };
196
        selections.forEach(item => {
11✔
197
            const index = fromDataSource.indexOf(item);
11✔
198
            const removed = fromDataSource.splice(index, 1)[0];
11✔
199
            removed.checked = !removed.checked;
11✔
200
            removed.direction = to;
11✔
201
            toDataSource.push(removed);
11✔
202
        });
203
        this.thyChange.emit({
11✔
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;
4!
212
        const otherListData = this.groupListByIsLock(otherDirectionData);
4✔
213
        this.thyDraggableUpdate.emit({
4✔
214
            ...event.dragEvent,
215
            left: direction === TransferDirection.left ? event.listData : otherListData,
4!
216
            right: direction === TransferDirection.right ? event.listData : otherListData
4!
217
        });
218

219
        this.rightDataSource =
4✔
220
            direction === TransferDirection.right
4!
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