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

atinc / ngx-tethys / 4fab39f9-ac62-4400-9e25-e980fb84c2f6

30 Aug 2023 06:45AM UTC coverage: 90.118% (+0.002%) from 90.116%
4fab39f9-ac62-4400-9e25-e980fb84c2f6

push

circleci

minlovehua
feat(layout): sidebar support thyDragMinWidth

5130 of 6351 branches covered (0.0%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

12962 of 13725 relevant lines covered (94.44%)

975.0 hits per line

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

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

134✔
26
const LG_WIDTH = 300;
27
const SIDEBAR_DEFAULT_WIDTH = 240;
28

29
export type ThySidebarTheme = 'white' | 'light' | 'dark';
2✔
30

31
export type ThySidebarDirection = 'left' | 'right';
32

2✔
33
/**
34
 * 布局侧边栏组件
35
 * @name thy-sidebar
31✔
36
 * @order 20
1✔
37
 */
38
@Component({
31✔
39
    selector: 'thy-sidebar',
40
    preserveWhitespaces: false,
41
    template: `
88✔
42
        <ng-content></ng-content>
43
        <div
44
            thyResizable
29✔
45
            class="sidebar-drag"
46
            *ngIf="thyDraggable"
47
            thyBounds="window"
29✔
48
            [thyMaxWidth]="thyDragMaxWidth"
49
            [thyMinWidth]="dragMinWidth"
50
            (thyResize)="resizeHandler($event)"
29✔
51
            (thyResizeStart)="resizeStart()"
52
            (thyResizeEnd)="resizeEnd()"
53
            [style.display]="!isResizable ? 'contents' : null">
37✔
54
            <thy-resize-handle
37✔
55
                *ngIf="!thyCollapsed"
9✔
56
                [thyDirection]="thyDirection === 'right' ? 'left' : 'right'"
57
                class="sidebar-resize-handle"
58
                thyLine="true"
28✔
59
                (mouseenter)="toggleResizable($event, 'enter')"
60
                (mouseleave)="toggleResizable($event, 'leave')"
61
                (dblclick)="restoreToDefaultWidth()">
62
            </thy-resize-handle>
433✔
63
        </div>
64
        <div *ngIf="thyCollapsible" class="sidebar-collapse-line"></div>
65
        <div
39✔
66
            *ngIf="thyCollapsible && thyTrigger !== null"
67
            class="sidebar-collapse"
68
            [ngClass]="{ 'collapse-visible': collapseVisible, 'collapse-hidden': collapseHidden }"
271✔
69
            (click)="toggleCollapse($event)"
70
            [thyTooltip]="!thyTrigger && collapseTip">
71
            <ng-template [ngTemplateOutlet]="thyTrigger || defaultTrigger"></ng-template>
140✔
72
            <ng-template #defaultTrigger>
73
                <thy-icon class="sidebar-collapse-icon" [thyIconName]="this.thyCollapsed ? 'indent' : 'outdent'"></thy-icon>
74
            </ng-template>
140✔
75
        </div>
76
    `,
77
    host: {
30✔
78
        class: 'thy-layout-sidebar',
30✔
79
        '[class.thy-layout-sidebar-right]': 'thyDirection === "right"',
30✔
80
        '[class.thy-layout-sidebar--clear-border-right]': 'thyDirection === "left" && !isDivided',
30✔
81
        '[class.thy-layout-sidebar--clear-border-left]': 'thyDirection === "right" && !isDivided',
30✔
82
        '[class.sidebar-theme-light]': 'thyTheme === "light"',
30✔
83
        '[class.sidebar-theme-dark]': 'thyTheme === "dark"',
30✔
84
        '[class.thy-layout-sidebar-isolated]': 'sidebarIsolated'
30✔
85
    },
30✔
86
    standalone: true,
30✔
87
    imports: [
30✔
88
        NgTemplateOutlet,
30✔
89
        NgIf,
30✔
90
        ThyResizeHandleComponent,
91
        ThyResizableDirective,
92
        ThyIconComponent,
33✔
93
        ThyTooltipDirective,
33!
94
        NgClass,
33✔
95
        NgStyle
96
    ]
33✔
97
})
2✔
98
export class ThySidebarComponent implements OnInit, OnDestroy {
99
    thyLayoutSidebarWidth: number;
33✔
100

101
    isDivided = true;
102

9✔
103
    sidebarIsolated = false;
2✔
104

105
    @HostBinding('style.width.px') get sidebarWidth() {
106
        if (this.thyCollapsible && this.thyCollapsed) {
107
            return this.thyCollapsedWidth;
40✔
108
        } else {
40!
109
            return this.thyLayoutSidebarWidth;
110
        }
111
    }
7✔
112

1✔
113
    @HostListener('mouseenter', ['$event'])
114
    mouseenter($event: MouseEvent) {
6!
115
        this.resizeHandleHover($event, 'enter');
×
116
    }
117

6✔
118
    @HostListener('mouseleave', ['$event'])
2✔
119
    mouseleave($event: MouseEvent) {
2✔
120
        this.resizeHandleHover($event, 'leave');
2✔
121
    }
2✔
122

2✔
123
    /**
2✔
124
     * 宽度,默认是 240px,传入 `lg` 大小时宽度是300px
125
     * @default 240px
4✔
126
     */
4✔
127
    @Input('thyWidth')
128
    set thyWidth(value: any) {
129
        if (value === 'lg') {
5✔
130
            value = LG_WIDTH;
5✔
131
        }
5✔
132
        this.thyLayoutSidebarWidth = value || SIDEBAR_DEFAULT_WIDTH;
133
    }
134

4✔
135
    /**
4✔
136
     * sidebar 位置,默认在左侧
137
     */
138
    @Input() thyDirection: ThySidebarDirection = 'left';
4✔
139

140
    /**
141
     * sidebar 是否有分割线。当`thyDirection`值为`left`时,控制右侧是否有分割线;当`thyDirection`值为`right`时,控制左侧是否有分割线。
5✔
142
     * @default true
5✔
143
     */
5✔
144
    @Input('thyDivided')
145
    set thyDivided(value: string) {
146
        this.isDivided = coerceBooleanProperty(value);
2✔
147
    }
148

149
    /**
1!
150
     * 右侧是否有边框,已废弃,请使用 thyDivided
×
151
     * @deprecated please use thyDivided
152
     * @default true
1!
153
     */
1✔
154
    @Input('thyHasBorderRight')
155
    set thyHasBorderRight(value: string) {
156
        this.thyDivided = value;
30✔
157
    }
158

1✔
159
    /**
160
     * 左侧是否有边框,已废弃,请使用 thyDivided
161
     * @deprecated please use thyDivided
162
     * @default true
163
     */
1✔
164
    @Input('thyHasBorderLeft')
165
    set thyHasBorderLeft(value: string) {
166
        this.thyDivided = value;
167
    }
168

169
    /**
170
     * 是否和右侧 /左侧隔离,当为 true 时距右侧 /左侧会有 margin,同时边框会去掉
171
     * @default false
172
     */
173
    @Input('thyIsolated')
174
    set thyIsolated(value: string) {
175
        this.sidebarIsolated = coerceBooleanProperty(value);
176
    }
177

178
    /**
179
     * 宽度是否可以拖拽
180
     * @default false
181
     */
182
    @Input() @InputBoolean() thyDraggable: boolean = false;
183

184
    /**
185
     * 拖拽的最大宽度
186
     */
187
    @Input() @InputNumber() thyDragMaxWidth: number;
188

1✔
189
    /**
190
     * 拖拽的最小宽度
191
     */
192
    @Input() @InputNumber() thyDragMinWidth: number;
1✔
193

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

1✔
201
    /**
202
     * 收起状态改变后的事件
203
     */
204
    @Output()
205
    thyCollapsedChange = new EventEmitter<boolean>();
1✔
206

207
    /**
208
     * 拖拽宽度的修改事件
209
     */
210
    @Output()
1✔
211
    thyDragWidthChange = new EventEmitter<number>();
212

213
    /**
214
     * 开启收起/展开功能
1✔
215
     * @default false
216
     */
217
    @Input() @InputBoolean() set thyCollapsible(collapsible: boolean) {
218
        this.collapsible = collapsible;
219
        if (this.collapsible) {
220
            this.subscribeHotkeyEvent();
221
        } else {
222
            this.hotkeySubscription?.unsubscribe();
223
        }
224
    }
225

226
    get thyCollapsible() {
227
        return this.collapsible;
228
    }
229

230
    /**
231
     * 是否是收起
232
     * @default false
233
     */
234
    @Input() @InputBoolean() set thyCollapsed(value: boolean) {
235
        this.isCollapsed = value;
236
    }
237

238
    get thyCollapsed() {
239
        return this.isCollapsed;
240
    }
241

242
    /**
243
     * 收起后的宽度、拖拽的最小宽度
244
     */
245
    @Input() @InputNumber() thyCollapsedWidth = 20;
246

247
    /**
248
     * 主题
249
     * @type white | light | dark
250
     * @default white
251
     */
252
    @Input() thyTheme: ThySidebarTheme;
253

254
    /**
255
     * 默认宽度,双击后可恢复到此宽度,默认是 240px,传入 lg 大小时宽度是300px
256
     */
257
    @Input() thyDefaultWidth: string | number;
258

259
    @HostBinding('class.sidebar-collapse-show')
260
    get collapseVisibility() {
261
        return this.thyCollapsed;
262
    }
263

264
    @HostBinding('class.remove-transition')
265
    get removeTransition() {
266
        return this.isRemoveTransition;
267
    }
268

269
    collapseTip: string;
270

271
    collapsible: boolean;
272

273
    isCollapsed = false;
274

275
    originWidth: number = SIDEBAR_DEFAULT_WIDTH;
276

277
    collapseVisible: boolean;
278

279
    collapseHidden: boolean;
280

281
    isRemoveTransition: boolean;
282

283
    isResizable: boolean;
284

285
    dragMinWidth: number;
286

287
    private hotkeySubscription: Subscription;
288

289
    constructor(
290
        @Optional() @Host() private thyLayoutComponent: ThyLayoutComponent,
291
        public elementRef: ElementRef,
292
        private hotkeyDispatcher: ThyHotkeyDispatcher
293
    ) {}
294

295
    ngOnInit() {
296
        this.dragMinWidth = this.thyDragMinWidth || this.thyCollapsedWidth;
297

298
        if (this.thyLayoutComponent) {
299
            this.thyLayoutComponent.hasSidebar = true;
300
        }
301
        if (this.thyDirection === 'right') {
302
            this.thyLayoutComponent.isSidebarRight = true;
303
        }
304
        this.updateCollapseTip();
305
    }
306

307
    private subscribeHotkeyEvent() {
308
        this.hotkeySubscription = this.hotkeyDispatcher.keydown(['Control+/', 'Meta+/']).subscribe(() => {
309
            this.toggleCollapse();
310
        });
311
    }
312

313
    private updateCollapseTip() {
314
        this.collapseTip = this.thyCollapsed ? '展开' : '收起';
315
        this.collapseTip = this.collapseTip + (isMacPlatform() ? `(⌘ + /)` : `(Ctrl + /)`);
316
    }
317

318
    resizeHandler({ width }: ThyResizeEvent) {
319
        if (width === this.thyLayoutSidebarWidth) {
320
            return;
321
        }
322
        if (this.thyCollapsible && width < this.thyCollapsedWidth) {
323
            return;
324
        }
325
        if (this.thyCollapsible && width === this.thyCollapsedWidth) {
326
            this.thyCollapsed = true;
327
            setTimeout(() => this.updateCollapseTip(), 200);
328
            this.thyCollapsedChange.emit(this.isCollapsed);
329
            this.thyLayoutSidebarWidth = this.originWidth;
330
            this.collapseVisible = false;
331
            return;
332
        }
333
        this.thyLayoutSidebarWidth = width;
334
        this.thyDragWidthChange.emit(width);
335
    }
336

337
    resizeStart() {
338
        this.originWidth = this.thyLayoutSidebarWidth;
339
        this.collapseHidden = true;
340
        this.isRemoveTransition = true;
341
    }
342

343
    resizeEnd() {
344
        this.collapseHidden = false;
345
        this.isRemoveTransition = false;
346
    }
347

348
    resizeHandleHover(event: MouseEvent, type: 'enter' | 'leave') {
349
        this.collapseVisible = type === 'enter' && !this.thyCollapsed ? true : false;
350
    }
351

352
    toggleCollapse(event?: MouseEvent) {
353
        this.thyCollapsed = !this.thyCollapsed;
354
        setTimeout(() => this.updateCollapseTip(), 200);
355
        this.thyCollapsedChange.emit(this.isCollapsed);
356
    }
357

358
    public toggleResizable(event: MouseEvent, type: 'enter' | 'leave') {
359
        this.isResizable = type === 'enter' ? true : false;
360
    }
361

362
    restoreToDefaultWidth() {
363
        if (this.thyDefaultWidth === 'lg') {
364
            this.thyDefaultWidth = LG_WIDTH;
365
        }
366
        this.thyLayoutSidebarWidth = (this.thyDefaultWidth as number) || SIDEBAR_DEFAULT_WIDTH;
367
        this.thyDragWidthChange.emit(this.thyLayoutSidebarWidth);
368
    }
369

370
    ngOnDestroy(): void {
371
        this.hotkeySubscription?.unsubscribe();
372
    }
373
}
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