Files
windmill/frontend/src/lib/components/JsonInputs.svelte
T
AlexRV12andClaude Opus 5 fb82f36e6d fix: pre-fill the test panel JSON args editor and align its placeholder (#10871)
* fix: pre-fill the test panel JSON args editor and align its placeholder

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: reseed the JSON args editor when the preprocessor tab is selected

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: seed schema defaults and own-property args in the JSON payload

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: follow the schema in an untouched JSON payload, ignore same-tab clicks

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: latch JSON editor ownership from Monaco, drop the late remounts

The pristine check read the bound `code` value, which trails the buffer by
SimpleEditor's 200ms debounce — a reseed arriving in that window overwrote text
already typed. Latch ownership from Monaco's own change event instead, via a new
undebounced `input` event guarded so `setCode`'s `setValue` does not read as an
edit.

Both `.then(() => argsRender++)` bumps are gone: the arg views now remount at the
tab transition only, and follow the schema in through `initialCode` when
inference resolves, so a remount can no longer land on an in-progress payload.

`FlowPreviewContent.selectInput` overwrote the editor on select but not on
deselect, leaving the abandoned input's payload over reverted args.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 18:51:08 +02:00

123 lines
3.5 KiB
Svelte

<script lang="ts">
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
import { createEventDispatcher, untrack } from 'svelte'
const dispatch = createEventDispatcher()
interface Props {
updateOnBlur?: boolean
placeholder?: string
selected?: boolean
/** Content the editor opens with, and keeps following while the buffer is untouched — so a
* payload nobody has typed into tracks the schema instead of going stale. The first edit
* hands the buffer to the user and later changes stop overwriting it. */
initialCode?: string
}
let {
updateOnBlur = true,
placeholder = 'Write a JSON payload. The input schema will be inferred.<br/><br/>Example:<br/><br/>{<br/>&nbsp;&nbsp;"foo": "12"<br/>}',
selected = false,
initialCode = ''
}: Props = $props()
let pendingJson = $state(untrack(() => initialCode))
// The last content this component wrote, kept only to skip a reseed that would replace the
// buffer with what it already holds — `setValue` resets the cursor and the undo stack.
let seededCode = untrack(() => initialCode)
// Latched from Monaco's own change event, never from `pendingJson`: that trails the buffer by
// SimpleEditor's debounce, a window in which typed text still looks like the seeded payload
// and a reseed lands on top of it.
let userEdited = false
let simpleEditor: SimpleEditor | undefined = $state(undefined)
let focusTrap: HTMLElement | undefined = $state()
$effect(() => {
const next = initialCode
untrack(() => {
if (next !== seededCode && !userEdited) {
seed(next)
}
})
})
// `SimpleEditor.setCode` cancels the change burst its own `setValue` opens, so reseeding
// never dispatches `select` — the payload reaches `args` only when the user edits it.
function seed(code: string) {
seededCode = code
userEdited = false
pendingJson = code
simpleEditor?.setCode(code)
}
function updatePayloadFromJson(jsonInput: string) {
if (jsonInput === undefined || jsonInput === null || jsonInput.trim() === '') {
dispatch('select', undefined)
return
}
try {
const parsed = JSON.parse(jsonInput)
dispatch('select', parsed)
} catch (error) {
dispatch('select', undefined)
}
}
/** Authoritative overwrite: replaces the buffer whether or not it has been typed into, and
* re-establishes it as the content to keep following. */
export function setCode(code: string) {
seed(code)
}
export function resetSelected(dispatchEvent?: boolean) {
if (dispatchEvent) {
dispatch('select', undefined)
}
}
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Escape' && selected) {
focusTrap?.focus()
resetSelected(true)
}
}
</script>
<svelte:window onkeydown={handleKeydown} />
<!-- Add a hidden button that can receive focus -->
<button bind:this={focusTrap} class="sr-only" tabindex="-1" aria-hidden="true">Focus trap</button>
<div class="h-full rounded-md border">
<SimpleEditor
bind:this={simpleEditor}
on:input={() => (userEdited = true)}
on:focus={() => {
if (updateOnBlur) {
dispatch('focus')
updatePayloadFromJson(pendingJson)
}
}}
on:blur={async () => {
if (updateOnBlur) {
dispatch('blur')
}
}}
on:change={(e) => {
if (e.detail?.code !== undefined) {
updatePayloadFromJson(e.detail.code)
}
}}
bind:code={pendingJson}
lang="json"
class="h-full json-inputs-editor"
{placeholder}
/>
</div>
<style>
:global(.json-inputs-editor .monaco-editor .suggest-widget) {
z-index: 200000 !important;
}
</style>