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

atinc / ngx-tethys / 5ba5b9d7-3ca9-4ff2-bbba-bde58c0f849f

22 Feb 2024 09:41AM UTC coverage: 90.604%. Remained the same
5ba5b9d7-3ca9-4ff2-bbba-bde58c0f849f

Pull #3027

circleci

minlovehua
feat(schematics): provide schematics for removing the suffix of standalone components #INFR-11662
Pull Request #3027: refactor: remove the component suffix for standalone components and provide schematics #INFR-10654

5425 of 6642 branches covered (81.68%)

Branch coverage included in aggregate %.

323 of 333 new or added lines in 193 files covered. (97.0%)

36 existing lines in 8 files now uncovered.

13504 of 14250 relevant lines covered (94.76%)

981.28 hits per line

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

94.62
/src/layout/sidebar.component.ts
1
import { NgClass, NgIf, NgStyle, NgTemplateOutlet } from '@angular/common';
2
import {
3
    Component,
4
    Directive,
5
    ElementRef,
6
    EventEmitter,
7
    Host,
8
    HostBinding,
9
    HostListener,
10
    Input,
11
    OnDestroy,
12
    OnInit,
1✔
13
    Optional,
1✔
14
    Output,
1✔
15
    TemplateRef,
16
    inject
17
} from '@angular/core';
18
import { ThyHotkeyDispatcher } from '@tethys/cdk/hotkey';
19
import { isMacPlatform } from '@tethys/cdk/is';
20
import { InputBoolean, InputNumber } from 'ngx-tethys/core';
1✔
21
import { ThyIcon } from 'ngx-tethys/icon';
22
import { ThyResizableDirective, ThyResizeEvent, ThyResizeHandle } from 'ngx-tethys/resizable';
45✔
23
import { ThyTooltipDirective } from 'ngx-tethys/tooltip';
2✔
24
import { coerceBooleanProperty } from 'ngx-tethys/util';
25
import { Subscription } from 'rxjs';
45✔
26
import { ThyLayoutDirective } from './layout.component';
27

28
const LG_WIDTH = 300;
42✔
29
const SIDEBAR_DEFAULT_WIDTH = 240;
30
const SIDEBAR_COLLAPSED_WIDTH = 20;
31

102✔
32
export type ThySidebarTheme = 'white' | 'light' | 'dark';
33

34
export type ThySidebarDirection = 'left' | 'right';
29✔
35

36
/**
37
 * 侧边栏布局指令
29✔
38
 * @name thySidebar
39
 * @order 20
40
 */
