Skip to content

Commit a52f133

Browse files
committed
feat: add move to FieldArray
1 parent a0bf660 commit a52f133

File tree

5 files changed

+88
-2
lines changed

5 files changed

+88
-2
lines changed

packages/vee-validate/src/FieldArray.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const FieldArrayImpl = defineComponent({
1313
},
1414
},
1515
setup(props, ctx) {
16-
const { push, remove, swap, insert, replace, update, prepend, fields } = useFieldArray(toRef(props, 'name'));
16+
const { push, remove, swap, insert, replace, update, prepend, move, fields } = useFieldArray(toRef(props, 'name'));
1717

1818
function slotProps() {
1919
return {
@@ -25,6 +25,7 @@ const FieldArrayImpl = defineComponent({
2525
update,
2626
replace,
2727
prepend,
28+
move,
2829
};
2930
}
3031

@@ -36,6 +37,7 @@ const FieldArrayImpl = defineComponent({
3637
update,
3738
replace,
3839
prepend,
40+
move,
3941
});
4042

4143
return () => {
@@ -55,6 +57,7 @@ export const FieldArray = FieldArrayImpl as typeof FieldArrayImpl & {
5557
update: FieldArrayContext['update'];
5658
replace: FieldArrayContext['replace'];
5759
prepend: FieldArrayContext['prepend'];
60+
move: FieldArrayContext['move'];
5861
$slots: {
5962
default: (arg: UnwrapRef<FieldArrayContext>) => VNode[];
6063
};

packages/vee-validate/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export interface FieldArrayContext<TValue = unknown> {
6666
swap(indexA: number, indexB: number): void;
6767
insert(idx: number, value: TValue): void;
6868
prepend(value: TValue): void;
69+
move(oldIdx: number, newIdx: number): void;
6970
}
7071

7172
export interface PrivateFieldArrayContext {

packages/vee-validate/src/useFieldArray.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export function useFieldArray<TValue = unknown>(arrayPath: MaybeRef<string>): Fi
2121
update: noOp,
2222
replace: noOp,
2323
prepend: noOp,
24+
move: noOp,
2425
};
2526

2627
if (!form) {
@@ -175,6 +176,30 @@ export function useFieldArray<TValue = unknown>(arrayPath: MaybeRef<string>): Fi
175176
updateEntryFlags();
176177
}
177178

179+
function move(oldIdx: number, newIdx: number) {
180+
const pathName = unref(arrayPath);
181+
const pathValue = getFromPath<TValue[]>(form?.values, pathName);
182+
const newValue = isNullOrUndefined(pathValue) ? [] : [...pathValue];
183+
184+
if (!Array.isArray(pathValue) || !(oldIdx in pathValue) || !(newIdx in pathValue)) {
185+
return;
186+
}
187+
188+
const newFields = [...fields.value];
189+
190+
const movedItem = newFields[oldIdx];
191+
newFields.splice(oldIdx, 1);
192+
newFields.splice(newIdx, 0, movedItem);
193+
194+
const movedValue = newValue[oldIdx];
195+
newValue.splice(oldIdx, 1);
196+
newValue.splice(newIdx, 0, movedValue);
197+
198+
form?.setFieldValue(pathName, newValue);
199+
fields.value = newFields;
200+
updateEntryFlags();
201+
}
202+
178203
form.fieldArraysLookup[id] = {
179204
reset: initFields,
180205
};
@@ -192,5 +217,6 @@ export function useFieldArray<TValue = unknown>(arrayPath: MaybeRef<string>): Fi
192217
update,
193218
replace,
194219
prepend,
220+
move,
195221
};
196222
}

packages/vee-validate/tests/FieldArray.spec.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,3 +666,59 @@ test('clears old errors path when last item is removed and value update validati
666666

667667
expect(errorList.children).toHaveLength(0);
668668
});
669+
670+
test('moves items around the array with move()', async () => {
671+
const onSubmit = jest.fn();
672+
mountWithHoc({
673+
setup() {
674+
const initialValues = {
675+
users: [{ name: '1' }, { name: '2' }, { name: '3' }, { name: '4' }],
676+
};
677+
678+
return {
679+
onSubmit,
680+
initialValues,
681+
};
682+
},
683+
template: `
684+
<VForm @submit="onSubmit" :initial-values="initialValues">
685+
<FieldArray name="users" v-slot="{ move, fields }">
686+
<fieldset v-for="(field, idx) in fields" :key="field.key">
687+
<legend>User #{{ idx }}</legend>
688+
<label :for="'name_' + idx">Name</label>
689+
<Field :id="'name_' + idx" :name="'users[' + idx + '].name'" />
690+
<ErrorMessage :name="'users[' + idx + '].name'" />
691+
692+
<button class="move" type="button" @click="move(idx, 0)">Move</button>
693+
</fieldset>
694+
</FieldArray>
695+
696+
<button class="submit" type="submit">Submit</button>
697+
</VForm>
698+
`,
699+
});
700+
701+
await flushPromises();
702+
const submitBtn = document.querySelector('.submit') as HTMLButtonElement;
703+
const inputAt = (idx: number) => (document.querySelectorAll('input') || [])[idx] as HTMLInputElement;
704+
const moveElAt = (idx: number) => (document.querySelectorAll('.move') || [])[idx] as HTMLInputElement;
705+
706+
expect(getValue(inputAt(0))).toBe('1');
707+
expect(getValue(inputAt(1))).toBe('2');
708+
expect(getValue(inputAt(2))).toBe('3');
709+
expect(getValue(inputAt(3))).toBe('4');
710+
dispatchEvent(moveElAt(3), 'click');
711+
await flushPromises();
712+
expect(getValue(inputAt(0))).toBe('4');
713+
expect(getValue(inputAt(1))).toBe('1');
714+
expect(getValue(inputAt(2))).toBe('2');
715+
expect(getValue(inputAt(3))).toBe('3');
716+
(submitBtn as HTMLButtonElement).click();
717+
await flushPromises();
718+
expect(onSubmit).toHaveBeenLastCalledWith(
719+
expect.objectContaining({
720+
users: [{ name: '4' }, { name: '1' }, { name: '2' }, { name: '3' }],
721+
}),
722+
expect.anything()
723+
);
724+
});

packages/vee-validate/tests/helpers/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export function dispatchEvent(node: ComponentPublicInstance | HTMLElement | stri
5959
return;
6060
}
6161

62-
if (HTML_TAGS.includes((node as any).tagName)) {
62+
if ('tagName' in node) {
6363
const input = node as HTMLElement;
6464
input.dispatchEvent(new window.Event(eventName));
6565
return;

0 commit comments

Comments
 (0)