UNPKG

carbon-components-angular

Version:
1,237 lines (1,222 loc) 86.1 kB
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