42✔
41
@Directive({
42✔
42
    selector: '[thySidebar]',
42✔
43
    host: {
42✔
44
        class: 'thy-layout-sidebar',
45
        '[class.thy-layout-sidebar-right]': 'thyDirection === "right"',
46
        '[class.thy-layout-sidebar--clear-border-right]': 'thyDirection === "left" && !isDivided',
42!
47
        '[class.thy-layout-sidebar--clear-border-left]': 'thyDirection === "right" && !isDivided',
42✔
48
        '[class.sidebar-theme-light]': 'thyTheme === "light"',
49
        '[class.sidebar-theme-dark]': 'thyTheme === "dark"',
42✔
50
        '[class.thy-layout-sidebar-isolated]': 'sidebarIsolated'
14✔
51
    },
52
    standalone: true
53
})
1✔
54
export class ThySidebarDirective implements OnInit {
55
    sidebarIsolated = false;
56

1✔
57
    isDivided = true;
58

59
    @HostBinding('style.width.px') thyLayoutSidebarWidth: number;
60

61
    /**
62
     * sidebar 位置,默认在左侧
63
     */
64
    @Input() thyDirection: ThySidebarDirection = 'left';
65

66
    /**
67
     * 主题
1✔
68
     * @type white | light | dark
69
     * @default white
70
     */
71
    @Input() thyTheme: ThySidebarTheme;
72

73
    /**
74
     * 宽度,默认是 240px,传入 `lg` 大小时宽度是300px
75
     * @default 240px
76
     */
77
    @Input('thyWidth')
78
    set thyWidth(value: string | number) {
79
        if (value === 'lg') {
80
            value = LG_WIDTH;
81
        }
82
        this.thyLayoutSidebarWidth = (value as number) || SIDEBAR_DEFAULT_WIDTH;
83
    }
84

85
    /**
86
     * 是否和右侧 /左侧隔离,当为 true 时距右侧 /左侧会有 margin,同时边框会去掉
87
     * @default false
88
     */
89
    @Input('thyIsolated')
1✔
90
    set thyIsolated(value: string) {
91
        this.sidebarIsolated = coerceBooleanProperty(value);
134✔
92
    }
6✔
93

94
    /**
95
     * sidebar 是否有分割线。当`thyDirection`值为`left`时,控制右侧是否有分割线;当`thyDirection`值为`right`时,控制左侧是否有分割线。
128✔
96
     * @default true
97
     */
98
    @Input('thyDivided')
99
    set thyDivided(value: string) {
2✔
100
        this.isDivided = coerceBooleanProperty(value);
101
    }
102

2✔
103
    /**
104
     * 右侧是否有边框,已废弃,请使用 thyDivided
105
     * @deprecated please use thyDivided
37✔
106
     * @default true
37✔
107
     */
9✔
108
    @Input('thyHasBorderRight')
109
    set thyHasBorderRight(value: string) {
110
        this.thyDivided = value;
28✔
111
    }
112

113
    /**
114
     * 左侧是否有边框,已废弃,请使用 thyDivided
415✔
115
     * @deprecated please use thyDivided
116
     * @default true
117
     */
39✔
118
    @Input('thyHasBorderLeft')
119
    set thyHasBorderLeft(value: string) {
120
        this.thyDivided = value;
252✔
121
    }
122

123
    constructor(@Optional() @Host() private thyLayoutDirective: ThyLayoutDirective) {}
134✔
124

125
    ngOnInit() {
126
        if (this.thyLayoutDirective) {
134✔
127
            this.thyLayoutDirective.hasSidebar = true;
128
        }
129
        if (this.thyDirection === 'right') {
24✔
130
            this.thyLayoutDirective.isSidebarRight = true;
131
        }
132
    }
30✔
133
}
30✔
134

30✔
135
/**
30✔
136
 * 侧边栏布局组件
30✔
137
 * @name thy-sidebar
30✔
138
 * @order 21
30✔
139
 */
