carbon-components-angular
Version:
Next generation components
1,237 lines (1,222 loc) • 86.1 kB
JavaScript
import * as i0 from '@angular/core';
import { Directive, Input, HostBinding, TemplateRef, Component, ViewChild, ContentChild, EventEmitter, Output, NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import * as i1 from '@angular/common';
import { CommonModule } from '@angular/common';
import * as i2 from 'carbon-components-angular/icon';
import { IconModule } from 'carbon-components-angular/icon';
import { BaseIconButton, ButtonModule } from 'carbon-components-angular/button';
import * as i3 from 'carbon-components-angular/tooltip';
import { TooltipModule } from 'carbon-components-angular/tooltip';
/**
* A directive for applying styling to an input element.
*
* Example:
*
* ```html
* <input cdsText/>
* ```
*
* See the [vanilla carbon docs](http://www.carbondesignsystem.com/components/text-input/code) for more detail.
*/
class TextInput {
constructor() {
/**
* @deprecated since v5 - Use `cdsLayer` directive instead
* `light` or `dark` input theme
*/
this.theme = "dark";
/**
* Input field render size
*/
this.size = "md";
this.inputClass = true;
this.invalid = false;
this.warn = false;
this.skeleton = false;
}
/**
* @todo - remove `cds--text-input--${size}` classes in v12
*/
get isSizeSm() {
return this.size === "sm";
}
get isSizeMd() {
return this.size === "md";
}
get isSizelg() {
return this.size === "lg";
}
// Size
get sizeSm() {
return this.size === "sm";
}
get sizeMd() {
return this.size === "md";
}
get sizelg() {
return this.size === "lg";
}
get isLightTheme() {
return this.theme === "light";
}
get getInvalidAttribute() {
return this.invalid ? true : undefined;
}
}
TextInput.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: TextInput, deps: [], target: i0.ɵɵFactoryTarget.Directive });
TextInput.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.3.0", type: TextInput, selector: "[cdsText], [ibmText]", inputs: { theme: "theme", size: "size", invalid: "invalid", warn: "warn", skeleton: "skeleton" }, host: { properties: { "class.cds--text-input": "this.inputClass", "class.cds--text-input--sm": "this.isSizeSm", "class.cds--text-input--md": "this.isSizeMd", "class.cds--text-input--lg": "this.isSizelg", "class.cds--layout--size-sm": "this.sizeSm", "class.cds--layout--size-md": "this.sizeMd", "class.cds--layout--size-lg": "this.sizelg", "class.cds--text-input--invalid": "this.invalid", "class.cds--text-input--warning": "this.warn", "class.cds--skeleton": "this.skeleton", "class.cds--text-input--light": "this.isLightTheme", "attr.data-invalid": "this.getInvalidAttribute" } }, ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: TextInput, decorators: [{
type: Directive,
args: [{
selector: "[cdsText], [ibmText]"
}]
}], propDecorators: { theme: [{
type: Input
}], size: [{
type: Input
}], inputClass: [{
type: HostBinding,
args: ["class.cds--text-input"]
}], isSizeSm: [{
type: HostBinding,
args: ["class.cds--text-input--sm"]
}], isSizeMd: [{
type: HostBinding,
args: ["class.cds--text-input--md"]
}], isSizelg: [{
type: HostBinding,
args: ["class.cds--text-input--lg"]
}], sizeSm: [{
type: HostBinding,
args: ["class.cds--layout--size-sm"]
}], sizeMd: [{
type: HostBinding,
args: ["class.cds--layout--size-md"]
}], sizelg: [{
type: HostBinding,
args: ["class.cds--layout--size-lg"]
}], invalid: [{
type: HostBinding,
args: ["class.cds--text-input--invalid"]
}, {
type: Input
}], warn: [{
type: HostBinding,
args: ["class.cds--text-input--warning"]
}, {
type: Input
}], skeleton: [{
type: HostBinding,
args: ["class.cds--skeleton"]
}, {
type: Input
}], isLightTheme: [{
type: HostBinding,
args: ["class.cds--text-input--light"]
}], getInvalidAttribute: [{
type: HostBinding,
args: ["attr.data-invalid"]
}] } });
/**
* A directive for applying styling to a textarea element.
*
* Example:
*
* ```html
* <textarea cdsTextArea></textarea>
* ```
*
* See the [vanilla carbon docs](http://www.carbondesignsystem.com/components/text-input/code) for more detail.
*/
class TextArea {
constructor() {
/**
* @deprecated since v5 - Use `cdsLayer` directive instead
* `light` or `dark` input theme
*/
this.theme = "dark";
this.baseClass = true;
this.invalid = false;
/**
* Set to `true` to put the textarea in a warning state.
* Mirrors the `warn` prop of the React `TextArea` component.
*/
this.warn = false;
this.skeleton = false;
}
get isLightTheme() {
return this.theme === "light";
}
get getInvalidAttr() {
return this.invalid ? true : undefined;
}
}
TextArea.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: TextArea, deps: [], target: i0.ɵɵFactoryTarget.Directive });
TextArea.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.3.0", type: TextArea, selector: "[cdsTextArea], [ibmTextArea]", inputs: { theme: "theme", invalid: "invalid", warn: "warn", skeleton: "skeleton" }, host: { properties: { "class.cds--text-area": "this.baseClass", "class.cds--text-area--invalid": "this.invalid", "class.cds--text-area--warn": "this.warn", "class.cds--skeleton": "this.skeleton", "class.cds--text-area--light": "this.isLightTheme", "attr.data-invalid": "this.getInvalidAttr" } }, ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: TextArea, decorators: [{
type: Directive,
args: [{
selector: "[cdsTextArea], [ibmTextArea]"
}]
}], propDecorators: { theme: [{
type: Input
}], baseClass: [{
type: HostBinding,
args: ["class.cds--text-area"]
}], invalid: [{
type: HostBinding,
args: ["class.cds--text-area--invalid"]
}, {
type: Input
}], warn: [{
type: HostBinding,
args: ["class.cds--text-area--warn"]
}, {
type: Input
}], skeleton: [{
type: HostBinding,
args: ["class.cds--skeleton"]
}, {
type: Input
}], isLightTheme: [{
type: HostBinding,
args: ["class.cds--text-area--light"]
}], getInvalidAttr: [{
type: HostBinding,
args: ["attr.data-invalid"]
}] } });
class PasswordInput {
constructor(elementRef, renderer) {
this.elementRef = elementRef;
this.renderer = renderer;
this.passwordInputClass = true;
this.inputClass = true;
this.invalid = false;
this.warn = false;
this.skeleton = false;
/**
* @deprecated since v5 - Use `cdsLayer` directive instead
* `light` or `dark` input theme
*/
this.theme = "dark";
/**
* Input field render size
*/
this.size = "md";
this._type = "password";
}
set type(type) {
if (type) {
this._type = type;
if (this.elementRef) {
this.renderer.setAttribute(this.elementRef.nativeElement, "type", this._type);
}
}
}
/**
* @todo - remove `cds--text-input--${size}` classes in v12
*/
get isSizeSm() {
return this.size === "sm";
}
get isSizeMd() {
return this.size === "md";
}
get isSizelg() {
return this.size === "lg";
}
// Size
get sizeSm() {
return this.size === "sm";
}
get sizeMd() {
return this.size === "md";
}
get sizelg() {
return this.size === "lg";
}
get isLightTheme() {
return this.theme === "light";
}
get getInvalidAttribute() {
return this.invalid ? true : undefined;
}
ngAfterViewInit() {
this.renderer.setAttribute(this.elementRef.nativeElement, "type", this._type);
}
}
PasswordInput.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: PasswordInput, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive });
PasswordInput.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.3.0", type: PasswordInput, selector: "[cdsPassword], [ibmPassword]", inputs: { type: "type", invalid: "invalid", warn: "warn", skeleton: "skeleton", theme: "theme", size: "size" }, host: { properties: { "class.cds--password-input": "this.passwordInputClass", "class.cds--text-input--sm": "this.isSizeSm", "class.cds--text-input--md": "this.isSizeMd", "class.cds--text-input--lg": "this.isSizelg", "class.cds--layout--size-sm": "this.sizeSm", "class.cds--layout--size-md": "this.sizeMd", "class.cds--layout--size-lg": "this.sizelg", "class.cds--text-input--light": "this.isLightTheme", "class.cds--text-input": "this.inputClass", "class.cds--text-input--invalid": "this.invalid", "class.cds--text-input--warning": "this.warn", "class.cds--skeleton": "this.skeleton", "attr.data-invalid": "this.getInvalidAttribute" } }, ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: PasswordInput, decorators: [{
type: Directive,
args: [{
selector: "[cdsPassword], [ibmPassword]"
}]
}], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.Renderer2 }]; }, propDecorators: { type: [{
type: Input
}], passwordInputClass: [{
type: HostBinding,
args: ["class.cds--password-input"]
}], isSizeSm: [{
type: HostBinding,
args: ["class.cds--text-input--sm"]
}], isSizeMd: [{
type: HostBinding,
args: ["class.cds--text-input--md"]
}], isSizelg: [{
type: HostBinding,
args: ["class.cds--text-input--lg"]
}], sizeSm: [{
type: HostBinding,
args: ["class.cds--layout--size-sm"]
}], sizeMd: [{
type: HostBinding,
args: ["class.cds--layout--size-md"]
}], sizelg: [{
type: HostBinding,
args: ["class.cds--layout--size-lg"]
}], isLightTheme: [{
type: HostBinding,
args: ["class.cds--text-input--light"]
}], inputClass: [{
type: HostBinding,
args: ["class.cds--text-input"]
}], invalid: [{
type: HostBinding,
args: ["class.cds--text-input--invalid"]
}, {
type: Input
}], warn: [{
type: HostBinding,
args: ["class.cds--text-input--warning"]
}, {
type: Input
}], skeleton: [{
type: HostBinding,
args: ["class.cds--skeleton"]
}, {
type: Input
}], theme: [{
type: Input
}], size: [{
type: Input
}], getInvalidAttribute: [{
type: HostBinding,
args: ["attr.data-invalid"]
}] } });
/**
* Get started with importing the module:
*
* ```typescript
* import { InputModule } from 'carbon-components-angular';
* ```
*
* ```html
* <cds-textarea-label>
* Label
* <textarea cdsTextArea class="textarea-field">
* </cds-textarea-label>
* ```
*
* [See demo](../../?path=/story/components-input-text-area--basic)
*/
class TextareaLabelComponent {
/**
* Creates an instance of Label.
*/
constructor(changeDetectorRef) {
this.changeDetectorRef = changeDetectorRef;
/**
* The id of the input item associated with the `Label`. This value is also used to associate the `Label` with
* its input counterpart through the 'for' attribute.
*/
this.labelInputID = "ibm-textarea-" + TextareaLabelComponent.labelCounter;
/**
* Set to `true` for a disabled label.
*/
this.disabled = false;
/**
* Set to `true` for a loading label.
*/
this.skeleton = false;
/**
* Set to `true` for an invalid label component.
*/
this.invalid = false;
/**
* Set to `true` to show a warning (contents set by warningText)
*/
this.warn = false;
/**
* Experimental: enable fluid state
*/
this.fluid = false;
/**
* Set to `true` to hide the label visually, but keep accessible to
* screen readers.
*/
this.hideLabel = false;
/**
* Set to `true` (`maxCount` must be set) to displays a live character/word
* counter alongside the label.
*/
this.enableCounter = false;
/**
* Determines whether the counter counts characters or words.
* When `"word"` and `maxCount` is set, input is clamped to `maxCount` words
* on each change. Excess words are trimmed from the end of the value.
*/
this.counterMode = "character";
// Tracks current character / word count for the counter display.
this.textCount = 0;
this.labelClass = true;
// Cached reference to the textarea element, set once in ngAfterViewInit.
this._textareaElement = null;
// Cached listener so it can be removed precisely (avoids anonymous-function leak)
this._inputListener = null;
}
get isReadonly() {
return this.wrapper?.nativeElement.querySelector("textarea")?.readOnly ?? false;
}
get fluidClass() {
return this.fluid && !this.skeleton;
}
get fluidSkeletonClass() {
return this.fluid && this.skeleton;
}
/**
* Sets the id on the input item associated with the `Label` and attaches the
* counter listener when `enableCounter` is already `true` on first render.
*/
ngAfterViewInit() {
if (this.wrapper) {
// Prioritize setting id to `textarea` over div
const inputElement = this.wrapper.nativeElement.querySelector("textarea");
if (inputElement) {
// avoid overriding ids already set by the user — reuse it instead
if (inputElement.id) {
this.labelInputID = inputElement.id;
this.changeDetectorRef.detectChanges();
}
inputElement.setAttribute("id", this.labelInputID);
this._textareaElement = inputElement;
this._syncMaxLength();
if (this.enableCounter) {
this.textCount = this._countValue(inputElement.value || "");
this._attachCounterListener();
}
return;
}
const divElement = this.wrapper.nativeElement.querySelector("div");
if (divElement) {
if (divElement.id) {
this.labelInputID = divElement.id;
this.changeDetectorRef.detectChanges();
}
divElement.setAttribute("id", this.labelInputID);
}
}
}
/**
* Attach/remove listener and seed `textCount` from the textarea's current value.
* @param changes
*/
ngOnChanges(changes) {
if (changes.enableCounter && !changes.enableCounter.firstChange) {
if (changes.enableCounter.currentValue) {
if (this._textareaElement) {
this.textCount = this._countValue(this._textareaElement.value || "");
this._attachCounterListener();
this.changeDetectorRef.detectChanges();
}
}
else {
this._detachCounterListener();
}
}
if ((changes.maxCount || changes.counterMode) &&
!(changes.maxCount?.firstChange && changes.counterMode?.firstChange)) {
this._syncMaxLength();
}
}
ngOnDestroy() {
this._detachCounterListener();
}
isTemplate(value) {
return value instanceof TemplateRef;
}
/**
* Keeps the textarea's `maxlength` attribute in sync with `maxCount`. This is only set
* when counterMode is set to `character`. When counterMode is set to `word`, we enforce limit via JS.
* If `maxCount` is unset or the mode is `"word"`, any previously applied
* `maxlength` is removed so the textarea is unrestricted by the attribute.
*/
_syncMaxLength() {
if (!this._textareaElement) {
return;
}
if (this.counterMode === "character" && this.maxCount != null) {
this._textareaElement.setAttribute("maxlength", String(this.maxCount));
}
else {
this._textareaElement.removeAttribute("maxlength");
}
}
/**
* Attaches the input event listener, ensuring it is never added twice.
*/
_attachCounterListener() {
this._detachCounterListener();
if (!this._textareaElement) {
return;
}
this._inputListener = (e) => {
const el = e.target;
// Word-mode enforcement: clamp value to maxCount words on each input so
// the textarea never holds more words than allowed. Character mode relies
// on the native `maxlength` attribute set by the developer.
if (this.counterMode === "word" && this.maxCount != null) {
const clamped = this._truncateToWordLimit(el.value || "", this.maxCount);
if (clamped !== el.value) {
el.value = clamped;
}
}
this.textCount = this._countValue(el.value || "");
};
this._textareaElement.addEventListener("input", this._inputListener);
}
/**
* Truncates `value` so it contains at most `limit` Unicode words.
* Whitespace between and around words is preserved up to the last allowed word;
* any trailing content (partial word or space) beyond the limit is dropped.
*/
_truncateToWordLimit(value, limit) {
let wordsSeen = 0;
// Walk through the string capturing word boundaries
const wordPattern = /\p{L}+/gu;
let match;
let cutIndex = value.length;
while ((match = wordPattern.exec(value)) !== null) {
wordsSeen++;
if (wordsSeen === limit) {
// Allow the string to continue up to (but not past) the end of this word
cutIndex = match.index + match[0].length;
break;
}
}
return wordsSeen < limit ? value : value.slice(0, cutIndex);
}
/**
* Removes the input event listener and clears the cached reference.
*/
_detachCounterListener() {
if (this._inputListener && this._textareaElement) {
this._textareaElement.removeEventListener("input", this._inputListener);
this._inputListener = null;
}
}
_countValue(value) {
if (this.counterMode === "word") {
return value.match(/\p{L}+/gu)?.length || 0;
}
return value.length;
}
}
/**
* Used to build the id of the input item associated with the `Label`.
*/
TextareaLabelComponent.labelCounter = 0;
TextareaLabelComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: TextareaLabelComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
TextareaLabelComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: TextareaLabelComponent, selector: "cds-textarea-label, ibm-textarea-label", inputs: { labelInputID: "labelInputID", disabled: "disabled", skeleton: "skeleton", labelTemplate: "labelTemplate", textAreaTemplate: "textAreaTemplate", helperText: "helperText", invalidText: "invalidText", invalid: "invalid", warn: "warn", warnText: "warnText", ariaLabel: "ariaLabel", fluid: "fluid", hideLabel: "hideLabel", enableCounter: "enableCounter", maxCount: "maxCount", counterMode: "counterMode" }, host: { properties: { "class.cds--text-area__wrapper--readonly": "this.isReadonly", "class.cds--text-area--fluid": "this.fluidClass", "class.cds--text-area--fluid__skeleton": "this.fluidSkeletonClass", "class.cds--form-item": "this.labelClass" } }, queries: [{ propertyName: "textArea", first: true, predicate: TextArea, descendants: true }], viewQueries: [{ propertyName: "wrapper", first: true, predicate: ["wrapper"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
<ng-container *ngIf="skeleton">
<span class="cds--label cds--skeleton"></span>
<div class="cds--text-area cds--skeleton"></div>
</ng-container>
<ng-container *ngIf="!skeleton">
<div class="cds--text-area__label-wrapper">
<label
[for]="labelInputID"
[attr.aria-label]="ariaLabel"
class="cds--label"
[ngClass]="{
'cds--label--disabled': disabled,
'cds--visually-hidden': hideLabel
}">
<ng-template *ngIf="labelTemplate; else labelContent" [ngTemplateOutlet]="labelTemplate"></ng-template>
<ng-template #labelContent>
<ng-content></ng-content>
</ng-template>
</label>
<span
*ngIf="enableCounter && maxCount"
class="cds--label"
[ngClass]="{'cds--label--disabled': disabled}"
aria-hidden="true">
{{textCount}}/{{maxCount}}
</span>
</div>
<div
class="cds--text-area__wrapper"
[ngClass]="{
'cds--text-area__wrapper--warn': warn
}"
[attr.data-invalid]="(invalid ? true : null)"
#wrapper>
<svg
*ngIf="!fluid && invalid"
cdsIcon="warning--filled"
size="16"
class="cds--text-area__invalid-icon">
</svg>
<svg
*ngIf="!fluid && !invalid && warn"
cdsIcon="warning--alt--filled"
size="16"
class="cds--text-area__invalid-icon cds--text-area__invalid-icon--warning">
</svg>
<ng-template *ngIf="textAreaTemplate; else textAreaContent" [ngTemplateOutlet]="textAreaTemplate"></ng-template>
<ng-template #textAreaContent>
<ng-content select="[cdsTextArea],[ibmTextArea],textarea"></ng-content>
</ng-template>
<ng-container *ngIf="fluid">
<hr class="cds--text-area__divider" />
<div *ngIf="invalid" class="cds--form-requirement">
<ng-container *ngIf="!isTemplate(invalidText)">{{invalidText}}</ng-container>
<ng-template *ngIf="isTemplate(invalidText)" [ngTemplateOutlet]="invalidText"></ng-template>
<svg
cdsIcon="warning--filled"
size="16"
class="cds--text-area__invalid-icon">
</svg>
</div>
<div *ngIf="!invalid && warn" class="cds--form-requirement">
<ng-container *ngIf="!isTemplate(warnText)">{{warnText}}</ng-container>
<ng-template *ngIf="isTemplate(warnText)" [ngTemplateOutlet]="warnText"></ng-template>
<svg
cdsIcon="warning--alt--filled"
size="16"
class="cds--text-area__invalid-icon cds--text-area__invalid-icon--warning">
</svg>
</div>
</ng-container>
</div>
<ng-container *ngIf="!fluid">
<div
*ngIf="helperText && !invalid && !warn"
class="cds--form__helper-text"
[ngClass]="{'cds--form__helper-text--disabled': disabled}">
<ng-container *ngIf="!isTemplate(helperText)">{{helperText}}</ng-container>
<ng-template *ngIf="isTemplate(helperText)" [ngTemplateOutlet]="helperText"></ng-template>
</div>
<div *ngIf="invalid" class="cds--form-requirement">
<ng-container *ngIf="!isTemplate(invalidText)">{{invalidText}}</ng-container>
<ng-template *ngIf="isTemplate(invalidText)" [ngTemplateOutlet]="invalidText"></ng-template>
</div>
<div *ngIf="!invalid && warn" class="cds--form-requirement">
<ng-container *ngIf="!isTemplate(warnText)">{{warnText}}</ng-container>
<ng-template *ngIf="isTemplate(warnText)" [ngTemplateOutlet]="warnText"></ng-template>
</div>
</ng-container>
</ng-container>
`, isInline: true, dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2.IconDirective, selector: "[cdsIcon], [ibmIcon]", inputs: ["ibmIcon", "cdsIcon", "size", "title", "ariaLabel", "ariaLabelledBy", "ariaHidden", "isFocusable"] }] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: TextareaLabelComponent, decorators: [{
type: Component,
args: [{
selector: "cds-textarea-label, ibm-textarea-label",
template: `
<ng-container *ngIf="skeleton">
<span class="cds--label cds--skeleton"></span>
<div class="cds--text-area cds--skeleton"></div>
</ng-container>
<ng-container *ngIf="!skeleton">
<div class="cds--text-area__label-wrapper">
<label
[for]="labelInputID"
[attr.aria-label]="ariaLabel"
class="cds--label"
[ngClass]="{
'cds--label--disabled': disabled,
'cds--visually-hidden': hideLabel
}">
<ng-template *ngIf="labelTemplate; else labelContent" [ngTemplateOutlet]="labelTemplate"></ng-template>
<ng-template #labelContent>
<ng-content></ng-content>
</ng-template>
</label>
<span
*ngIf="enableCounter && maxCount"
class="cds--label"
[ngClass]="{'cds--label--disabled': disabled}"
aria-hidden="true">
{{textCount}}/{{maxCount}}
</span>
</div>
<div
class="cds--text-area__wrapper"
[ngClass]="{
'cds--text-area__wrapper--warn': warn
}"
[attr.data-invalid]="(invalid ? true : null)"
#wrapper>
<svg
*ngIf="!fluid && invalid"
cdsIcon="warning--filled"
size="16"
class="cds--text-area__invalid-icon">
</svg>
<svg
*ngIf="!fluid && !invalid && warn"
cdsIcon="warning--alt--filled"
size="16"
class="cds--text-area__invalid-icon cds--text-area__invalid-icon--warning">
</svg>
<ng-template *ngIf="textAreaTemplate; else textAreaContent" [ngTemplateOutlet]="textAreaTemplate"></ng-template>
<ng-template #textAreaContent>
<ng-content select="[cdsTextArea],[ibmTextArea],textarea"></ng-content>
</ng-template>
<ng-container *ngIf="fluid">
<hr class="cds--text-area__divider" />
<div *ngIf="invalid" class="cds--form-requirement">
<ng-container *ngIf="!isTemplate(invalidText)">{{invalidText}}</ng-container>
<ng-template *ngIf="isTemplate(invalidText)" [ngTemplateOutlet]="invalidText"></ng-template>
<svg
cdsIcon="warning--filled"
size="16"
class="cds--text-area__invalid-icon">
</svg>
</div>
<div *ngIf="!invalid && warn" class="cds--form-requirement">
<ng-container *ngIf="!isTemplate(warnText)">{{warnText}}</ng-container>
<ng-template *ngIf="isTemplate(warnText)" [ngTemplateOutlet]="warnText"></ng-template>
<svg
cdsIcon="warning--alt--filled"
size="16"
class="cds--text-area__invalid-icon cds--text-area__invalid-icon--warning">
</svg>
</div>
</ng-container>
</div>
<ng-container *ngIf="!fluid">
<div
*ngIf="helperText && !invalid && !warn"
class="cds--form__helper-text"
[ngClass]="{'cds--form__helper-text--disabled': disabled}">
<ng-container *ngIf="!isTemplate(helperText)">{{helperText}}</ng-container>
<ng-template *ngIf="isTemplate(helperText)" [ngTemplateOutlet]="helperText"></ng-template>
</div>
<div *ngIf="invalid" class="cds--form-requirement">
<ng-container *ngIf="!isTemplate(invalidText)">{{invalidText}}</ng-container>
<ng-template *ngIf="isTemplate(invalidText)" [ngTemplateOutlet]="invalidText"></ng-template>
</div>
<div *ngIf="!invalid && warn" class="cds--form-requirement">
<ng-container *ngIf="!isTemplate(warnText)">{{warnText}}</ng-container>
<ng-template *ngIf="isTemplate(warnText)" [ngTemplateOutlet]="warnText"></ng-template>
</div>
</ng-container>
</ng-container>
`
}]
}], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }]; }, propDecorators: { isReadonly: [{
type: HostBinding,
args: ["class.cds--text-area__wrapper--readonly"]
}], fluidClass: [{
type: HostBinding,
args: ["class.cds--text-area--fluid"]
}], fluidSkeletonClass: [{
type: HostBinding,
args: ["class.cds--text-area--fluid__skeleton"]
}], labelInputID: [{
type: Input
}], disabled: [{
type: Input
}], skeleton: [{
type: Input
}], labelTemplate: [{
type: Input
}], textAreaTemplate: [{
type: Input
}], helperText: [{
type: Input
}], invalidText: [{
type: Input
}], invalid: [{
type: Input
}], warn: [{
type: Input
}], warnText: [{
type: Input
}], ariaLabel: [{
type: Input
}], fluid: [{
type: Input
}], hideLabel: [{
type: Input
}], enableCounter: [{
type: Input
}], maxCount: [{
type: Input
}], counterMode: [{
type: Input
}], wrapper: [{
type: ViewChild,
args: ["wrapper", { static: false }]
}], textArea: [{
type: ContentChild,
args: [TextArea, { static: false }]
}], labelClass: [{
type: HostBinding,
args: ["class.cds--form-item"]
}] } });
/**
* Get started with importing the module:
*
* ```typescript
* import { InputModule } from 'carbon-components-angular';
* ```
*
* ```html
* <cds-text-label>
* Label
* <input cdsText type="text" class="input-field">
* </cds-text-label>
* ```
*
* [See demo](../../?path=/story/components-input--basic)
*/
class TextInputLabelComponent {
/**
* Creates an instance of Label.
*/
constructor(changeDetectorRef) {
this.changeDetectorRef = changeDetectorRef;
/**
* The id of the input item associated with the `Label`. This value is also used to associate the `Label` with
* its input counterpart through the 'for' attribute.
*/
this.labelInputID = "ibm-text-input-" + TextInputLabelComponent.labelCounter++;
/**
* Set to `true` for a disabled label.
*/
this.disabled = false;
/**
* Set to `true` for a loading label.
*/
this.skeleton = false;
/**
* Set to `true` for an invalid label component.
*/
this.invalid = false;
/**
* Set to `true` to show a warning (contents set by warningText)
*/
this.warn = false;
/**
* Experimental: enable fluid state
*/
this.fluid = false;
/**
* Set to `true` to hide the label visually, but keep accessible to
* screen readers.
*/
this.hideLabel = false;
/**
* Set to `true` to render the label and field side-by-side instead of stacked.
*/
this.inline = false;
/**
* The render size for the `TextInput`. Used to compute the INLINE label size
* variant class (`cds--label--inline--{size}`).
*/
this.size = "md";
/**
* Set to `true` (`maxCount` must be set) to displays a live character
* counter alongside the label.
*/
this.enableCounter = false;
// Tracks current character count for the counter display.
this.textCount = 0;
this.labelClass = true;
this.textInputWrapper = true;
// Cached reference to the input element, set once in ngAfterViewInit.
this._inputElement = null;
// Cached listener so it can be removed precisely (avoids anonymous-function leak).
this._inputListener = null;
}
get isInlineWrapper() {
return this.inline;
}
get isReadonly() {
return this.wrapper?.nativeElement.querySelector("input")?.readOnly ?? false;
}
get fluidClass() {
return this.fluid && !this.skeleton;
}
get fluidSkeletonClass() {
return this.fluid && this.skeleton;
}
/**
* Sets the id on the input item associated with the `Label` and attaches the
* counter listener when `enableCounter` is already `true` on first render.
*/
ngAfterViewInit() {
if (this.wrapper) {
// Prioritize setting id to `input` over div
const inputElement = this.wrapper.nativeElement.querySelector("input");
if (inputElement) {
// avoid overriding ids already set by the user, reuse it instead
if (inputElement.id) {
this.labelInputID = inputElement.id;
this.changeDetectorRef.detectChanges();
}
inputElement.setAttribute("id", this.labelInputID);
this._inputElement = inputElement;
if (this.enableCounter) {
this.textCount = inputElement.value?.length || 0;
this._attachCounterListener();
}
return;
}
const divElement = this.wrapper.nativeElement.querySelector("div");
if (divElement) {
if (divElement.id) {
this.labelInputID = divElement.id;
this.changeDetectorRef.detectChanges();
}
divElement.setAttribute("id", this.labelInputID);
}
}
}
/**
* Attach/remove listener and seed `textCount` from the textarea's current value.
* @param changes
*/
ngOnChanges(changes) {
if (changes.enableCounter && !changes.enableCounter.firstChange) {
if (changes.enableCounter.currentValue) {
if (this._inputElement) {
this.textCount = this._inputElement.value?.length || 0;
this._attachCounterListener();
this.changeDetectorRef.detectChanges();
}
}
else {
this._detachCounterListener();
}
}
}
ngAfterContentInit() {
this.changeDetectorRef.detectChanges();
}
ngOnDestroy() {
this._detachCounterListener();
}
isTemplate(value) {
return value instanceof TemplateRef;
}
/**
* Attaches the input event listener, ensuring it is never added twice.
*/
_attachCounterListener() {
this._detachCounterListener();
if (!this._inputElement) {
return;
}
this._inputListener = (e) => {
this.textCount = e.target.value?.length || 0;
this.changeDetectorRef.detectChanges();
};
this._inputElement.addEventListener("input", this._inputListener);
}
/**
* Removes the input event listener and clears the cached reference.
*/
_detachCounterListener() {
if (this._inputListener && this._inputElement) {
this._inputElement.removeEventListener("input", this._inputListener);
this._inputListener = null;
}
}
}
/**
* Used to build the id of the input item associated with the `Label`.
*/
TextInputLabelComponent.labelCounter = 0;
TextInputLabelComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: TextInputLabelComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
TextInputLabelComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: TextInputLabelComponent, selector: "cds-text-label, ibm-text-label", inputs: { labelInputID: "labelInputID", disabled: "disabled", skeleton: "skeleton", labelTemplate: "labelTemplate", textInputTemplate: "textInputTemplate", helperText: "helperText", invalidText: "invalidText", invalid: "invalid", warn: "warn", warnText: "warnText", ariaLabel: "ariaLabel", fluid: "fluid", hideLabel: "hideLabel", inline: "inline", size: "size", enableCounter: "enableCounter", maxCount: "maxCount" }, host: { properties: { "class.cds--text-input-wrapper--inline": "this.isInlineWrapper", "class.cds--text-input-wrapper--readonly": "this.isReadonly", "class.cds--text-input--fluid": "this.fluidClass", "class.cds--text-input--fluid__skeleton": "this.fluidSkeletonClass", "class.cds--form-item": "this.labelClass", "class.cds--text-input-wrapper": "this.textInputWrapper" } }, viewQueries: [{ propertyName: "wrapper", first: true, predicate: ["wrapper"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
<ng-container *ngIf="skeleton">
<span class="cds--label cds--skeleton"></span>
<div class="cds--text-input cds--skeleton"></div>
</ng-container>
<ng-container *ngIf="!skeleton">
<!-- non-inline: label-wrapper above field; inline: label+validation side-by-side -->
<ng-container *ngIf="!inline; else inlineHeader">
<div class="cds--text-input__label-wrapper">
<label
[for]="labelInputID"
[attr.aria-label]="ariaLabel"
class="cds--label"
[ngClass]="{
'cds--label--disabled': disabled,
'cds--visually-hidden': hideLabel
}">
<ng-template *ngIf="labelTemplate; else labelContent" [ngTemplateOutlet]="labelTemplate"></ng-template>
<ng-template #labelContent>
<ng-content></ng-content>
</ng-template>
</label>
<span
*ngIf="enableCounter && maxCount"
class="cds--label"
[ngClass]="{'cds--label--disabled': disabled}"
aria-hidden="true">
{{textCount}}/{{maxCount}}
</span>
</div>
</ng-container>
<ng-template #inlineHeader>
<div class="cds--text-input__label-helper-wrapper">
<div class="cds--text-input__label-wrapper">
<label
[for]="labelInputID"
[attr.aria-label]="ariaLabel"
class="cds--label"
[ngClass]="{
'cds--label--disabled': disabled,
'cds--visually-hidden': hideLabel,
'cds--label--inline': true,
'cds--label--inline--sm': size === 'sm',
'cds--label--inline--md': size === 'md',
'cds--label--inline--lg': size === 'lg'
}">
<ng-template *ngIf="labelTemplate" [ngTemplateOutlet]="labelTemplate"></ng-template>
</label>
</div>
<ng-container *ngIf="!fluid">
<ng-container [ngTemplateOutlet]="validationOrHelper"></ng-container>
</ng-container>
</div>
</ng-template>
<div
class="cds--text-input__field-outer-wrapper"
[ngClass]="{'cds--text-input__field-outer-wrapper--inline': inline}">
<div
class="cds--text-input__field-wrapper"
[ngClass]="{
'cds--text-input__field-wrapper--warning': warn
}"
[attr.data-invalid]="(invalid ? true : null)"
#wrapper>
<svg
*ngIf="invalid && !warn"
cdsIcon="warning--filled"
size="16"
class="cds--text-input__invalid-icon">
</svg>
<svg
*ngIf="!invalid && warn"
cdsIcon="warning--alt--filled"
size="16"
class="cds--text-input__invalid-icon cds--text-input__invalid-icon--warning">
</svg>
<ng-template *ngIf="textInputTemplate; else textInputContent" [ngTemplateOutlet]="textInputTemplate"></ng-template>
<ng-template #textInputContent>
<ng-content select="[cdsText],[ibmText],input[type=text],div"></ng-content>
</ng-template>
<ng-container *ngIf="fluid">
<hr class="cds--text-input__divider" />
<div *ngIf="invalid" class="cds--form-requirement">
<ng-container *ngIf="!isTemplate(invalidText)">{{invalidText}}</ng-container>
<ng-template *ngIf="isTemplate(invalidText)" [ngTemplateOutlet]="invalidText"></ng-template>
</div>
<div *ngIf="!invalid && warn" class="cds--form-requirement">
<ng-container *ngIf="!isTemplate(warnText)">{{warnText}}</ng-container>
<ng-template *ngIf="isTemplate(warnText)" [ngTemplateOutlet]="warnText"></ng-template>
</div>
</ng-container>
</div>
<ng-container *ngIf="!fluid && !inline">
<ng-container [ngTemplateOutlet]="validationOrHelper"></ng-container>
</ng-container>
</div>
</ng-container>
<ng-template #validationOrHelper>
<div
*ngIf="helperText && !invalid && !warn"
class="cds--form__helper-text"
[ngClass]="{'cds--form__helper-text--disabled': disabled, 'cds--form__helper-text--inline': inline}">
<ng-container *ngIf="!isTemplate(helperText)">{{helperText}}</ng-container>
<ng-template *ngIf="isTemplate(helperText)" [ngTemplateOutlet]="helperText"></ng-template>
</div>
<div *ngIf="invalid" class="cds--form-requirement">
<ng-container *ngIf="!isTemplate(invalidText)">{{invalidText}}</ng-container>
<ng-template *ngIf="isTemplate(invalidText)" [ngTemplateOutlet]="invalidText"></ng-template>
</div>
<div *ngIf="!invalid && warn" class="cds--form-requirement">
<ng-container *ngIf="!isTemplate(warnText)">{{warnText}}</ng-container>
<ng-template *ngIf="isTemplate(warnText)" [ngTemplateOutlet]="warnText"></ng-template>
</div>
</ng-template>
`, isInline: true, dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2.IconDirective, selector: "[cdsIcon], [ibmIcon]", inputs: ["ibmIcon", "cdsIcon", "size", "title", "ariaLabel", "ariaLabelledBy", "ariaHidden", "isFocusable"] }] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: TextInputLabelComponent, decorators: [{
type: Component,
args: [{
selector: "cds-text-label, ibm-text-label",
template: `
<ng-container *ngIf="skeleton">
<span class="cds--label cds--skeleton"></span>
<div class="cds--text-input cds--skeleton"></div>
</ng-container>
<ng-container *ngIf="!skeleton">
<!-- non-inline: label-wrapper above field; inline: label+validation side-by-side -->
<ng-container *ngIf="!inline; else inlineHeader">
<div class="cds--text-input__label-wrapper">
<label
[for]="labelInputID"
[attr.aria-label]="ariaLabel"
class="cds--label"
[ngClass]="{
'cds--label--disabled': disabled,
'cds--visually-hidden': hideLabel
}">
<ng-template *ngIf="labelTemplate; else labelContent" [ngTemplateOutlet]="labelTemplate"></ng-template>
<ng-template #labelContent>
<ng-content></ng-content>
</ng-template>
</label>
<span
*ngIf="enableCounter && maxCount"
class="cds--label"
[ngClass]="{'cds--label--disabled': disabled}"
aria-hidden="true">
{{textCount}}/{{maxCount}}
</span>
</div>
</ng-container>
<ng-template #inlineHeader>
<div class="cds--text-input__label-helper-wrapper">
<div class="cds--text-input__label-wrapper">
<label
[for]="labelInputID"
[attr.aria-label]="ariaLabel"
class="cds--label"
[ngClass]="{
'cds--label--disabled': disabled,
'cds--visually-hidden': hideLabel,
'cds--label--inline': true,
'cds--label--inline--sm': size === 'sm',
'cds--label--inline--md': size === 'md',
'cds--label--inline--lg': size === 'lg'
}">
<ng-template *ngIf="labelTemplate" [ngTemplateOutlet]="labelTemplate"></ng-template>
</label>
</div>
<ng-container *ngIf="!fluid">
<ng-container [ngTemplateOutlet]="validationOrHelper"></ng-container>
</ng-container>
</div>
</ng-template>
<div
class="cds--text-input__field-outer-wrapper"
[ngClass]="{'cds--text-input__field-outer-wrapper--inline': inline}">
<div
class="cds--text-input__field-wrapper"
[ngClass]="{
'cds--text-input__field-wrapper--warning': warn
}"
[attr.data-invalid]="(invalid ? true : null)"
#wrapper>
<svg
*ngIf="invalid && !warn"
cdsIcon="warning--filled"
size="16"
class="cds--text-input__invalid-icon">
</svg>
<svg
*ngIf="!invalid && warn"
cdsIcon="warning--alt--filled"
size="16"
class="cds--text-input__invalid-icon cds--text-input__invalid-icon--warning">
</svg>
<ng-template *ngIf="textInputTemplate; else textInputContent" [ngTemplateOutlet]="textInputTemplate"></ng-template>
<ng-template #textInputContent>
<ng-content select="[cdsText],[ibmText],input[type=text],div"></ng-content>
</ng-template>
<ng-container *ngIf="fluid">
<hr class="cds--text-input__divider" />
<div *ngIf="invalid" class="cds--form-requirement">
<ng-container *ngIf="!isTemplate(invalidText)">{{invalidText}}</ng-container>
<ng-template *ngIf="isTemplate(invalidText)" [ngTemplateOutlet]="invalidText"></ng-template>
</div>
<div *ngIf="!invalid && warn" class="cds--form-requirement">
<ng-container *ngIf="!isTemplate(warnText)">{{warnText}}</ng-container>
<ng-template *ngIf="isTemplate(warnText)" [ngTemplateOutlet]="warnText"></ng-template>
</div>
</ng-container>
</div>
<ng-container *ngIf="!fluid && !inline">
<ng-container [ngTemplateOutlet]="validationOrHelper"></ng-container>
</ng-container>
</div>
</ng-container>
<ng-template #validationOrHelper>
<div
*ngIf="helperText && !invalid && !warn"
class="cds--form__helper-text"
[ngClass]="{'cds--form__helper-text--disabled': disabled, 'cds--form__helper-text--inline': inline}">
<ng-container *ngIf="!isTemplate(helperText)">{{helperText}}</ng-container>
<ng-template *ngIf="isTemplate(helperText)" [ngTemplateOutlet]="helperText"></ng-template>
</div>
<div *ngIf="invalid" class="cds--form-requirement">
<ng-container *ngIf="!isTemplate(invalidText)">{{invalidText}}</ng-container>
<ng-template *ngIf="isTemplate(invalidText)" [ngTemplateOutlet]="invalidText"></ng-template>
</div>
<div *ngIf="!invalid && warn" class="cds--form-requirement">
<ng-container *ngIf="!isTemplate(warnText)">{{warnText}}</ng-container>
<ng-template *ngIf="isTemplate(warnText)" [ngTemplateOutlet]="warnText"></ng-template>
</div>
</ng-template>
`
}]
}], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }]; }, propDecorators: { isInlineWrapper: [{
type: HostBinding,
args: ["class.cds--text-input-wrapper--inline"]
}], isReadonly: [{
type: HostBinding,
args: ["class.cds--text-input-wrapper--readonly"]
}], fluidClass: [{
type: HostBinding,
args: ["class.cds--text-input--fluid"]
}], fluidSkeletonClass: [{
type: HostBinding,
args: ["class.cds--text-input--fluid__skeleton"]
}], labelInputID: [{
type: Input
}], disabled: [{
type: Input
}], skeleton: [{
type: Input
}], labelTemplate: [{
type: Input
}], textInputTemplate: [{
type: Input
}], helperText: [{
type: Input
}], invalidText: [{
type: Input
}], invalid: [{
type: Input
}], warn: [{
type: Input
}], warnText: [{
type: Input
}], ariaLabel: [{
type: Input
}], fluid: [{
type: Input
}], hideLabel: [{
type: Input
}], inline: [{
type: Input
}], size: [{
t