-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
26 changed files
with
1,354 additions
and
989 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/** | ||
* @license | ||
* Copyright Google LLC All Rights Reserved. | ||
* | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://angular.io/license | ||
*/ | ||
|
||
import {Attribute, Directive, ElementRef, InjectionToken, Input} from '@angular/core'; | ||
|
||
let nextUniqueId = 0; | ||
|
||
/** | ||
* Injection token that can be used to reference instances of `MatError`. It serves as | ||
* alternative token to the actual `MatError` class which could cause unnecessary | ||
* retention of the class and its directive metadata. | ||
*/ | ||
export const TRI_ERROR = new InjectionToken<TriError>('TriError'); | ||
|
||
/** Single error message to be shown underneath the form-field. */ | ||
@Directive({ | ||
selector: 'tri-error, [triError]', | ||
host: { | ||
'class': 'tri-form-field-error tri-form-field-bottom-align', | ||
'aria-atomic': 'true', | ||
'[id]': 'id', | ||
}, | ||
providers: [{provide: TRI_ERROR, useExisting: TriError}], | ||
standalone: true, | ||
}) | ||
export class TriError { | ||
@Input() id: string = `tri-error-${nextUniqueId++}`; | ||
|
||
constructor(@Attribute('aria-live') ariaLive: string, elementRef: ElementRef) { | ||
// If no aria-live value is set add 'polite' as a default. This is preferred over setting | ||
// role='alert' so that screen readers do not interrupt the current task to read this aloud. | ||
if (!ariaLive) { | ||
elementRef.nativeElement.setAttribute('aria-live', 'polite'); | ||
} | ||
} | ||
} |
152 changes: 152 additions & 0 deletions
152
libs/triangle/form-field/src/directives/floating-label.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
/** | ||
* @license | ||
* Copyright Google LLC All Rights Reserved. | ||
* | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://angular.io/license | ||
*/ | ||
|
||
import { | ||
Directive, | ||
ElementRef, | ||
inject, | ||
Input, | ||
NgZone, | ||
OnDestroy, | ||
InjectionToken, | ||
} from '@angular/core'; | ||
import {SharedResizeObserver} from '@angular/cdk/observers/private'; | ||
import {Subscription} from 'rxjs'; | ||
|
||
/** An interface that the parent form-field should implement to receive resize events. */ | ||
export interface FloatingLabelParent { | ||
_handleLabelResized(): void; | ||
} | ||
|
||
/** An injion token for the parent form-field. */ | ||
export const FLOATING_LABEL_PARENT = new InjectionToken<FloatingLabelParent>('FloatingLabelParent'); | ||
|
||
/** | ||
* Internal directive that maintains a MDC floating label. This directive does not | ||
* use the `MDCFloatingLabelFoundation` class, as it is not worth the size cost of | ||
* including it just to measure the label width and toggle some classes. | ||
* | ||
* The use of a directive allows us to conditionally render a floating label in the | ||
* template without having to manually manage instantiation and destruction of the | ||
* floating label component based on. | ||
* | ||
* The component is responsible for setting up the floating label styles, measuring label | ||
* width for the outline notch, and providing inputs that can be used to toggle the | ||
* label's floating or required state. | ||
*/ | ||
@Directive({ | ||
selector: 'label[triFormFieldFloatingLabel]', | ||
host: { | ||
'class': 'tri-floating-label mat-mdc-floating-label', | ||
'[class.tri-floating-label--float-above]': 'floating', | ||
}, | ||
standalone: true, | ||
}) | ||
export class TriFormFieldFloatingLabel implements OnDestroy { | ||
/** Whether the label is floating. */ | ||
@Input() | ||
get floating() { | ||
return this._floating; | ||
} | ||
set floating(value: boolean) { | ||
this._floating = value; | ||
if (this.monitorResize) { | ||
this._handleResize(); | ||
} | ||
} | ||
private _floating = false; | ||
|
||
/** Whether to monitor for resize events on the floating label. */ | ||
@Input() | ||
get monitorResize() { | ||
return this._monitorResize; | ||
} | ||
set monitorResize(value: boolean) { | ||
this._monitorResize = value; | ||
if (this._monitorResize) { | ||
this._subscribeToResize(); | ||
} else { | ||
this._resizeSubscription.unsubscribe(); | ||
} | ||
} | ||
private _monitorResize = false; | ||
|
||
/** The shared ResizeObserver. */ | ||
private _resizeObserver = inject(SharedResizeObserver); | ||
|
||
/** The Angular zone. */ | ||
private _ngZone = inject(NgZone); | ||
|
||
/** The parent form-field. */ | ||
private _parent = inject(FLOATING_LABEL_PARENT); | ||
|
||
/** The current resize event subscription. */ | ||
private _resizeSubscription = new Subscription(); | ||
|
||
constructor(private _elementRef: ElementRef<HTMLElement>) {} | ||
|
||
ngOnDestroy() { | ||
this._resizeSubscription.unsubscribe(); | ||
} | ||
|
||
/** Gets the width of the label. Used for the outline notch. */ | ||
getWidth(): number { | ||
return estimateScrollWidth(this._elementRef.nativeElement); | ||
} | ||
|
||
/** Gets the HTML element for the floating label. */ | ||
get element(): HTMLElement { | ||
return this._elementRef.nativeElement; | ||
} | ||
|
||
/** Handles resize events from the ResizeObserver. */ | ||
private _handleResize() { | ||
// In the case where the label grows in size, the following sequence of events occurs: | ||
// 1. The label grows by 1px triggering the ResizeObserver | ||
// 2. The notch is expanded to accommodate the entire label | ||
// 3. The label expands to its full width, triggering the ResizeObserver again | ||
// | ||
// This is expected, but If we allow this to all happen within the same macro task it causes an | ||
// error: `ResizeObserver loop limit exceeded`. Therefore we push the notch resize out until | ||
// the next macro task. | ||
setTimeout(() => this._parent._handleLabelResized()); | ||
} | ||
|
||
/** Subscribes to resize events. */ | ||
private _subscribeToResize() { | ||
this._resizeSubscription.unsubscribe(); | ||
this._ngZone.runOutsideAngular(() => { | ||
this._resizeSubscription = this._resizeObserver | ||
.observe(this._elementRef.nativeElement, {box: 'border-box'}) | ||
.subscribe(() => this._handleResize()); | ||
}); | ||
} | ||
} | ||
|
||
/** | ||
* Estimates the scroll width of an element. | ||
* via https://github.com/material-components/material-components-web/blob/c0a11ef0d000a098fd0c372be8f12d6a99302855/packages/mdc-dom/ponyfill.ts | ||
*/ | ||
function estimateScrollWidth(element: HTMLElement): number { | ||
// Check the offsetParent. If the element inherits display: none from any | ||
// parent, the offsetParent property will be null (see | ||
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent). | ||
// This check ensures we only clone the node when necessary. | ||
const htmlEl = element as HTMLElement; | ||
if (htmlEl.offsetParent !== null) { | ||
return htmlEl.scrollWidth; | ||
} | ||
|
||
const clone = htmlEl.cloneNode(true) as HTMLElement; | ||
clone.style.setProperty('position', 'absolute'); | ||
clone.style.setProperty('transform', 'translate(-9999px, -9999px)'); | ||
document.documentElement.appendChild(clone); | ||
const scrollWidth = clone.scrollWidth; | ||
clone.remove(); | ||
return scrollWidth; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/** | ||
* @license | ||
* Copyright Google LLC All Rights Reserved. | ||
* | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://angular.io/license | ||
*/ | ||
|
||
import {Directive, Input} from '@angular/core'; | ||
|
||
let nextUniqueId = 0; | ||
|
||
/** Hint text to be shown underneath the form field control. */ | ||
@Directive({ | ||
selector: 'tri-hint', | ||
host: { | ||
'class': 'tri-form-field-hint tri-form-field-bottom-align', | ||
'[class.tri-form-field-hint-end]': 'align === "end"', | ||
'[id]': 'id', | ||
// Remove align attribute to prevent it from interfering with layout. | ||
'[attr.align]': 'null', | ||
}, | ||
standalone: true, | ||
}) | ||
export class TriHint { | ||
/** Whether to align the hint label at the start or end of the line. */ | ||
@Input() align: 'start' | 'end' = 'start'; | ||
|
||
/** Unique ID for the hint. Used for the aria-describedby on the form field control. */ | ||
@Input() id: string = `tri-hint-${nextUniqueId++}`; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
/** | ||
* @license | ||
* Copyright Google LLC All Rights Reserved. | ||
* | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://angular.io/license | ||
*/ | ||
|
||
import {Directive} from '@angular/core'; | ||
|
||
/** The floating label for a `mat-form-field`. */ | ||
@Directive({ | ||
selector: 'tri-label', | ||
standalone: true, | ||
}) | ||
export class TriLabel {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
/** | ||
* @license | ||
* Copyright Google LLC All Rights Reserved. | ||
* | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://angular.io/license | ||
*/ | ||
|
||
import {Directive, ElementRef, NgZone, OnDestroy} from '@angular/core'; | ||
|
||
/** Class added when the line ripple is active. */ | ||
const ACTIVATE_CLASS = 'tri-line-ripple--active'; | ||
|
||
/** Class added when the line ripple is being deactivated. */ | ||
const DEACTIVATING_CLASS = 'tri-line-ripple--deactivating'; | ||
|
||
/** | ||
* Internal directive that creates an instance of the MDC line-ripple component. Using a | ||
* directive allows us to conditionally render a line-ripple in the template without having | ||
* to manually create and destroy the `MDCLineRipple` component whenever the condition changes. | ||
* | ||
* The directive sets up the styles for the line-ripple and provides an API for activating | ||
* and deactivating the line-ripple. | ||
*/ | ||
@Directive({ | ||
selector: 'div[triFormFieldLineRipple]', | ||
host: { | ||
'class': 'tri-line-ripple', | ||
}, | ||
standalone: true, | ||
}) | ||
export class TriFormFieldLineRipple implements OnDestroy { | ||
constructor( | ||
private _elementRef: ElementRef<HTMLElement>, | ||
ngZone: NgZone, | ||
) { | ||
ngZone.runOutsideAngular(() => { | ||
_elementRef.nativeElement.addEventListener('transitionend', this._handleTransitionEnd); | ||
}); | ||
} | ||
|
||
activate() { | ||
const classList = this._elementRef.nativeElement.classList; | ||
classList.remove(DEACTIVATING_CLASS); | ||
classList.add(ACTIVATE_CLASS); | ||
} | ||
|
||
deactivate() { | ||
this._elementRef.nativeElement.classList.add(DEACTIVATING_CLASS); | ||
} | ||
|
||
private _handleTransitionEnd = (event: TransitionEvent) => { | ||
const classList = this._elementRef.nativeElement.classList; | ||
const isDeactivating = classList.contains(DEACTIVATING_CLASS); | ||
|
||
if (event.propertyName === 'opacity' && isDeactivating) { | ||
classList.remove(ACTIVATE_CLASS, DEACTIVATING_CLASS); | ||
} | ||
}; | ||
|
||
ngOnDestroy() { | ||
this._elementRef.nativeElement.removeEventListener('transitionend', this._handleTransitionEnd); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
<div class="tri-notched-outline__leading"></div> | ||
<div class="tri-notched-outline__notch" #notch> | ||
<ng-content></ng-content> | ||
</div> | ||
<div class="tri-notched-outline__trailing"></div> |
Oops, something went wrong.