diff --git a/resources/js/bootstrap/globals.js b/resources/js/bootstrap/globals.js index f8e79def259..89f520bf2b3 100644 --- a/resources/js/bootstrap/globals.js +++ b/resources/js/bootstrap/globals.js @@ -157,3 +157,22 @@ export function str_slug(string) { export function snake_case(string) { return Statamic.$slug.separatedBy('_').create(string); } + +export function arrayAdd(items, item, index) { + return [ + ...items.slice(0, index), + item, + ...items.slice(index, items.length) + ] +} + +export function arrayRemove(items, index) { + return [ + ...items.slice(0, index), + ...items.slice(index + 1, items.length) + ] +} + +export function arrayMove(items, oldIndex, newIndex) { + return arrayAdd(arrayRemove(items, oldIndex), items[oldIndex], newIndex); +} \ No newline at end of file diff --git a/resources/js/components/fieldtypes/replicator/Replicator.vue b/resources/js/components/fieldtypes/replicator/Replicator.vue index 9626e8cf9a3..7b53b475508 100644 --- a/resources/js/components/fieldtypes/replicator/Replicator.vue +++ b/resources/js/components/fieldtypes/replicator/Replicator.vue @@ -20,11 +20,17 @@ c.handle === handle) || {}; }, + setConfigHash(handle) { + return this.setConfigHashes[handle]; + }, + updated(index, set) { this.update([...this.value.slice(0, index), set, ...this.value.slice(index + 1)]); }, @@ -194,9 +210,31 @@ export default { this.update([...this.value.slice(0, index), ...this.value.slice(index + 1)]); }, - sorted(value) { - this.update(value); - }, + sorted({ operation, oldIndex, newIndex, oldList }) { + if (operation === 'move') { + // Move set within this replicator + this.update(arrayMove(this.value, oldIndex, newIndex)); + } else if (operation === 'add') { + // Add set to this replicator + const oldReplicator = oldList.owner; + const set = oldReplicator.value[oldIndex]; + const meta = oldReplicator.meta.existing[set._id]; + this.updateSetMeta(set._id, meta); + this.update(arrayAdd(this.value, set, newIndex)); + if (oldReplicator.collapsed.includes(set._id)) { + this.collapseSet(set._id); + } else { + this.expandSet(set._id); + } + // Remove set from old replicator + // Do this from the target replicator in order to avoid race conditions with nested + // replicators both trying to update their parent's value at the same time. + this.$nextTick(() => { + oldReplicator.removeSetMeta(set._id); + oldReplicator.update(arrayRemove(oldReplicator.value, oldIndex)); + }); + } + }, addSet(handle, index) { const set = { @@ -273,6 +311,20 @@ export default { return this.errorsById.hasOwnProperty(id) && this.errorsById[id].length > 0; }, + + sortableGroupValidator({ source }) { + this.canDropSet = this.canAddSet && Object.values(this.setConfigHashes).includes(source.dataset.configHash); + return this.canDropSet; + }, + + sortableGroupStart({ valid }) { + this.canDropSet = valid; + }, + + sortableGroupEnd() { + this.canDropSet = null; + }, + }, mounted() { diff --git a/resources/js/components/fieldtypes/replicator/Set.vue b/resources/js/components/fieldtypes/replicator/Set.vue index 240641ca93a..ca8a537897f 100644 --- a/resources/js/components/fieldtypes/replicator/Set.vue +++ b/resources/js/components/fieldtypes/replicator/Set.vue @@ -25,6 +25,7 @@ const replicatorSets = inject('replicatorSets'); const props = defineProps({ config: Object, + configHash: String, id: String, fieldPath: String, metaPath: String, @@ -120,7 +121,7 @@ function destroy() {