mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-24 08:01:38 +00:00
26050f96c3
* feat: support multiline secrets in resource password fields Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: wire minRows through to password textarea instead of disabling it Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: auto-detect multiline in password field instead of always using textarea Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: switch to textarea on Enter keypress in password field Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: focus textarea after switching from single-line password input Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: simplify Password multiline logic and fix cursor position bug Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
126 lines
3.1 KiB
Svelte
126 lines
3.1 KiB
Svelte
<script lang="ts">
|
|
import { createBubbler } from 'svelte/legacy'
|
|
import { tick } from 'svelte'
|
|
import Button from './common/button/Button.svelte'
|
|
import TextInput from './text_input/TextInput.svelte'
|
|
import { Eye, EyeClosed } from 'lucide-svelte'
|
|
|
|
const bubble = createBubbler()
|
|
interface Props {
|
|
password: string | undefined
|
|
placeholder?: string
|
|
disabled?: boolean
|
|
required?: boolean
|
|
small?: boolean
|
|
minRows?: number
|
|
id?: string
|
|
onKeyDown?: (event: KeyboardEvent) => void
|
|
onBlur?: (event: FocusEvent) => void
|
|
}
|
|
|
|
let {
|
|
password = $bindable(),
|
|
placeholder = '******',
|
|
disabled = false,
|
|
required = false,
|
|
small = false,
|
|
minRows,
|
|
id,
|
|
onKeyDown,
|
|
onBlur
|
|
}: Props = $props()
|
|
|
|
let red = $derived(required && (password == '' || password == undefined))
|
|
let hideValue = $state(true)
|
|
let forceMultiline = $state(false)
|
|
let isMultiline = $derived(
|
|
forceMultiline || (minRows != null && minRows > 1) || (password?.includes('\n') ?? false)
|
|
)
|
|
|
|
let textareaRef: TextInput<'textarea'> | undefined = $state()
|
|
|
|
function insertAndSwitchToMultiline(input: HTMLInputElement, text: string) {
|
|
const start = input.selectionStart
|
|
const end = input.selectionEnd
|
|
if (start != null && end != null) {
|
|
password = (password ?? '').substring(0, start) + text + (password ?? '').substring(end)
|
|
} else {
|
|
// selectionStart/End are null for type="password" inputs
|
|
password = (password ?? '') + text
|
|
}
|
|
forceMultiline = true
|
|
tick().then(() => textareaRef?.focus())
|
|
}
|
|
</script>
|
|
|
|
<div class="relative w-full {small ? 'max-w-lg' : ''}">
|
|
<div class="absolute {isMultiline ? 'top-1' : 'inset-y-0'} right-1 flex items-center z-10">
|
|
<Button
|
|
unifiedSize="sm"
|
|
onClick={() => (hideValue = !hideValue)}
|
|
iconOnly
|
|
startIcon={{ icon: hideValue ? Eye : EyeClosed }}
|
|
variant="subtle"
|
|
wrapperClasses="bg-surface-input"
|
|
/>
|
|
</div>
|
|
{#if isMultiline}
|
|
<TextInput
|
|
bind:this={textareaRef}
|
|
size="md"
|
|
error={red}
|
|
bind:value={password}
|
|
underlyingInputEl="textarea"
|
|
inputProps={{
|
|
id,
|
|
disabled,
|
|
placeholder,
|
|
rows: minRows ?? 3,
|
|
autocomplete: 'new-password',
|
|
onblur: (e) => onBlur?.(e),
|
|
onkeydown: (e) => {
|
|
onKeyDown?.(e)
|
|
bubble('keydown')(e)
|
|
},
|
|
style: hideValue ? '-webkit-text-security: disc' : ''
|
|
}}
|
|
class="pr-8"
|
|
unifiedHeight={false}
|
|
/>
|
|
{:else}
|
|
<TextInput
|
|
size="md"
|
|
error={red}
|
|
bind:value={password}
|
|
inputProps={{
|
|
id,
|
|
disabled,
|
|
placeholder,
|
|
autocomplete: 'new-password',
|
|
onblur: (e) => onBlur?.(e),
|
|
onkeydown: (e) => {
|
|
if (e.key === 'Enter') {
|
|
e.preventDefault()
|
|
insertAndSwitchToMultiline(e.currentTarget as HTMLInputElement, '\n')
|
|
return
|
|
}
|
|
onKeyDown?.(e)
|
|
bubble('keydown')(e)
|
|
},
|
|
onpaste: (e) => {
|
|
const text = e.clipboardData?.getData('text')
|
|
if (text?.includes('\n')) {
|
|
e.preventDefault()
|
|
insertAndSwitchToMultiline(e.currentTarget as HTMLInputElement, text)
|
|
}
|
|
},
|
|
type: hideValue ? 'password' : 'text'
|
|
}}
|
|
class="pr-8"
|
|
/>
|
|
{/if}
|
|
</div>
|
|
{#if red}
|
|
<div class="text-red-600 text-2xs grow">This field is required</div>
|
|
{/if}
|