/** @license Copyright (c) 2024 trading_peter This program is available under Apache License Version 2.0 */ import { FormElement } from '@tp/helpers/form-element.js'; import { DomQuery } from '@tp/helpers/dom-query.js'; import { ControlState } from '@tp/helpers/control-state.js'; import { LitElement, html, css } from 'lit'; class TpTextarea extends FormElement(DomQuery(ControlState(LitElement))) { static get styles() { return [ css` :host { display: block; position: relative; border: solid 1px #9E9E9E; overflow: hidden; } :host([invalid]) { border: solid 1px #B71C1C; } .error-message { position: absolute; bottom: 0; left: 0; right: 0; font-size: 10px; color: var(--tp-textarea-text-color-invalid, #B71C1C); transition: opacity 0.3s; opacity: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; pointer-events: none; background: #ffffff; } :host([invalid]) .error-message { opacity: 1; } .wrap { position: absolute; top: 0; left: 0; right: 0; bottom: 0; padding: var(--tp-textarea-padding, 0); } .mirror-text { visibility: hidden; word-wrap: break-word; padding: var(--tp-textarea-padding, 0); } .wrap ::slotted(textarea) { position: relative; outline: none; border: none; resize: none; background: inherit; color: inherit; width: 100%; height: 100%; font-size: inherit; font-family: inherit; line-height: inherit; text-align: inherit; box-sizing: border-box; overflow: hidden; padding: 0; } ` ]; } render() { const { errorMessage } = this; return html`
${errorMessage}
`; } static get properties() { return { readonly: { type: Boolean }, required: { type: Boolean }, errorMessage: { type: String }, invalid: { type: Boolean, reflect: true }, }; } constructor() { super(); this.value = ''; } connectedCallback() { super.connectedCallback(); this.addEventListener('input', this._onInput); } disconnectedCallback() { super.disconnectedCallback(); this.removeEventListener('input', this._onInput); } updated(changes) { if (changes.has('value')) { this._syncToTextarea(); } if (changes.has('readonly')) { this._readOnlyChanged(); } } get textarea() { return this.querySelector('textarea'); } get selectionStart() { return this.textarea.selectionStart; } get selectionEnd() { return this.textarea.selectionEnd; } validate() { // Use the nested input's native validity. let valid = this.textarea.validity.valid; // Only do extra checking if the browser thought this was valid. if (valid) { // Empty, required input is invalid if (this.required && this.value === '') { valid = false; } } this.invalid = !valid; return valid; } reset() { this.value = ''; this.textarea.value = ''; this.invalid = false; } _syncToTextarea() { if (typeof this.value !== 'string') { return this.value = ''; } if (this.textarea) { this.textarea.value = this.value || ''; this.$.mirror.innerHTML = this._valueForMirror(); } } _readOnlyChanged(state) { this.textarea.readOnly = state; } _onInput() { this.$.mirror.innerHTML = this._valueForMirror(); this.value = this.textarea.value; } _constrain(tokens) { let _tokens; tokens = tokens || ['']; // Enforce the min and max heights for a multiline input to avoid measurement if (this.maxRows > 0 && tokens.length > this.maxRows) { _tokens = tokens.slice(0, this.maxRows); } else { _tokens = tokens.slice(0); } while (this.rows > 0 && _tokens.length < this.rows) { _tokens.push(''); } // Use   instead   of to allow this element to be used in XHTML. return _tokens.join('
') + ' '; } _valueForMirror() { const input = this.textarea; if (!input) { return; } this.tokens = (input && input.value) ? input.value.replace(/&/gm, '&').replace(/"/gm, '"').replace(/'/gm, ''').replace(//gm, '>').split('\n') : ['']; return this._constrain(this.tokens); } _updateCached() { this.$.mirror.innerHTML = this._constrain(this.tokens); } } window.customElements.define('tp-textarea', TpTextarea);