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

atinc / ngx-tethys / d9ae709b-3c27-4b69-b125-b8b80b54f90b

pending completion
d9ae709b-3c27-4b69-b125-b8b80b54f90b

Pull #2757

circleci

mengshuicmq
fix: fix code review
Pull Request #2757: feat(color-picker): color-picker support disabled (#INFR-8645)

98 of 6315 branches covered (1.55%)

Branch coverage included in aggregate %.

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

2392 of 13661 relevant lines covered (17.51%)

83.12 hits per line

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

4.94
/src/dialog/dialog-container.component.ts
1
import { reqAnimFrame, ThyAbstractOverlayContainer, ThyClickPositioner } from 'ngx-tethys/core';
2
import { Observable } from 'rxjs';
3
import { filter } from 'rxjs/operators';
4

5
import { AnimationEvent } from '@angular/animations';
6
import { FocusTrap, FocusTrapFactory } from '@angular/cdk/a11y';
7
import { CdkPortalOutlet, PortalModule } from '@angular/cdk/portal';
8
import { DOCUMENT } from '@angular/common';
9
import {
10
    ChangeDetectionStrategy,
11
    ChangeDetectorRef,
12
    Component,
13
    ElementRef,
14
    EventEmitter,
1✔
15
    HostBinding,
16
    Inject,
×
17
    NgZone,
×
18
    OnDestroy,
19
    ViewChild
×
20
} from '@angular/core';
21

22
import { thyDialogAnimations } from './dialog-animations';
×
23
import { ThyDialogConfig } from './dialog.config';
24
import { dialogAbstractOverlayOptions } from './dialog.options';
25

26
/**
27
 * @private
28
 */
