mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-11-22 11:02:11 +03:00
Improve textarea and fix bugs (#5380)
* Delete Textarea-old.svelte * Update PrDetailsModal.svelte * update height calculation method * remove `console.log` * Delete autoHeight.ts * Update CommitCard.svelte * code review fixes * improve textarea size detection * Update CommitMessageInput.svelte * simplify styles * Update CommitMessageInput.svelte
This commit is contained in:
parent
f8d9434573
commit
359d8d5b51
@ -150,7 +150,6 @@
|
||||
<Modal bind:this={commitMessageModal} width="small" onSubmit={submitCommitMessageModal}>
|
||||
{#snippet children(_, close)}
|
||||
<CommitMessageInput
|
||||
focusOnMount
|
||||
bind:commitMessage={description}
|
||||
bind:valid={commitMessageValid}
|
||||
isExpanded={true}
|
||||
|
@ -19,16 +19,14 @@
|
||||
import { getContext, getContextStore } from '@gitbutler/shared/context';
|
||||
import Checkbox from '@gitbutler/ui/Checkbox.svelte';
|
||||
import Icon from '@gitbutler/ui/Icon.svelte';
|
||||
import Textarea from '@gitbutler/ui/Textarea.svelte';
|
||||
import Tooltip from '@gitbutler/ui/Tooltip.svelte';
|
||||
import { autoHeight } from '@gitbutler/ui/utils/autoHeight';
|
||||
import { resizeObserver } from '@gitbutler/ui/utils/resizeObserver';
|
||||
import { isWhiteSpaceString } from '@gitbutler/ui/utils/string';
|
||||
import { createEventDispatcher, onMount, tick } from 'svelte';
|
||||
import { fly } from 'svelte/transition';
|
||||
|
||||
export let isExpanded: boolean;
|
||||
export let commitMessage: string;
|
||||
export let focusOnMount: boolean = false;
|
||||
export let valid: boolean = false;
|
||||
export let commit: (() => void) | undefined = undefined;
|
||||
export let cancel: () => void;
|
||||
@ -60,15 +58,6 @@
|
||||
return `${title}\n\n${description}`;
|
||||
}
|
||||
|
||||
function focusTextAreaOnMount(el: HTMLTextAreaElement) {
|
||||
if (focusOnMount) el.focus();
|
||||
}
|
||||
|
||||
function updateFieldsHeight() {
|
||||
if (titleTextArea) autoHeight(titleTextArea);
|
||||
if (descriptionTextArea) autoHeight(descriptionTextArea);
|
||||
}
|
||||
|
||||
async function generateCommitMessage(files: LocalFile[]) {
|
||||
const hunks = files.flatMap((f) =>
|
||||
f.hunks.filter((h) => $selectedOwnership.isSelected(f.id, h.id))
|
||||
@ -111,11 +100,6 @@
|
||||
}
|
||||
|
||||
aiLoading = false;
|
||||
|
||||
// set timeout to update the height of the textareas
|
||||
setTimeout(() => {
|
||||
updateFieldsHeight();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
@ -139,7 +123,6 @@
|
||||
titleTextArea.focus();
|
||||
titleTextArea.selectionStart = titleTextArea.textLength;
|
||||
}
|
||||
autoHeight(e.currentTarget);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -179,7 +162,6 @@
|
||||
if (descriptionTextArea) {
|
||||
descriptionTextArea.focus();
|
||||
descriptionTextArea.setSelectionRange(0, 0);
|
||||
autoHeight(descriptionTextArea);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -194,40 +176,45 @@
|
||||
</script>
|
||||
|
||||
{#if isExpanded}
|
||||
<div class="commit-box__textarea-wrapper text-input" use:resizeObserver={updateFieldsHeight}>
|
||||
<textarea
|
||||
<div class="commit-box__textarea-wrapper text-input">
|
||||
<Textarea
|
||||
value={title}
|
||||
unstyled
|
||||
placeholder="Commit summary"
|
||||
disabled={aiLoading}
|
||||
class="text-13 text-body text-semibold commit-box__textarea commit-box__textarea__title"
|
||||
fontSize={13}
|
||||
padding={{ top: 12, right: 12, bottom: 0, left: 12 }}
|
||||
fontWeight="semibold"
|
||||
spellcheck="false"
|
||||
rows="1"
|
||||
bind:this={titleTextArea}
|
||||
use:focusTextAreaOnMount
|
||||
on:focus={(e) => autoHeight(e.currentTarget)}
|
||||
on:input={(e) => {
|
||||
commitMessage = concatMessage(e.currentTarget.value, description);
|
||||
autoHeight(e.currentTarget);
|
||||
minRows={1}
|
||||
maxRows={10}
|
||||
bind:textBoxEl={titleTextArea}
|
||||
autofocus
|
||||
oninput={(e: InputEvent & { currentTarget: HTMLTextAreaElement }) => {
|
||||
const target = e.currentTarget;
|
||||
commitMessage = concatMessage(target.value, description);
|
||||
}}
|
||||
on:keydown={handleSummaryKeyDown}
|
||||
></textarea>
|
||||
onkeydown={handleSummaryKeyDown}
|
||||
/>
|
||||
|
||||
{#if title.length > 0 || description}
|
||||
<textarea
|
||||
<Textarea
|
||||
value={description}
|
||||
disabled={aiLoading}
|
||||
unstyled
|
||||
placeholder="Commit description (optional)"
|
||||
class="text-13 text-body commit-box__textarea commit-box__textarea__description"
|
||||
disabled={aiLoading}
|
||||
fontSize={13}
|
||||
padding={{ top: 0, right: 12, bottom: 0, left: 12 }}
|
||||
spellcheck="false"
|
||||
rows="1"
|
||||
bind:this={descriptionTextArea}
|
||||
on:focus={(e) => autoHeight(e.currentTarget)}
|
||||
on:input={(e) => {
|
||||
commitMessage = concatMessage(title, e.currentTarget.value);
|
||||
autoHeight(e.currentTarget);
|
||||
minRows={1}
|
||||
maxRows={30}
|
||||
bind:textBoxEl={descriptionTextArea}
|
||||
oninput={(e: InputEvent & { currentTarget: HTMLTextAreaElement }) => {
|
||||
const target = e.currentTarget;
|
||||
commitMessage = concatMessage(title, target.value);
|
||||
}}
|
||||
on:keydown={handleDescriptionKeyDown}
|
||||
></textarea>
|
||||
onkeydown={handleDescriptionKeyDown}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if title.length > 50}
|
||||
@ -310,24 +297,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.commit-box__textarea {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 16px;
|
||||
background: none;
|
||||
resize: none;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: oklch(from var(--clr-scale-ntrl-30) l c h / 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
.commit-box__textarea-tooltip {
|
||||
position: absolute;
|
||||
bottom: 12px;
|
||||
@ -345,15 +314,6 @@
|
||||
color: var(--clr-scale-ntrl-50);
|
||||
}
|
||||
|
||||
.commit-box__textarea__title {
|
||||
min-height: 31px;
|
||||
padding: 12px 12px 0 12px;
|
||||
}
|
||||
|
||||
.commit-box__textarea__description {
|
||||
padding: 0 12px 0 12px;
|
||||
}
|
||||
|
||||
.commit-box__texarea-actions {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
|
@ -160,7 +160,6 @@
|
||||
<Modal bind:this={commitMessageModal} width="small" onSubmit={submitCommitMessageModal}>
|
||||
{#snippet children(_, close)}
|
||||
<CommitMessageInput
|
||||
focusOnMount
|
||||
bind:commitMessage={description}
|
||||
bind:valid={commitMessageValid}
|
||||
isExpanded={true}
|
||||
|
@ -390,7 +390,7 @@
|
||||
autofocus
|
||||
padding={{ top: 12, right: 12, bottom: 12, left: 12 }}
|
||||
placeholder="Add description…"
|
||||
onchange={(e: InputEvent) => {
|
||||
oninput={(e: InputEvent) => {
|
||||
const target = e.currentTarget as HTMLTextAreaElement;
|
||||
inputBody = target.value;
|
||||
}}
|
||||
|
@ -1,253 +0,0 @@
|
||||
<script lang="ts" module>
|
||||
import { clickOutside } from './utils/clickOutside';
|
||||
export interface Props {
|
||||
id?: string;
|
||||
textBoxEl?: HTMLDivElement;
|
||||
label?: string;
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
fontSize?: number;
|
||||
minRows?: number;
|
||||
maxRows?: number;
|
||||
autofocus?: boolean;
|
||||
spellcheck?: boolean;
|
||||
autoComplete?: string;
|
||||
class?: string;
|
||||
flex?: string;
|
||||
padding?: {
|
||||
top: number;
|
||||
right: number;
|
||||
bottom: number;
|
||||
left: number;
|
||||
};
|
||||
borderless?: boolean;
|
||||
borderTop?: boolean;
|
||||
borderRight?: boolean;
|
||||
borderBottom?: boolean;
|
||||
borderLeft?: boolean;
|
||||
unstyled?: boolean;
|
||||
oninput?: (e: Event & { currentTarget: EventTarget & HTMLTextAreaElement }) => void;
|
||||
onfocus?: (
|
||||
this: void,
|
||||
e: FocusEvent & { currentTarget: EventTarget & HTMLTextAreaElement }
|
||||
) => void;
|
||||
onblur?: (
|
||||
this: void,
|
||||
e: FocusEvent & { currentTarget: EventTarget & HTMLTextAreaElement }
|
||||
) => void;
|
||||
onkeydown?: (e: KeyboardEvent & { currentTarget: EventTarget & HTMLTextAreaElement }) => void;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { pxToRem } from '$lib/utils/pxToRem';
|
||||
|
||||
let {
|
||||
id,
|
||||
textBoxEl = $bindable(),
|
||||
label,
|
||||
value = $bindable(),
|
||||
placeholder,
|
||||
disabled,
|
||||
fontSize = 13,
|
||||
minRows = 1,
|
||||
maxRows = 100,
|
||||
autofocus,
|
||||
class: className = '',
|
||||
flex,
|
||||
padding = { top: 12, right: 12, bottom: 12, left: 12 },
|
||||
borderless,
|
||||
borderTop = true,
|
||||
borderRight = true,
|
||||
borderBottom = true,
|
||||
borderLeft = true,
|
||||
unstyled,
|
||||
oninput,
|
||||
onfocus,
|
||||
onblur,
|
||||
onkeydown
|
||||
}: Props = $props();
|
||||
|
||||
let isEmpty = $state(value === '');
|
||||
|
||||
function getSelectionRange() {
|
||||
const selection = window.getSelection();
|
||||
if (selection) {
|
||||
const range = selection.getRangeAt(0);
|
||||
return range;
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (autofocus) {
|
||||
textBoxEl?.focus();
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (textBoxEl) {
|
||||
if (!disabled) {
|
||||
textBoxEl.setAttribute('contenteditable', 'plaintext-only');
|
||||
} else {
|
||||
textBoxEl.removeAttribute('contenteditable');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (value === ' ' || value === '') {
|
||||
isEmpty = true;
|
||||
} else {
|
||||
isEmpty = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="textarea-container"
|
||||
style:--placeholder-text={`"${placeholder && placeholder !== '' ? placeholder : 'Type here...'}"`}
|
||||
style:--font-size={pxToRem(fontSize)}
|
||||
style:--min-rows={minRows}
|
||||
style:--max-rows={maxRows}
|
||||
style:--padding-top={pxToRem(padding.top)}
|
||||
style:--padding-right={pxToRem(padding.right)}
|
||||
style:--padding-bottom={pxToRem(padding.bottom)}
|
||||
style:--padding-left={pxToRem(padding.left)}
|
||||
style:--line-height-ratio={1.6}
|
||||
style:flex
|
||||
>
|
||||
{#if label}
|
||||
<label class="textarea-label text-13 text-semibold" for={id}>
|
||||
{label}
|
||||
</label>
|
||||
{/if}
|
||||
<div
|
||||
bind:this={textBoxEl}
|
||||
use:clickOutside={{ handler: () => textBoxEl?.blur() }}
|
||||
{id}
|
||||
role="textbox"
|
||||
aria-multiline="true"
|
||||
tabindex={disabled ? -1 : 0}
|
||||
contenteditable="plaintext-only"
|
||||
onfocus={(e: Event) => {
|
||||
if (e.currentTarget) {
|
||||
onfocus?.(e as FocusEvent & { currentTarget: EventTarget & HTMLTextAreaElement });
|
||||
}
|
||||
}}
|
||||
onblur={(e: Event) => {
|
||||
if (e.currentTarget) {
|
||||
onblur?.(e as FocusEvent & { currentTarget: EventTarget & HTMLTextAreaElement });
|
||||
}
|
||||
}}
|
||||
oninput={(e: Event) => {
|
||||
const innerText = (e.target as HTMLDivElement).innerText;
|
||||
const eventMock = { currentTarget: { value: innerText } } as Event & {
|
||||
currentTarget: EventTarget & HTMLTextAreaElement;
|
||||
};
|
||||
|
||||
isEmpty = innerText === '';
|
||||
|
||||
oninput?.(eventMock);
|
||||
}}
|
||||
onkeydown={(e: KeyboardEvent) => {
|
||||
const selection = getSelectionRange();
|
||||
|
||||
const eventMock = {
|
||||
key: e.key,
|
||||
code: e.code,
|
||||
altKey: e.altKey,
|
||||
metaKey: e.metaKey,
|
||||
ctrlKey: e.ctrlKey,
|
||||
shiftKey: e.shiftKey,
|
||||
location: e.location,
|
||||
currentTarget: {
|
||||
value: (e.currentTarget as HTMLDivElement).innerText,
|
||||
selectionStart: selection?.startOffset,
|
||||
selectionEnd: selection?.endOffset
|
||||
}
|
||||
} as unknown as KeyboardEvent & { currentTarget: EventTarget & HTMLTextAreaElement };
|
||||
|
||||
onkeydown?.(eventMock);
|
||||
}}
|
||||
class="textarea scrollbar {className}"
|
||||
class:disabled
|
||||
class:text-input={!unstyled}
|
||||
class:textarea-unstyled={unstyled}
|
||||
class:textarea-placeholder={isEmpty}
|
||||
style:border-top-width={borderTop && !borderless ? '1px' : '0'}
|
||||
style:border-right-width={borderRight && !borderless ? '1px' : '0'}
|
||||
style:border-bottom-width={borderBottom && !borderless ? '1px' : '0'}
|
||||
style:border-left-width={borderLeft && !borderless ? '1px' : '0'}
|
||||
style:border-top-right-radius={!borderTop || !borderRight ? '0' : undefined}
|
||||
style:border-top-left-radius={!borderTop || !borderLeft ? '0' : undefined}
|
||||
style:border-bottom-right-radius={!borderBottom || !borderRight ? '0' : undefined}
|
||||
style:border-bottom-left-radius={!borderBottom || !borderLeft ? '0' : undefined}
|
||||
>
|
||||
{value}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.textarea-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.textarea-unstyled {
|
||||
outline: none;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.textarea {
|
||||
font-family: var(--base-font-family);
|
||||
line-height: var(--body-line-height);
|
||||
font-weight: var(--base-font-weight);
|
||||
white-space: pre-wrap;
|
||||
cursor: text;
|
||||
resize: none;
|
||||
|
||||
width: 100%;
|
||||
font-size: var(--font-size);
|
||||
min-height: calc(
|
||||
var(--font-size) * var(--line-height-ratio) * var(--min-rows) + var(--padding-top) +
|
||||
var(--padding-bottom)
|
||||
);
|
||||
max-height: calc(
|
||||
var(--font-size) * var(--line-height-ratio) * var(--max-rows) + var(--padding-top) +
|
||||
var(--padding-bottom)
|
||||
);
|
||||
padding: var(--padding-top) var(--padding-right) var(--padding-bottom) var(--padding-left);
|
||||
overflow-y: auto; /* Enable scrolling when max height is reached */
|
||||
overflow-x: hidden;
|
||||
word-wrap: break-word;
|
||||
transition:
|
||||
border-color var(--transition-fast),
|
||||
background-color var(--transition-fast);
|
||||
|
||||
&.disabled {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&.textarea-placeholder {
|
||||
display: block;
|
||||
white-space: pre-wrap;
|
||||
|
||||
&:before {
|
||||
content: var(--placeholder-text);
|
||||
color: var(--clr-text-3);
|
||||
cursor: text;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.textarea-label {
|
||||
color: var(--clr-text-2);
|
||||
}
|
||||
</style>
|
@ -6,6 +6,7 @@
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
fontWeight?: 'regular' | 'bold' | 'semibold';
|
||||
fontSize?: number;
|
||||
minRows?: number;
|
||||
maxRows?: number;
|
||||
@ -55,6 +56,7 @@
|
||||
maxRows = 100,
|
||||
autofocus,
|
||||
class: className = '',
|
||||
fontWeight = 'regular',
|
||||
flex,
|
||||
padding = { top: 12, right: 12, bottom: 12, left: 12 },
|
||||
borderless,
|
||||
@ -70,7 +72,19 @@
|
||||
onkeydown
|
||||
}: Props = $props();
|
||||
|
||||
let textBoxValue = $state(value);
|
||||
let measureEl: HTMLPreElement | undefined = $state();
|
||||
|
||||
$effect(() => {
|
||||
// mock textarea style
|
||||
if (textBoxEl && measureEl) {
|
||||
const textBoxElStyles = window.getComputedStyle(textBoxEl);
|
||||
|
||||
measureEl.style.fontFamily = textBoxElStyles.fontFamily;
|
||||
measureEl.style.fontSize = textBoxElStyles.fontSize;
|
||||
measureEl.style.fontWeight = textBoxElStyles.fontWeight;
|
||||
measureEl.style.border = textBoxElStyles.border;
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (autofocus) {
|
||||
@ -78,23 +92,17 @@
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (value !== undefined) {
|
||||
textBoxValue = value;
|
||||
}
|
||||
});
|
||||
const lineHeight = 1.6;
|
||||
|
||||
let maxHeight = fontSize * 1.5 * maxRows + padding.top + padding.bottom;
|
||||
let minHeight = fontSize * 1.5 * minRows + padding.top + padding.bottom;
|
||||
let maxHeight = $derived(fontSize * maxRows + padding.top + padding.bottom);
|
||||
let minHeight = $derived(fontSize * minRows + padding.top + padding.bottom);
|
||||
|
||||
let measureElHeight = $state(0);
|
||||
let textBoxElHeight = $state(0);
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="textarea-container"
|
||||
style:--placeholder-text={`"${placeholder && placeholder !== '' ? placeholder : 'Type here...'}"`}
|
||||
style:--font-size={pxToRem(fontSize)}
|
||||
style:--min-rows={minRows}
|
||||
style:--max-rows={maxRows}
|
||||
style:--padding-top={pxToRem(padding.top)}
|
||||
@ -112,54 +120,53 @@
|
||||
<pre
|
||||
class="textarea-measure-el"
|
||||
aria-hidden="true"
|
||||
bind:clientHeight={measureElHeight}
|
||||
style="min-height: {pxToRem(minHeight)}; max-height: {pxToRem(maxHeight)}">{textBoxValue +
|
||||
'\n'}</pre>
|
||||
<div class="textarea-wrapper">
|
||||
<textarea
|
||||
bind:this={textBoxEl}
|
||||
name={id}
|
||||
{id}
|
||||
bind:clientHeight={textBoxElHeight}
|
||||
class="textarea scrollbar {className}"
|
||||
class:disabled
|
||||
class:text-input={!unstyled}
|
||||
class:textarea-unstyled={unstyled}
|
||||
style:height={pxToRem(measureElHeight)}
|
||||
class:hide-scrollbar={measureElHeight < maxHeight}
|
||||
style:border-top-width={borderTop && !borderless ? '1px' : '0'}
|
||||
style:border-right-width={borderRight && !borderless ? '1px' : '0'}
|
||||
style:border-bottom-width={borderBottom && !borderless ? '1px' : '0'}
|
||||
style:border-left-width={borderLeft && !borderless ? '1px' : '0'}
|
||||
style:border-top-right-radius={!borderTop || !borderRight ? '0' : undefined}
|
||||
style:border-top-left-radius={!borderTop || !borderLeft ? '0' : undefined}
|
||||
style:border-bottom-right-radius={!borderBottom || !borderRight ? '0' : undefined}
|
||||
style:border-bottom-left-radius={!borderBottom || !borderLeft ? '0' : undefined}
|
||||
{placeholder}
|
||||
{value}
|
||||
{disabled}
|
||||
oninput={(e: Event & { currentTarget: EventTarget & HTMLTextAreaElement }) => {
|
||||
textBoxValue = e.currentTarget.value;
|
||||
oninput?.(e);
|
||||
}}
|
||||
onchange={(e: Event & { currentTarget: EventTarget & HTMLTextAreaElement }) => {
|
||||
textBoxValue = e.currentTarget.value;
|
||||
onchange?.(e);
|
||||
}}
|
||||
{onblur}
|
||||
{onkeydown}
|
||||
{onfocus}
|
||||
rows={minRows}
|
||||
></textarea>
|
||||
</div>
|
||||
bind:this={measureEl}
|
||||
bind:offsetHeight={measureElHeight}
|
||||
style:line-height={lineHeight}
|
||||
style:min-height={pxToRem(minHeight)}
|
||||
style:max-height={pxToRem(maxHeight)}>{value + '\n'}</pre>
|
||||
<textarea
|
||||
bind:this={textBoxEl}
|
||||
name={id}
|
||||
{id}
|
||||
class="textarea scrollbar {className} text-{fontWeight}"
|
||||
class:disabled
|
||||
class:text-input={!unstyled}
|
||||
class:textarea-unstyled={unstyled}
|
||||
class:hide-scrollbar={measureElHeight < maxHeight}
|
||||
style:height={pxToRem(measureElHeight)}
|
||||
style:font-size={pxToRem(fontSize)}
|
||||
style:border-top-width={borderTop && !borderless ? '1px' : '0'}
|
||||
style:border-right-width={borderRight && !borderless ? '1px' : '0'}
|
||||
style:border-bottom-width={borderBottom && !borderless ? '1px' : '0'}
|
||||
style:border-left-width={borderLeft && !borderless ? '1px' : '0'}
|
||||
style:border-top-right-radius={!borderTop || !borderRight ? '0' : undefined}
|
||||
style:border-top-left-radius={!borderTop || !borderLeft ? '0' : undefined}
|
||||
style:border-bottom-right-radius={!borderBottom || !borderRight ? '0' : undefined}
|
||||
style:border-bottom-left-radius={!borderBottom || !borderLeft ? '0' : undefined}
|
||||
{placeholder}
|
||||
bind:value
|
||||
{disabled}
|
||||
{oninput}
|
||||
{onchange}
|
||||
{onblur}
|
||||
{onkeydown}
|
||||
{onfocus}
|
||||
rows={minRows}
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.textarea-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
overflow-x: hidden;
|
||||
|
||||
/* hide scrollbar */
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
@ -175,30 +182,32 @@
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.textarea-measure-el,
|
||||
.textarea {
|
||||
padding: var(--padding-top) var(--padding-right) var(--padding-bottom) var(--padding-left);
|
||||
line-height: var(--line-height-ratio);
|
||||
width: 100%;
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.textarea-measure-el {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
pointer-events: none;
|
||||
height: fit-content;
|
||||
margin: 0;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.textarea,
|
||||
.textarea-measure-el {
|
||||
.textarea {
|
||||
font-family: var(--base-font-family);
|
||||
line-height: var(--body-line-height);
|
||||
font-weight: var(--base-font-weight);
|
||||
white-space: pre-wrap;
|
||||
cursor: text;
|
||||
resize: none;
|
||||
|
||||
width: 100%;
|
||||
font-size: var(--font-size);
|
||||
|
||||
padding: var(--padding-top) var(--padding-right) var(--padding-bottom) var(--padding-left);
|
||||
overflow-y: auto; /* Enable scrolling when max height is reached */
|
||||
overflow-x: hidden;
|
||||
word-wrap: break-word;
|
||||
transition:
|
||||
border-color var(--transition-fast),
|
||||
background-color var(--transition-fast);
|
||||
@ -227,6 +236,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.text-regular {
|
||||
font-weight: var(--base-font-weight);
|
||||
}
|
||||
|
||||
.textarea-label {
|
||||
color: var(--clr-text-2);
|
||||
}
|
||||
|
@ -1,9 +0,0 @@
|
||||
export function autoHeight(element: HTMLTextAreaElement) {
|
||||
if (!element) return;
|
||||
|
||||
const elementBorder =
|
||||
parseInt(getComputedStyle(element).borderTopWidth) +
|
||||
parseInt(getComputedStyle(element).borderBottomWidth);
|
||||
element.style.height = 'auto';
|
||||
element.style.height = `${element.scrollHeight + elementBorder}px`;
|
||||
}
|
@ -7,13 +7,8 @@
|
||||
let changableValue = $state(props.value);
|
||||
|
||||
function fillTheForm() {
|
||||
changableValue = `## ☕️ Reasoning
|
||||
|
||||
|
||||
## 🧢 Changesd
|
||||
|
||||
|
||||
## 📌 Todos`;
|
||||
console.log('fillTheForm');
|
||||
changableValue = `## ☕️ Reasoning ## 🧢 Chang sdf sdf sdfsdf sdfsfsd ## 📌 Todos`;
|
||||
}
|
||||
|
||||
function handleDescriptionKeyDown(e: KeyboardEvent & { currentTarget: HTMLTextAreaElement }) {
|
||||
@ -34,7 +29,7 @@
|
||||
<div class="wrapper">
|
||||
<Textarea
|
||||
label={props.label}
|
||||
value={changableValue}
|
||||
bind:value={changableValue}
|
||||
placeholder={props.placeholder}
|
||||
minRows={props.minRows}
|
||||
maxRows={props.maxRows}
|
||||
|
Loading…
Reference in New Issue
Block a user