30✔
140
@Component({
30✔
141
    selector: 'thy-sidebar',
30✔
142
    preserveWhitespaces: false,
143
    template: `
144
        <ng-content></ng-content>
30✔
145
        <div
146
            thyResizable
147
            class="sidebar-drag"
9✔
148
            *ngIf="thyDraggable"
2✔
149
            thyBounds="window"
150
            [thyMaxWidth]="thyDragMaxWidth"
151
            [thyMinWidth]="dragMinWidth"
152
            (thyResize)="resizeHandler($event)"
37✔
153
            (thyResizeStart)="resizeStart()"
37!
154
            (thyResizeEnd)="resizeEnd()"
155
            [style.display]="!isResizable ? 'contents' : null">
156
            <thy-resize-handle
7✔
157
                *ngIf="!thyCollapsed"
1✔
158
                [thyDirection]="sidebarDirective.thyDirection === 'right' ? 'left' : 'right'"
159
                class="sidebar-resize-handle"
6!
160
                thyLine="true"
×
161
                (mouseenter)="toggleResizable($event, 'enter')"
162
                (mouseleave)="toggleResizable($event, 'leave')"
6✔
163
                (dblclick)="restoreToDefaultWidth()">
2✔
164
            </thy-resize-handle>
2✔
165
        </div>
2✔
166
        <div *ngIf="thyCollapsible" class="sidebar-collapse-line"></div>
2✔
167
        <div
2✔
168
            *ngIf="thyCollapsible && thyTrigger !== null"
2✔
169
            class="sidebar-collapse"
170
            [ngClass]="{ 'collapse-visible': collapseVisible, 'collapse-hidden': collapseHidden }"
4✔
171
            (click)="toggleCollapse($event)"
4✔
172
            [thyTooltip]="!thyTrigger && collapseTip">
173
            <ng-template [ngTemplateOutlet]="thyTrigger || defaultTrigger"></ng-template>
174
            <ng-template #defaultTrigger>
5✔
175
                <thy-icon class="sidebar-collapse-icon" [thyIconName]="this.thyCollapsed ? 'indent' : 'outdent'"></thy-icon>
5✔
176
            </ng-template>
5✔
177
        </div>
178
    `,
179
    hostDirectives: [
4✔
180
        {
4✔
181
            directive: ThySidebarDirective,
182
            inputs: ['thyTheme', 'thyDirection', 'thyWidth', 'thyIsolated', 'thyDivided', 'thyHasBorderLeft', 'thyHasBorderRight']
183
        }
4✔
184
    ],
185
    standalone: true,
186
    imports: [NgTemplateOutlet, NgIf, ThyResizeHandle, ThyResizableDirective, ThyIcon, ThyTooltipDirective, NgClass, NgStyle]
5✔
187
})
5✔
188
export class ThySidebar implements OnInit, OnDestroy {
5✔
189
    sidebarDirective = inject(ThySidebarDirective);
190

191
    @HostBinding('style.width.px') get sidebarWidth() {
2✔
192
        if (this.thyCollapsible && this.thyCollapsed) {
193
            return this.thyCollapsedWidth;
194
        } else {
1!
UNCOV
195
            return this.sidebarDirective.thyLayoutSidebarWidth;
×
196
        }
197
    }
1!
198

1✔
199
    @HostListener('mouseenter', ['$event'])
200
    mouseenter($event: MouseEvent) {
201
        this.resizeHandleHover($event, 'enter');
30✔
202
    }
203

1✔
204
    @HostListener('mouseleave', ['$event'])
205
    mouseleave($event: MouseEvent) {
206
        this.resizeHandleHover($event, 'leave');
207
    }
1✔
208

209
    /**
210
     * 宽度是否可以拖拽
211
     * @default false
212
     */
213
    @Input() @InputBoolean() thyDraggable: boolean = false;
214

215
    /**
216
     * 拖拽的最大宽度
217
     */
218
    @Input() @InputNumber() thyDragMaxWidth: number;
219

220
    /**
221
     * 拖拽的最小宽度
222
     */
223
    @Input() @InputNumber() thyDragMinWidth: number;
224

225
    /**
1✔
226
     * 展示收起的触发器自定义模板,默认显示展开收起的圆形图标,设置为 null 表示不展示触发元素,手动控制展开收起状态
227
     * @type null | undefined | TemplateRef<any>
228
     * @default undefined
229
     */
1✔
230
    @Input() thyTrigger: null | undefined | TemplateRef<any> = undefined;
231

232
    /**
233
     * 收起状态改变后的事件
1✔
234
     */
235
    @Output()
236
    thyCollapsedChange = new EventEmitter<boolean>();
237

1✔
238
    /**
239
     * 拖拽宽度的修改事件
240
     */
241
    @Output()
242
    thyDragWidthChange = new EventEmitter<number>();
1✔
243

244
    /**
245
     * 开启收起/展开功能
246
     * @default false
247
     */
1✔
248
    @Input() @InputBoolean() set thyCollapsible(collapsible: boolean) {
249
        this.collapsible = collapsible;
250
        if (this.collapsible) {
251
            this.subscribeHotkeyEvent();
1✔
252
        } else {
253
            this.hotkeySubscription?.unsubscribe();
254
        }
255
    }
256

257
    get thyCollapsible() {
258
        return this.collapsible;
259
    }
260

261
    /**
262
     * 是否是收起
263
     * @default false
264
     */
265
    @Input() @InputBoolean() set thyCollapsed(value: boolean) {
266
        this.isCollapsed = value;
267
    }
268

269
    get thyCollapsed() {
270
        return this.isCollapsed;
271
    }
272

273
    /**
274
     * 收起后的宽度
275
     */
276
    @Input() @InputNumber() thyCollapsedWidth = SIDEBAR_COLLAPSED_WIDTH;
277

278
    /**
279
     * 默认宽度,双击后可恢复到此宽度,默认是 240px,传入 lg 大小时宽度是300px
280
     */
281
    @Input() thyDefaultWidth: string | number;
282

283
    @HostBinding('class.sidebar-collapse-show')
284
    get collapseVisibility() {
285
        return this.thyCollapsed;
286
    }
287

288
    @HostBinding('class.remove-transition')
289
    get removeTransition() {
290
        return this.isRemoveTransition;
291
    }
292

293
    collapseTip: string;
294

295
    collapsible: boolean;
296

297
    isCollapsed = false;
298

299
    originWidth: number = SIDEBAR_DEFAULT_WIDTH;
300

301
    collapseVisible: boolean;
302

303
    collapseHidden: boolean;
304

305
    isRemoveTransition: boolean;
306

307
    isResizable: boolean;
308

309
    get dragMinWidth() {
310
        return this.thyDragMinWidth || this.thyCollapsedWidth;
311
    }
312

313
    private hotkeySubscription: Subscription;
314

315
    constructor(public elementRef: ElementRef, private hotkeyDispatcher: ThyHotkeyDispatcher) {}
316

317
    ngOnInit() {
318
        this.updateCollapseTip();
319
    }
320

321
    private subscribeHotkeyEvent() {
322
        this.hotkeySubscription = this.hotkeyDispatcher.keydown(['Control+/', 'Meta+/']).subscribe(() => {
323
            this.toggleCollapse();
324
        });
325
    }
326

327
    private updateCollapseTip() {
328
        this.collapseTip = this.thyCollapsed ? '展开' : '收起';
329
        this.collapseTip = this.collapseTip + (isMacPlatform() ? `(⌘ + /)` : `(Ctrl + /)`);
330
    }
331

332
    resizeHandler({ width }: ThyResizeEvent) {
333
        if (width === this.sidebarDirective.thyLayoutSidebarWidth) {
334
            return;
335
        }
336
        if (this.thyCollapsible && width < this.thyCollapsedWidth) {
337
            return;
338
        }
339
        if (this.thyCollapsible && width === this.thyCollapsedWidth) {
340
            this.thyCollapsed = true;
341
            setTimeout(() => this.updateCollapseTip(), 200);
342
            this.thyCollapsedChange.emit(this.isCollapsed);
343
            this.sidebarDirective.thyLayoutSidebarWidth = this.originWidth;
344
            this.collapseVisible = false;
345
            return;
346
        }
347
        this.sidebarDirective.thyLayoutSidebarWidth = width;
348
        this.thyDragWidthChange.emit(width);
349
    }
350

351
    resizeStart() {
352
        this.originWidth = this.sidebarDirective.thyLayoutSidebarWidth;
353
        this.collapseHidden = true;
354
        this.isRemoveTransition = true;
355
    }
356

357
    resizeEnd() {
358
        this.collapseHidden = false;
359
        this.isRemoveTransition = false;
360
    }
361

362
    resizeHandleHover(event: MouseEvent, type: 'enter' | 'leave') {
363
        this.collapseVisible = type === 'enter' && !this.thyCollapsed ? true : false;
364
    }
365

366
    toggleCollapse(event?: MouseEvent) {
367
        this.thyCollapsed = !this.thyCollapsed;
368
        setTimeout(() => this.updateCollapseTip(), 200);
369
        this.thyCollapsedChange.emit(this.isCollapsed);
370
    }
371

372
    public toggleResizable(event: MouseEvent, type: 'enter' | 'leave') {
373
        this.isResizable = type === 'enter' ? true : false;
374
    }
375

376
    restoreToDefaultWidth() {
377
        if (this.thyDefaultWidth === 'lg') {
378
            this.thyDefaultWidth = LG_WIDTH;
379
        }
380
        this.sidebarDirective.thyLayoutSidebarWidth = (this.thyDefaultWidth as number) || SIDEBAR_DEFAULT_WIDTH;
381
        this.thyDragWidthChange.emit(this.sidebarDirective.thyLayoutSidebarWidth);
382
    }
383

384
    ngOnDestroy(): void {
385
        this.hotkeySubscription?.unsubscribe();
386
    }
387
}
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

© 2025 Coveralls, Inc