29
@Component({
30
    selector: 'thy-dialog-container',
×
31
    template: ` <ng-template cdkPortalOutlet></ng-template> `,
32
    // Using OnPush for dialogs caused some sync issues, e.g. custom ngModel can't to detect changes
33
    // Disabled until we can track them down.
34
    changeDetection: ChangeDetectionStrategy.Default,
35
    animations: [thyDialogAnimations.dialogContainer],
36
    host: {
×
37
        class: 'thy-dialog-container',
×
38
        tabindex: '-1',
×
39
        'aria-modal': 'true',
40
        '[attr.id]': 'id',
41
        '[attr.role]': 'config.role',
42
        '[attr.aria-labelledby]': 'config.ariaLabel ? null : ariaLabelledBy',
43
        '[attr.aria-label]': 'config.ariaLabel',
×
44
        '[attr.aria-describedby]': 'config.ariaDescribedBy || null',
×
45
        '[@dialogContainer]': 'animationState',
46
        '(@dialogContainer.start)': 'onAnimationStart($event)',
47
        '(@dialogContainer.done)': 'onAnimationDone($event)'
×
48
    },
49
    standalone: true,
50
    imports: [PortalModule]
51
})
52
export class ThyDialogContainerComponent extends ThyAbstractOverlayContainer implements OnDestroy {
53
    animationOpeningDone: Observable<AnimationEvent>;
×
54
    animationClosingDone: Observable<AnimationEvent>;
×
55

56
    @ViewChild(CdkPortalOutlet, { static: true })
57
    portalOutlet: CdkPortalOutlet;
58

59
    @HostBinding(`attr.id`)
×
60
    id: string;
61

×
62
    /** State of the dialog animation. */
×
63
    animationState: 'void' | 'enter' | 'exit' = 'void';
64

×
65
    /** Emits when an animation state changes. */
×
66
    animationStateChanged = new EventEmitter<AnimationEvent>();
67

68
    /** ID of the element that should be considered as the dialog's label. */
69
    ariaLabelledBy: string | null = null;
×
70

×
71
    /** Element that was focused before the dialog was opened. Save this to restore upon close. */
×
72
    private elementFocusedBeforeDialogWasOpened: HTMLElement | null = null;
×
73

×
74
    /** The class that traps and manages focus within the dialog. */
75
    private focusTrap: FocusTrap;
76

×
77
    private savePreviouslyFocusedElement() {
78
        if (this.document) {
79
            this.elementFocusedBeforeDialogWasOpened = this.document.activeElement as HTMLElement;
80

×
81
            // Note that there is no focus method when rendering on the server.
×
82
            if (this.elementRef.nativeElement.focus) {
×
83
                // Note: this is being run outside of the Angular zone because `element.focus()` doesn't require
×
84
                // running change detection.
×
85
                this.ngZone.runOutsideAngular(() =>
×
86
                    // Move focus onto the dialog immediately in order to prevent the user from accidentally
×
87
                    // opening multiple dialogs at the same time. Needs to be async, because the element
88
                    // may not be focusable immediately.
×
89

90
                    // Note: `element.focus()` causes re-layout and this may lead to frame drop on slower devices.
×
91
                    // https://gist.github.com/paulirish/5d52fb081b3570c81e3a#setting-focus
92
                    // `setTimeout` is a macrotask and macrotasks are executed within the current rendering frame.
×
93
                    // Animation tasks are executed within the next rendering frame.
94
                    reqAnimFrame(() => this.elementRef.nativeElement.focus())
×
95
                );
×
96
            }
×
97
        }
98
    }
×
99

×
100
    /** Moves the focus inside the focus trap. */
101
    private trapFocus() {
102
        const element = this.elementRef.nativeElement;
103

×
104
        if (!this.focusTrap) {
×
105
            this.focusTrap = this.focusTrapFactory.create(element);
106
        }
107

108
        // If we were to attempt to focus immediately, then the content of the dialog would not yet be
×
109
        // ready in instances where change detection has to run first. To deal with this, we simply
×
110
        // wait for the microtask queue to be empty.
111
        if (this.config.autoFocus) {
×
112
            this.focusTrap.focusInitialElementWhenReady();
×
113
        } else {
114
            const activeElement = this.document.activeElement;
×
115

116
            // Otherwise ensure that focus is on the dialog container. It's possible that a different
117
            // component tried to move focus while the open animation was running. See:
118
            // https://github.com/angular/components/issues/16215. Note that we only want to do this
×
119
            // if the focus isn't inside the dialog already, because it's possible that the consumer
120
            // turned off `autoFocus` in order to move focus themselves.
121
            if (activeElement !== element && !element.contains(activeElement)) {
×
122
                element.focus();
123
            }
1✔
124
        }
125
    }
126

127
    private restoreFocus() {
128
        const toFocus = this.elementFocusedBeforeDialogWasOpened;
129

130
        // We need the extra check, because IE can set the `activeElement` to null in some cases.
131
        if (this.config.restoreFocus && toFocus && typeof toFocus.focus === 'function') {
132
            toFocus.focus();
1✔
133
        }
134

135
        if (this.focusTrap) {
136
            this.focusTrap.destroy();
137
        }
1✔
138
    }
139

140
    private setTransformOrigin() {
141
        this.clickPositioner.runTaskUseLastPosition(lastPosition => {
142
            if (lastPosition) {
143
                const containerElement: HTMLElement = this.elementRef.nativeElement;
144
                const transformOrigin = `${lastPosition.x - containerElement.offsetLeft}px ${
145
                    lastPosition.y - containerElement.offsetTop
146
                }px 0px`;
147
                containerElement.style['transform-origin'] = transformOrigin;
148
                // 手动修改动画状态为从 void 到 enter, 开启动画
149
            }
150
            this.animationState = 'enter';
151
        });
152
    }
153

154
    constructor(
155
        private elementRef: ElementRef,
156
        @Inject(DOCUMENT) private document: any,
157
        public config: ThyDialogConfig,
158
        changeDetectorRef: ChangeDetectorRef,
159
        private clickPositioner: ThyClickPositioner,
160
        private focusTrapFactory: FocusTrapFactory,
161
        private ngZone: NgZone
162
    ) {
163
        super(dialogAbstractOverlayOptions, changeDetectorRef);
164
        this.animationOpeningDone = this.animationStateChanged.pipe(
165
            filter((event: AnimationEvent) => {
166
                return event.phaseName === 'done' && event.toState === 'void';
167
            })
168
        );
169
        this.animationClosingDone = this.animationStateChanged.pipe(
170
            filter((event: AnimationEvent) => {
171
                return event.phaseName === 'done' && event.toState === 'exit';
172
            })
173
        );
174
    }
175

176
    beforeAttachPortal(): void {
177
        this.setTransformOrigin();
178
        this.savePreviouslyFocusedElement();
179
    }
180

181
    /** Callback, invoked whenever an animation on the host completes. */
182
    onAnimationDone(event: AnimationEvent) {
183
        if (event.toState === 'void') {
184
            this.trapFocus();
185
        } else if (event.toState === 'exit') {
186
            this.restoreFocus();
187
        }
188
        this.animationStateChanged.emit(event);
189
    }
190

191
    /** Callback, invoked when an animation on the host starts. */
192
    onAnimationStart(event: AnimationEvent) {
193
        this.animationStateChanged.emit(event);
194
    }
195

196
    ngOnDestroy() {
197
        super.destroy();
198
    }
199
}
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