@import "./textarea.vars"; // Textarea // -------------------------------------------------- :host { /** * @prop --background: Background of the textarea * * @prop --border-radius: Border radius of the textarea * @prop --border-color: Color of the border below the textarea when using helper text, error text, or counter * @prop --border-radius: Radius of the textarea border. A large radius may display unevenly when using fill="outline"; if needed, use shape="round" instead or increase --padding-start. * @prop --border-style: Style of the border below the textarea when using helper text, error text, or counter * @prop --border-width: Width of the border below the textarea when using helper text, error text, or counter * * @prop --color: Color of the text * * @prop --placeholder-color: Color of the placeholder text * @prop --placeholder-font-style: Style of the placeholder text * @prop --placeholder-font-weight: Weight of the placeholder text * @prop --placeholder-opacity: Opacity of the placeholder text * * @prop --highlight-color-focused: The color of the highlight on the textarea when focused * @prop --highlight-color-valid: The color of the highlight on the textarea when valid * @prop --highlight-color-invalid: The color of the highlight on the textarea when invalid * * @prop --padding-top: Top padding of the textarea * @prop --padding-end: Right padding if direction is left-to-right, and left padding if direction is right-to-left of the textarea * @prop --padding-bottom: Bottom padding of the textarea * @prop --padding-start: Left padding if direction is left-to-right, and right padding if direction is right-to-left of the textarea */ --background: initial; --color: initial; --placeholder-color: initial; --placeholder-font-style: initial; --placeholder-font-weight: initial; --placeholder-opacity: #{$placeholder-opacity}; --padding-top: 0; --padding-end: 0; --padding-bottom: 0; --padding-start: 0; --border-radius: 0; --border-style: solid; --highlight-color-focused: #{ion-color(primary, base)}; --highlight-color-valid: #{ion-color(success, base)}; --highlight-color-invalid: #{ion-color(danger, base)}; /** * This is a private API that is used to switch * out the highlight color based on the state * of the component without having to write * different selectors for different fill variants. */ --highlight-color: var(--highlight-color-focused); display: block; position: relative; width: 100%; color: var(--color); font-family: $font-family-base; z-index: $z-index-item-input; box-sizing: border-box; } // Textarea Wrapper // ---------------------------------------------------------------- // TODO: FW-2876 - Make this style a :host style, remove :not selector :host(:not(.legacy-textarea)) { min-height: 44px; } /** * Since the label sits on top of the element, * the component needs to be taller otherwise the * label will appear too close to the textarea text. * Also, floating and stacked labels should not * push the label down since it it * sits on top of the textarea. */ :host(.textarea-label-placement-floating), :host(.textarea-label-placement-stacked) { --padding-top: 0px; min-height: 56px; } /** * When the cols property is set we should * respect that width instead of defaulting * to taking up the entire line. * Requires both the cols and autoGrow * properties to be reflected as attributes * on the host. * * cols does not work with autoGrow because * autoGrow would prevent line breaks from naturally * occurring until the textarea takes up the entire line width. */ :host([cols]:not([auto-grow])) { width: fit-content; } // TODO: FW-2876 - Remove this selector :host(.legacy-textarea) { flex: 1; background: var(--background); white-space: pre-wrap; } // TODO: FW-2876 - Remove this selector :host(.legacy-textarea.ion-color) { color: current-color(base); } // TODO: FW-2876 - Remove this selector, move styles to :host :host(:not(.legacy-textarea)) { --padding-bottom: #{$textarea-padding-bottom}; } :host(.ion-color) { --highlight-color-focused: #{current-color(base)}; background: initial; } // Textarea Within An Item // -------------------------------------------------- :host-context(ion-item) { align-self: baseline; } :host-context(ion-item:not(.item-label)) { --padding-start: 0; } :host-context(ion-item)[slot="start"], :host-context(ion-item)[slot="end"] { width: auto; } // Native Textarea // -------------------------------------------------- .native-textarea { // Browsers may add a default margin to the textarea @include margin(0); @include padding(0, 0, 0, 0); display: block; position: relative; flex: 1; width: 100%; max-width: 100%; max-height: 100%; border: 0; outline: none; background: transparent; white-space: pre-wrap; /** * This ensures the textarea * remains on top of any decoration * that we render (particularly the * outline border when fill="outline"). * If we did not do this then Axe would * be unable to determine the color * contrast of the textarea. */ z-index: 1; box-sizing: border-box; resize: none; appearance: none; &::placeholder { @include padding(0); color: var(--placeholder-color); font-family: inherit; font-style: var(--placeholder-font-style); font-weight: var(--placeholder-font-weight); opacity: var(--placeholder-opacity); } } // TODO: FW-2876 - Remove this selector :host(.legacy-textarea) .native-textarea { white-space: inherit; } // TODO: FW-2876 - Remove this selector :host(.legacy-textarea) .native-textarea, :host(.legacy-textarea) .textarea-legacy-wrapper::after { @include padding(var(--padding-top), var(--padding-end), var(--padding-bottom), var(--padding-start)); @include border-radius(var(--border-radius)); } .native-textarea { color: inherit; font-family: inherit; font-size: inherit; font-style: inherit; font-weight: inherit; letter-spacing: inherit; text-align: inherit; text-decoration: inherit; text-indent: inherit; text-overflow: inherit; text-transform: inherit; grid-area: 1 / 1 / 2 / 2; word-break: break-word; } // TODO: FW-2876 - Remove this selector :host(.legacy-textarea) .textarea-legacy-wrapper::after { @include text-inherit(); grid-area: 1 / 1 / 2 / 2; word-break: break-word; } // Input Cover: Unfocused // -------------------------------------------------- // The input cover is the div that actually receives the // tap/click event when scroll assist is configured to true. // This make it so the native input element is not clickable. // This will only show when the scroll assist is configured // otherwise the .input-cover will not be rendered at all // The input cover is not clickable when the input is disabled .cloned-input { @include position(0, null, 0, 0); position: absolute; pointer-events: none; } /** * The cloned input needs to be disabled on * Android otherwise the viewport will still * shift when running scroll assist. */ .cloned-input:disabled { opacity: 1; } // TODO: FW-2876 - Remove this selector :host(.legacy-textarea[auto-grow]) .cloned-input { @include margin(0, 0, 0, 0); } :host([auto-grow]) .cloned-input { // Workaround for webkit rendering issue with scroll assist. // When cloning the textarea and scrolling into view, // a white box is rendered from the difference in height // from the auto grow container. // This change forces the cloned input to match the true // height of the textarea container. height: 100%; } :host([auto-grow]) .native-textarea { overflow: hidden; } // Item Floating: Placeholder // ---------------------------------------------------------------- // When used with a floating item the placeholder should hide :host-context(.item-label-floating.item-has-placeholder:not(.item-has-value)) { opacity: 0; } :host-context(.item-label-floating.item-has-placeholder:not(.item-has-value).item-has-focus) { transition: opacity 0.15s cubic-bezier(0.4, 0, 0.2, 1); opacity: 1; } // Textarea Wrapper // ---------------------------------------------------------------- .textarea-wrapper { @include padding(0px, var(--padding-end), 0px, var(--padding-start)); @include border-radius(var(--border-radius)); display: flex; position: relative; flex-grow: 1; align-items: flex-start; height: inherit; min-height: inherit; transition: background-color 15ms linear; background: var(--background); line-height: normal; } // Textarea Native Wrapper // ---------------------------------------------------------------- .native-wrapper { display: flex; position: relative; flex-grow: 1; width: 100%; height: 100%; } // Textarea Native // ---------------------------------------------------------------- :host(.has-focus) textarea { caret-color: var(--highlight-color); } .native-wrapper textarea { @include padding(var(--padding-top), 0px, var(--padding-bottom), 0px); } .native-wrapper, // TODO: FW-2876 - Remove this selector, keep .native-wrapper .textarea-legacy-wrapper { display: grid; min-width: inherit; max-width: inherit; min-height: inherit; max-height: inherit; /** * This avoids a WebKit bug where * the height of the inner textarea * is incorrect and flows outside the * parent container: https://bugs.webkit.org/show_bug.cgi?id=256781 * TODO FW-4734 */ grid-auto-rows: 100%; &::after { // This technique is used for an auto-resizing textarea. // The text contents are reflected as a pseudo-element that is visually hidden. // This causes the textarea container to grow as needed to fit the contents. white-space: pre-wrap; content: attr(data-replicated-value) " "; visibility: hidden; } } .native-wrapper::after { @include padding(var(--padding-top), 0, var(--padding-bottom), 0); @include margin(0, 0, 0, 0); @include border-radius(var(--border-radius)); /** * Note: Do not use @include text-inherit() * as that sets white-space: inherit * Instead, we use white-space: pre-wrap above. */ color: inherit; font-family: inherit; font-size: inherit; font-style: inherit; font-weight: inherit; letter-spacing: inherit; text-align: inherit; text-decoration: inherit; text-indent: inherit; text-overflow: inherit; text-transform: inherit; grid-area: 1 / 1 / 2 / 2; word-break: break-word; } // Textarea Highlight // ---------------------------------------------------------------- :host(.ion-touched.ion-invalid) { --highlight-color: var(--highlight-color-invalid); } /** * The component highlight is only shown * on focus, so we can safely set the valid * color state when touched/valid. If we * set it when .has-focus is present then * the highlight color would change * from the valid color to the component's * color during the transition after the * component loses focus. */ :host(.ion-valid) { --highlight-color: var(--highlight-color-valid); } // Textarea Bottom Content // ---------------------------------------------------------------- .textarea-bottom { /** * The bottom content should take on the start and end * padding so it is always aligned with either the label * or the start of the textarea. */ @include padding(5px, var(--padding-end), 0, var(--padding-start)); display: flex; justify-content: space-between; border-top: var(--border-width) var(--border-style) var(--border-color); font-size: dynamic-font(12px); } /** * If the textarea has a validity state, the * border and label should reflect that as a color. */ :host(.has-focus.ion-valid), :host(.ion-touched.ion-invalid) { --border-color: var(--highlight-color); } // Textarea Hint Text // ---------------------------------------------------------------- /** * Error text should only be shown when .ion-invalid is * present on the textarea. Otherwise the helper text should * be shown. */ .textarea-bottom .error-text { display: none; color: var(--highlight-color-invalid); } .textarea-bottom .helper-text { display: block; color: #{$background-color-step-550}; } :host(.ion-touched.ion-invalid) .textarea-bottom .error-text { display: block; } :host(.ion-touched.ion-invalid) .textarea-bottom .helper-text { display: none; } // Textarea Max Length Counter // ---------------------------------------------------------------- .textarea-bottom .counter { /** * Counter should always be at * the end of the container even * when no helper/error texts are used. */ @include margin-horizontal(auto, null); color: #{$background-color-step-550}; white-space: nowrap; padding-inline-start: 16px; } // Textarea Label // ---------------------------------------------------------------- .label-text-wrapper { @include padding(var(--padding-top), 0px, var(--padding-bottom), 0px); /** * Label text should not extend * beyond the bounds of the textarea. * However, we do not set the max * width to 100% because then * only the label would show and users * would not be able to see what they are typing. */ max-width: 200px; transition: color 150ms cubic-bezier(0.4, 0, 0.2, 1), transform 150ms cubic-bezier(0.4, 0, 0.2, 1); /** * This ensures that double tapping this text * clicks the