1/*! 2 * pivot v1.2.0 3 * FCSA Pivot Design System 4 * https://careers.fcsamerica.com/ 5 */ 6import { __decorate } from '../../../node_modules/tslib/tslib.es6.js'; 7import '../../../node_modules/@lit/reactive-element/reactive-element.js'; 8import { nothing as E, html as x } from '../../../node_modules/lit-html/lit-html.js'; 9import '../../../node_modules/lit-element/lit-element.js'; 10import { customElement as t } from '../../../node_modules/@lit/reactive-element/decorators/custom-element.js'; 11import { property as n } from '../../../node_modules/@lit/reactive-element/decorators/property.js'; 12import { state as r } from '../../../node_modules/@lit/reactive-element/decorators/state.js'; 13import { ifDefined as o } from '../../../node_modules/lit-html/directives/if-defined.js'; 14import { unsafeSVG as o$1 } from '../../../node_modules/lit-html/directives/unsafe-svg.js'; 15import { nanoid } from '../../../node_modules/nanoid/index.browser.js'; 16import { styles } from './text-area.css.js'; 17import { styles as styles$1 } from '../field-label/field-label.css.js'; 18import { PivotBase } from '../base/base.js'; 19import { errorIcon } from './constants.js'; 20import { renderFieldLabel } from '../field-label/render-field-label.js'; 21import '../field-helper-message/field-helper-message.js'; 22import { FormAssociationController } from '../base/form-association-controller.js'; 23 24const tagName = "pivot-text-area"; 25/* eslint-disable jsdoc/valid-types */ 26/** 27 * A multi-line text input component with optional character count and helper text. 28 * 29 * **Accessibility:** In the error state (`pivot-error`, `pivot-error-text`, or a 30 * displayed native validation message), `aria-invalid="true"` is applied 31 * to the native textarea (WCAG 4.1.2). When helper or error text is present, the 32 * native textarea is linked to the `pivot-field-helper-message` via `aria-describedby` 33 * so assistive technology announces the message on focus (WCAG 1.3.1). The field 34 * label is rendered by the shared `renderFieldLabel` helper directly into this 35 * component's own shadow root â the same tree scope as the `<textarea>` â so its 36 * `<label for>` resolves to a working native label association and clicking or 37 * tapping the label focuses the textarea. The textarea's accessible name is still 38 * set explicitly via `aria-label` â `pivot-aria-label` if provided, otherwise the 39 * visible `pivot-label` text â since `aria-label` takes precedence over the 40 * associated `<label>` in accessible-name computation, letting `pivot-aria-label` 41 * override the visible text when the two need to differ (WCAG 4.1.2). 42 * 43 * @element pivot-text-area 44 * 45 * @fires pivot-text-area-input - Fired on every keystroke with the current value in `detail.value`. 46 * 47 * @slot field-label-tooltip - Optional tooltip content rendered beside the field label 48 * 49 * @csspart container - The outer wrapper element 50 * @csspart textarea - The native textarea element 51 * 52 * @cssprop --component-field-gap - Gap between the label, textarea, and helper/error message regions 53 * @cssprop --component-field-label-gap - Gap between the label text, optional/required indicator, and tooltip icon 54 * @cssprop --global-interaction-md - Minimum block size (height) of the textarea 55 * @cssprop --component-field-control-input-placeholder - Placeholder text color 56 * @cssprop --component-field-control-border-color-error - Border color of the textarea in the error state 57 * @cssprop --component-field-control-general-inset-y - Vertical padding reserved inside the textarea for the character counter when shown 58 * @cssprop --component-input-field-inset-y - Vertical position of the character counter within the textarea 59 * @cssprop --component-input-field-inset-x - Horizontal position of the character counter within the textarea 60 * @cssprop --component-field-helper-gap - Gap between the icon and text within the character counter 61 * @cssprop --global-color-text-on-light-error-color - Text color of the required-field indicator, and of the character counter when the limit is exceeded 62 */ 63let PivotTextArea = class PivotTextArea extends PivotBase { 64 /** 65 * Imported CSS styles from text-area.css 66 */ 67 static { this.styles = [ 68 PivotBase.convertCssForComponent(tagName, styles), 69 PivotBase.convertCssForComponent(tagName, styles$1), 70 ]; } 71 static { this.formAssociated = true; } 72 constructor() { 73 super(); 74 this._formController = new FormAssociationController(this); 75 /** 76 * The accessible name for the textarea. The field label is rendered by the 77 * shared `renderFieldLabel` helper directly into this component's own 78 * shadow root, in the same tree scope as the `<textarea>`, so its 79 * `<label for>` does resolve to a working native label association â but 80 * the textarea's `aria-label` still takes precedence in accessible-name 81 * computation, and falls back to the visible `pivotLabel` text 82 * automatically when this is unset, so this only needs to be set to 83 * override that text. 84 * 85 * @type {string} 86 * @attr pivot-aria-label 87 * @default "" 88 */ 89 this.pivotAriaLabel = ""; 90 /** 91 * This will create an attribute: pivot-name="value" 92 * 93 * @type {string} 94 * @attr pivot-name 95 * @default 96 */ 97 this.pivotName = ""; 98 /** 99 * This will create an attribute: pivot-id="value" 100 * 101 * @type {string} 102 * @attr pivot-id 103 * @default "" 104 */ 105 this.pivotId = ""; 106 /** 107 * Placeholder text shown inside the textarea when empty. 108 * 109 * @type {string | null} 110 * @attr pivot-placeholder 111 * @default null 112 */ 113 this.pivotPlaceholder = null; 114 /** 115 * Helper text displayed below the textarea. Shown automatically when a value is provided. 116 * 117 * @type {string} 118 * @attr pivot-helper-text 119 * @default "" 120 */ 121 this.pivotHelperText = ""; 122 /** 123 * Error message displayed below the textarea. Setting a value puts the
124 * component into its error state and takes priority over both 125 * `pivot-helper-text` and the browser's native validation message. 126 * 127 * @type {string} 128 * @attr pivot-error-text 129 * @default "" 130 */ 131 this.pivotErrorText = ""; 132 /** 133 * Puts the component into an error state, applying error styling and 134 * `aria-invalid="true"`. Does not affect the element's validity. The error 135 * message itself comes from `pivot-error-text` or, after a failed 136 * validation check, the browser's native validation message. 137 * 138 * @type {boolean} 139 * @attr pivot-error 140 * @default false 141 */ 142 this.pivotError = false; 143 /** 144 * Marks the field as optional, displaying an "(optional)" label next to the field label. 145 * 146 * @type {boolean} 147 * @attr pivot-optional 148 * @default false 149 */ 150 this.pivotOptional = false; 151 /** 152 * Hides the required asterisk, optional text, and tooltip on the field label. 153 * Set by parent components (e.g. `pivot-radio`, `pivot-checkbox`) that show them on their legend. 154 * 155 * @type {boolean} 156 * @default false 157 */ 158 this.pivotHideLabelIndicators = false; 159 /** 160 * Whether the field-label-tooltip slot has assigned content. 161 * 162 * @type {boolean} 163 * @default false 164 */ 165 this.hasFieldLabelTooltip = false; 166 /** 167 * Disables the textarea, preventing user interaction. 168 * 169 * @type {boolean} 170 * @attr pivot-disabled 171 * @default false 172 */ 173 this.pivotDisabled = false; 174 /** 175 * Maximum number of characters. When set, a character counter is shown automatically. 176 * The counter enters an error state when the limit is exceeded. 177 * 178 * @type {number | null} 179 * @attr pivot-max-length 180 * @default null 181 */ 182 this.pivotMaxLength = null; 183 /** 184 * Marks the field as required. 185 * 186 * @type {boolean} 187 * @attr pivot-required 188 * @default false 189 */ 190 this.pivotRequired = false; 191 /** 192 * Number of visible text rows. 193 * 194 * @type {number} 195 * @attr pivot-rows 196 * @default 4 197 */ 198 this.pivotRows = 4; 199 /** 200 * Current character count, updated on every input event. 201 * 202 * @type {number} 203 * @default 0 204 */ 205 this._charCount = 0; 206 this.uniqueID = nanoid(10); 207 } 208 connectedCallback() { 209 super.connectedCallback?.(); 210 this.prepareSlots(); 211 } 212 // Restores slot attributes stripped by SSG tools (e.g. WebC) at build time. 213 prepareSlots() { 214 for (const child of Array.from(this.children)) { 215 if (child.tagName === "PIVOT-FIELD-LABEL-TOOLTIP" && 216 !child.getAttribute("slot")) { 217 child.setAttribute("slot", "field-label-tooltip"); 218 } 219 } 220 } 221 get _isOverLimit() { 222 return (this.pivotMaxLength !== null && this._charCount > this.pivotMaxLength); 223 } 224 willUpdate(changedProperties) { 225 super.willUpdate(changedProperties); 226 if (!this.pivotName.trim()) { 227 throw new Error("pivot-text-area requires pivot-name."); 228 } 229 this.hasFieldLabelTooltip = Array.from(this.children).some((child) => child.slot === "field-label-tooltip" || 230 child.tagName === "PIVOT-FIELD-LABEL-TOOLTIP"); 231 // Drives the CSS-only error state; keeps `pivotError` free for consumer control. 232 this.toggleAttribute("has-error", this.pivotError || 233 !!this.pivotErrorText || 234 !!this._formController.nativeErrorText); 235 } 236 updated(changedProperties) { 237 super.updated(changedProperties); 238 if (changedProperties.has("pivotName")) { 239 if (this.pivotName) { 240 this.setAttribute("name", this.pivotName); 241 } 242 else { 243 this.removeAttribute("name"); 244 } 245 } 246 this.syncFormState(); 247 } 248 /** 249 * Syncs the component's form value and validity state with ElementInternals. 250 * Called after each render and after user input so the associated form 251 * always has an accurate value and constraint-validation state. 252 * 253 * @returns {void} 254 */ 255 syncFormState() { 256 const textarea = this.renderRoot?.querySelector("textarea"); 257 const value = textarea?.value ?? ""; 258 this._formController.setFormValue(value); 259 if (textarea) { 260 if (textarea.validity.valid) { 261 this._formController.setValidity({}); 262 } 263 else { 264 this._formController.setValidity({ 265 badInput: textarea.validity.badInput, 266 customError: textarea.validity.customError, 267 tooLong: textarea.validity.tooLong, 268 tooShort: textarea.validity.tooShort, 269 valueMissing: textarea.validity.valueMissing, 270 }, textarea.validationMessage, textarea); 271 } 272 } 273 } 274 /** 275 * Handles native textarea input events, updating the character count 276 * and syncing form state. 277 * 278 * @param {Event} event - The input event. 279 * @returns {void} 280 */ 281 handleInput(event) { 282 const target = event.target; 283 if (!(target instanceof HTMLTextAreaElement)) 284 return; 285 this._charCount = target.value.length; 286 this.syncFormState(); 287 this.dispatchEvent(new CustomEvent("pivot-text-area-input", { 288 detail: { value: target.value }, 289 bubbles: true, 290 composed: true, 291 })); 292 } 293 /** 294 * Resets the textarea and form state when the associated form resets. 295 * 296 * @returns {void} 297 */ 298 formResetCallback() { 299 const textarea = this.renderRoot?.querySelector("textarea"); 300 if (textarea) 301 textarea.value = ""; 302 this._charCount = 0; 303 this._formController.setFormValue(""); 304 this._formController.setValidity({}); 305 } 306 checkValidity() { 307 return this._formController.checkValidity(); 308 } 309 reportValidity() { 310 return this._formController.reportValidity(); 311 } 312 formDisabledCallback(disabled) { 313 return this._formController.formDisabledCallback(disabled); 314 } 315 render() { 316 const showCharCount = this.pivotMaxLength !== null; 317 const showCounterError = showCharCount && this._isOverLimit; 318 const helperId = `${this.pivotName}-helper`; 319 const controlId = this.pivotId || `${this.pivotName}-${this.uniqueID}`; 320 const nativeError = this._formController.nativeErrorText; 321 const showError = this.pivotError || !!this.pivotErrorText || !!nativeError; 322 const errorText = this.pivotErrorText || nativeError; 323 const hasMessage = !!(this.pivotHelperText || errorText); 324 return x ` <div class="text-area-container" part="container"> 325 ${renderFieldLabel({ 326 id: controlId, 327 pivotLabel: this.pivotLabel, 328 pivotOptional: this.pivotOptional, 329 pivotRequired: this.pivotRequired, 330 hasFieldLabelTooltip: this.hasFieldLabelTooltip, 331 hideIndicators: this.pivotHideLabelIndicators, 332 })} 333 <div 334 class="text-area-input-wrapper${showCharCount ? " has-char-count" : ""}" 335 > 336 <textarea 337 id="${controlId}" 338 part="textarea" 339 name=${o(this.pivotName || undefined)} 340 placeholder=${o(this.pivotPlaceholder ?? undefined)} 341 rows=${this.pivotRows} 342 ?disabled=${this.pivotDisabled} 343 ?required=${this.pivotRequired} 344 aria-label=${o(this.pivotAriaLabel || this.pivotLabel || undefined)}
345 @input=${this.handleInput} 346 aria-invalid=${showError ? "true" : "false"} 347 aria-describedby=${o(hasMessage ? helperId : undefined)} 348 ></textarea> 349 ${showCharCount 350 ? x `<span 351 class="text-area-char-count${showCounterError ? " error" : ""}" 352 aria-live="polite" 353 > 354 ${showCounterError ? o$1(errorIcon) : E} 355 ${this._charCount} / ${this.pivotMaxLength} 356 </span>` 357 : E} 358 </div> 359 360 ${this.pivotHelperText || hasMessage 361 ? x ` <pivot-field-helper-message 362 id="${helperId}" 363 .pivotError=${showError} 364 .pivotErrorText=${errorText} 365 .pivotHelperText=${this.pivotHelperText} 366 ></pivot-field-helper-message>` 367 : E} 368 </div>`; 369 } 370}; 371__decorate([ 372 n({ type: String, attribute: "pivot-label" }) 373], PivotTextArea.prototype, "pivotLabel", void 0); 374__decorate([ 375 n({ type: String, attribute: "pivot-aria-label" }) 376], PivotTextArea.prototype, "pivotAriaLabel", void 0); 377__decorate([ 378 n({ type: String, attribute: "pivot-name" }) 379], PivotTextArea.prototype, "pivotName", void 0); 380__decorate([ 381 n({ type: String, attribute: "pivot-id" }) 382], PivotTextArea.prototype, "pivotId", void 0); 383__decorate([ 384 n({ type: String, attribute: "pivot-placeholder" }) 385], PivotTextArea.prototype, "pivotPlaceholder", void 0); 386__decorate([ 387 n({ type: String, attribute: "pivot-helper-text" }) 388], PivotTextArea.prototype, "pivotHelperText", void 0); 389__decorate([ 390 n({ type: String, attribute: "pivot-error-text" }) 391], PivotTextArea.prototype, "pivotErrorText", void 0); 392__decorate([ 393 n({ type: Boolean, attribute: "pivot-error" }) 394], PivotTextArea.prototype, "pivotError", void 0); 395__decorate([ 396 n({ type: Boolean, attribute: "pivot-optional" }) 397], PivotTextArea.prototype, "pivotOptional", void 0); 398__decorate([ 399 n({ type: Boolean, attribute: false }) 400], PivotTextArea.prototype, "pivotHideLabelIndicators", void 0); 401__decorate([ 402 r() 403], PivotTextArea.prototype, "hasFieldLabelTooltip", void 0); 404__decorate([ 405 n({ type: Boolean, attribute: "pivot-disabled" }) 406], PivotTextArea.prototype, "pivotDisabled", void 0); 407__decorate([ 408 n({ type: Number, attribute: "pivot-max-length" }) 409], PivotTextArea.prototype, "pivotMaxLength", void 0); 410__decorate([ 411 n({ type: Boolean, attribute: "pivot-required" }) 412], PivotTextArea.prototype, "pivotRequired", void 0); 413__decorate([ 414 n({ type: Number, attribute: "pivot-rows" }) 415], PivotTextArea.prototype, "pivotRows", void 0); 416__decorate([ 417 r() 418], PivotTextArea.prototype, "_charCount", void 0); 419PivotTextArea = __decorate([ 420 t(tagName) 421], PivotTextArea); 422 423export { PivotTextArea }; 424//# sourceMappingURL=text-area.js.map
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.