mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-22 12:19:09 +00:00
fix(editor): Make inline text edit component reactive to prop changes (#17557)
This commit is contained in:
@@ -40,7 +40,7 @@ describe('N8nInlineTextEdit', () => {
|
||||
expect(emittedEvents?.[0]).toEqual(['Updated Value']);
|
||||
});
|
||||
|
||||
it('should not update value on blur if input is empty', async () => {
|
||||
it('should not update value on enter if input is empty', async () => {
|
||||
const wrapper = renderComponent({
|
||||
props: {
|
||||
modelValue: 'Test Value',
|
||||
@@ -52,11 +52,26 @@ describe('N8nInlineTextEdit', () => {
|
||||
const input = wrapper.getByTestId('inline-edit-input');
|
||||
|
||||
await userEvent.clear(input);
|
||||
await userEvent.tab(); // Simulate blur
|
||||
await userEvent.keyboard('{Enter}');
|
||||
|
||||
expect(preview).toHaveTextContent('Test Value');
|
||||
});
|
||||
|
||||
it('should display changes to props.modelValue', async () => {
|
||||
const wrapper = renderComponent({
|
||||
props: {
|
||||
modelValue: 'Test Value',
|
||||
},
|
||||
});
|
||||
const preview = wrapper.getByTestId('inline-edit-preview');
|
||||
|
||||
expect(preview).toHaveTextContent('Test Value');
|
||||
|
||||
await wrapper.rerender({ modelValue: 'New Value!' });
|
||||
|
||||
expect(preview).toHaveTextContent('New Value!');
|
||||
});
|
||||
|
||||
it('should not update on escape key press', async () => {
|
||||
const wrapper = renderComponent({
|
||||
props: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { useElementSize } from '@vueuse/core';
|
||||
import { EditableArea, EditableInput, EditablePreview, EditableRoot } from 'reka-ui';
|
||||
import { computed, ref, useTemplateRef } from 'vue';
|
||||
import { computed, ref, useTemplateRef, watchEffect } from 'vue';
|
||||
|
||||
type Props = {
|
||||
modelValue: string;
|
||||
@@ -25,9 +25,31 @@ const emit = defineEmits<{
|
||||
'update:model-value': [value: string];
|
||||
}>();
|
||||
|
||||
const newValue = ref(props.modelValue);
|
||||
const temp = ref(props.modelValue || props.placeholder);
|
||||
const editableRoot = useTemplateRef('editableRoot');
|
||||
const measureSpan = useTemplateRef('measureSpan');
|
||||
|
||||
// Internal editing value
|
||||
const editingValue = ref(props.modelValue);
|
||||
|
||||
// Content for width calculation
|
||||
const displayContent = computed(() => editingValue.value || props.placeholder);
|
||||
|
||||
// Sync when modelValue prop changes
|
||||
watchEffect(() => {
|
||||
editingValue.value = props.modelValue;
|
||||
});
|
||||
|
||||
// Resize logic
|
||||
const { width: measuredWidth } = useElementSize(measureSpan);
|
||||
const inputWidth = computed(() =>
|
||||
Math.max(props.minWidth, Math.min(measuredWidth.value + 1, props.maxWidth)),
|
||||
);
|
||||
|
||||
const computedInlineStyles = computed(() => ({
|
||||
width: `${inputWidth.value}px`,
|
||||
maxWidth: `${props.maxWidth}px`,
|
||||
zIndex: 1,
|
||||
}));
|
||||
|
||||
function forceFocus() {
|
||||
if (editableRoot.value && !props.readOnly) {
|
||||
@@ -37,54 +59,32 @@ function forceFocus() {
|
||||
|
||||
function forceCancel() {
|
||||
if (editableRoot.value) {
|
||||
newValue.value = props.modelValue;
|
||||
editingValue.value = props.modelValue;
|
||||
editableRoot.value.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
function onSubmit() {
|
||||
if (newValue.value === '') {
|
||||
newValue.value = props.modelValue;
|
||||
temp.value = props.modelValue;
|
||||
const trimmed = editingValue.value.trim();
|
||||
if (!trimmed) {
|
||||
editingValue.value = props.modelValue;
|
||||
return;
|
||||
}
|
||||
if (newValue.value !== props.modelValue) {
|
||||
emit('update:model-value', newValue.value);
|
||||
if (trimmed !== props.modelValue) {
|
||||
emit('update:model-value', trimmed);
|
||||
}
|
||||
}
|
||||
|
||||
function onInput(value: string) {
|
||||
newValue.value = value;
|
||||
editingValue.value = value;
|
||||
}
|
||||
|
||||
function onStateChange(state: string) {
|
||||
if (state === 'cancel') {
|
||||
temp.value = newValue.value;
|
||||
editingValue.value = props.modelValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Resize logic
|
||||
const measureSpan = useTemplateRef('measureSpan');
|
||||
const { width: measuredWidth } = useElementSize(measureSpan);
|
||||
|
||||
const inputWidth = computed(() => {
|
||||
return Math.max(props.minWidth, Math.min(measuredWidth.value + 1, props.maxWidth));
|
||||
});
|
||||
|
||||
function onChange(event: Event) {
|
||||
const { value } = event.target as HTMLInputElement;
|
||||
const processedValue = value.replace(/\s/g, '.');
|
||||
temp.value = processedValue.trim() !== '' ? processedValue : props.placeholder;
|
||||
}
|
||||
|
||||
const computedInlineStyles = computed(() => {
|
||||
return {
|
||||
width: `${inputWidth.value}px`,
|
||||
maxWidth: `${props.maxWidth}px`,
|
||||
zIndex: 1,
|
||||
};
|
||||
});
|
||||
|
||||
defineExpose({ forceFocus, forceCancel });
|
||||
</script>
|
||||
|
||||
@@ -92,10 +92,10 @@ defineExpose({ forceFocus, forceCancel });
|
||||
<EditableRoot
|
||||
ref="editableRoot"
|
||||
:placeholder="placeholder"
|
||||
:model-value="newValue"
|
||||
:model-value="editingValue"
|
||||
submit-mode="both"
|
||||
:class="$style.inlineRenameRoot"
|
||||
:title="modelValue"
|
||||
:title="props.modelValue"
|
||||
:disabled="disabled"
|
||||
:max-length="maxLength"
|
||||
:readonly="readOnly"
|
||||
@@ -111,7 +111,7 @@ defineExpose({ forceFocus, forceCancel });
|
||||
data-test-id="inline-editable-area"
|
||||
>
|
||||
<span ref="measureSpan" :class="$style.measureSpan">
|
||||
{{ temp }}
|
||||
{{ displayContent }}
|
||||
</span>
|
||||
<EditablePreview
|
||||
data-test-id="inline-edit-preview"
|
||||
@@ -123,7 +123,7 @@ defineExpose({ forceFocus, forceCancel });
|
||||
:class="$style.inlineRenameInput"
|
||||
data-test-id="inline-edit-input"
|
||||
:style="computedInlineStyles"
|
||||
@input="onChange"
|
||||
@input="onInput($event.target.value)"
|
||||
/>
|
||||
</EditableArea>
|
||||
</EditableRoot>
|
||||
@@ -185,7 +185,7 @@ defineExpose({ forceFocus, forceCancel });
|
||||
position: absolute;
|
||||
top: 0;
|
||||
visibility: hidden;
|
||||
white-space: nowrap;
|
||||
white-space: pre;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
|
||||
@@ -866,7 +866,8 @@ onBeforeUnmount(() => {
|
||||
|
||||
<style lang="scss" module>
|
||||
.backdrop {
|
||||
position: fixed;
|
||||
position: absolute;
|
||||
z-index: var(--z-index-ndv);
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
@@ -875,9 +876,10 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.dialog {
|
||||
position: fixed;
|
||||
width: calc(100vw - var(--spacing-2xl));
|
||||
height: calc(100vh - var(--spacing-2xl));
|
||||
position: absolute;
|
||||
z-index: var(--z-index-ndv);
|
||||
width: calc(100% - var(--spacing-2xl));
|
||||
height: calc(100% - var(--spacing-2xl));
|
||||
top: var(--spacing-l);
|
||||
left: var(--spacing-l);
|
||||
border: none;
|
||||
|
||||
@@ -777,6 +777,7 @@ export const EXPERIMENTS_TO_TRACK = [
|
||||
RAG_STARTER_WORKFLOW_EXPERIMENT.name,
|
||||
EXTRA_TEMPLATE_LINKS_EXPERIMENT.name,
|
||||
TEMPLATE_ONBOARDING_EXPERIMENT.name,
|
||||
NDV_UI_OVERHAUL_EXPERIMENT.name,
|
||||
];
|
||||
|
||||
export const MFA_FORM = {
|
||||
|
||||
Reference in New Issue
Block a user