diff --git a/packages/elements-core/src/components/TryIt/Parameters/ParameterEditor.tsx b/packages/elements-core/src/components/TryIt/Parameters/ParameterEditor.tsx index 69488d184..d77e61a21 100644 --- a/packages/elements-core/src/components/TryIt/Parameters/ParameterEditor.tsx +++ b/packages/elements-core/src/components/TryIt/Parameters/ParameterEditor.tsx @@ -3,6 +3,7 @@ import * as React from 'react'; import { useUniqueId } from '../../../hooks/useUniqueId'; import { + decodeSafeSelectorValue, exampleOptions, getPlaceholderForParameter, getPlaceholderForSelectedParameter, @@ -37,6 +38,15 @@ export const ParameterEditor: React.FC = ({ const selectedExample = examples?.find(e => e.value === value) ?? selectExampleOption; const parameterDisplayName = `${parameter.name}${parameter.required ? '*' : ''}`; + // Find the encoded value that matches the current (decoded) value + const encodedValue = React.useMemo(() => { + if (!value || !parameterValueOptions) return value || ''; + const matchingOption = parameterValueOptions.find(opt => { + return String(decodeSafeSelectorValue(opt.value as string | number)) === value; + }); + return matchingOption ? String(matchingOption.value) : value; + }, [value, parameterValueOptions]); + const requiredButEmpty = validate && parameter.required && !value; return ( @@ -51,8 +61,8 @@ export const ParameterEditor: React.FC = ({ flex={1} aria-label={parameter.name} options={parameterValueOptions} - value={value || ''} - onChange={onChange} + value={encodedValue} + onChange={val => onChange && onChange(String(decodeSafeSelectorValue(val as string | number)))} placeholder={getPlaceholderForSelectedParameter(parameter)} /> ) : ( diff --git a/packages/elements-core/src/components/TryIt/Parameters/parameter-utils.ts b/packages/elements-core/src/components/TryIt/Parameters/parameter-utils.ts index 625fb9cfd..8182e5bf7 100644 --- a/packages/elements-core/src/components/TryIt/Parameters/parameter-utils.ts +++ b/packages/elements-core/src/components/TryIt/Parameters/parameter-utils.ts @@ -13,8 +13,66 @@ const booleanOptions = [ { label: 'True', value: 'true' }, ]; +/** + * Encodes a value to be safe for use in CSS selectors (data-key attributes). + * Special characters like quotes, brackets, etc. can break querySelector, + * so we encode them using base64. + */ +export function encodeSafeSelectorValue(value: string | number): string | number { + // Numbers are safe to use as-is + if (typeof value === 'number') { + return value; + } + // Check if the value contains characters that would break CSS selectors + // This includes quotes, brackets, backslashes, etc. + const hasSpecialChars = /["'\[\]\\(){}]/.test(value); + if (!hasSpecialChars) { + return value; + } + // Encode to base64 to make it safe for CSS selectors + // We prefix with 'b64:' so we can decode it later if needed + try { + return 'b64:' + btoa(value); + } catch (e) { + // If btoa fails (e.g., with unicode), fallback to encodeURIComponent + return 'enc:' + encodeURIComponent(value); + } +} + +/** + * Decodes a value that was encoded by encodeSafeSelectorValue + */ +export function decodeSafeSelectorValue(value: string | number): string | number { + if (typeof value === 'number') { + return value; + } + + if (value.startsWith('b64:')) { + try { + return atob(value.substring(4)); + } catch (e) { + return value; + } + } + if (value.startsWith('enc:')) { + try { + return decodeURIComponent(value.substring(4)); + } catch (e) { + return value; + } + } + return value; +} + function enumOptions(enumValues: JSONSchema7Type[], required?: boolean) { - const options = map(enumValues, v => ({ value: typeof v === 'number' ? v : String(v) })); + const options = map(enumValues, v => { + // Handle objects and arrays by stringifying them + const stringValue = + typeof v === 'object' && v !== null ? safeStringify(v) ?? String(v) : typeof v === 'number' ? v : String(v); + // Encode the value to be safe for CSS selectors, but keep the original label + const safeValue = encodeSafeSelectorValue(stringValue); + return { value: safeValue, label: String(stringValue) }; + }); return required ? options : [{ label: 'Not Set', value: '' }, ...options]; } @@ -48,7 +106,10 @@ export function parameterSupportsFileUpload(parameter?: Pick | Omit) { @@ -56,10 +117,6 @@ function exampleValue(example: Omit | Omit { return { value: stringifyValue(defaultValue), isDefault: true }; } - const examples = parameter.examples ?? []; - if (examples.length > 0) { - return { value: exampleValue(examples[0]) }; - } - + // If the parameter has enums, prioritize using the first enum value + // over examples, as examples might not match the enum values const enums = parameter.schema?.enum ?? []; if (enums.length > 0) { return { value: stringifyValue(enums[0]) }; } + const examples = parameter.examples ?? []; + if (examples.length > 0) { + return { value: exampleValue(examples[0]) }; + } + return { value: '' }; }; diff --git a/packages/elements-core/src/components/TryIt/Servers/VariableEditor.tsx b/packages/elements-core/src/components/TryIt/Servers/VariableEditor.tsx index d1206d04b..428eb4648 100644 --- a/packages/elements-core/src/components/TryIt/Servers/VariableEditor.tsx +++ b/packages/elements-core/src/components/TryIt/Servers/VariableEditor.tsx @@ -3,6 +3,7 @@ import * as React from 'react'; import { useUniqueId } from '../../../hooks/useUniqueId'; import { ServerVariable } from '../../../utils/http-spec/IServer'; +import { decodeSafeSelectorValue, encodeSafeSelectorValue } from '../Parameters/parameter-utils'; interface VariableProps { variable: ServerVariable; @@ -14,6 +15,18 @@ interface VariableProps { export const VariableEditor: React.FC = ({ variable, value, onChange }) => { const inputId = useUniqueId(`id_${variable.name}_`); + // Find the encoded value that matches the current (decoded) value + const encodedOptions = React.useMemo( + () => (variable.enum ? variable.enum.map(s => ({ value: encodeSafeSelectorValue(s), label: String(s) })) : []), + [variable.enum], + ); + + const encodedValue = React.useMemo(() => { + if (!value || !variable.enum) return value || variable.default; + const matchingOption = encodedOptions.find(opt => decodeSafeSelectorValue(String(opt.value)) === value); + return matchingOption ? String(matchingOption.value) : value; + }, [value, variable.enum, variable.default, encodedOptions]); + return ( <>