diff --git a/packages/inline-repeater-interface/CHANGELOG.md b/packages/inline-repeater-interface/CHANGELOG.md new file mode 100644 index 00000000..a97753de --- /dev/null +++ b/packages/inline-repeater-interface/CHANGELOG.md @@ -0,0 +1,25 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [1.0.1] - 2025-09-23 + +### Improved +- Enhanced focus styling for better accessibility compliance +- Fixed focus ring positioning and conflicts with global CSS rules + +### Fixed +- Fixed expansion state preservation during drag and drop operations +- Expanded items now remain expanded when reordered via drag and drop +- Index-based expansion tracking now properly updates when items are moved + +## [1.0.0] - Initial Release + +### Added +- Initial implementation of inline repeater interface +- Support for inline editing and reordering of repeatable form fields +- Accordion-style expandable items +- Drag and drop functionality for reordering +- Add/remove item controls +- Template-based header rendering +- Integration with Directus form system diff --git a/packages/inline-repeater-interface/package.json b/packages/inline-repeater-interface/package.json index 0ea1220d..1ff3188b 100644 --- a/packages/inline-repeater-interface/package.json +++ b/packages/inline-repeater-interface/package.json @@ -1,7 +1,7 @@ { "name": "@directus-labs/inline-repeater-interface", "type": "module", - "version": "1.0.0", + "version": "1.0.1", "description": "A powerful interface for managing repeatable form fields within Directus that allows inline editing and reordering of items.", "license": "MIT", "keywords": [ @@ -26,18 +26,18 @@ "validate": "directus-extension validate" }, "dependencies": { - "@directus/format-title": "^12.0.0", - "lodash": "^4.17.21", - "nanoid": "^5.0.7", - "reka-ui": "1.0.0-alpha.8", - "sass": "^1.77.8", - "vue": "^3.4.38", - "vue-i18n": "^11.0.1", + "@directus/format-title": "12.1.0", + "lodash": "4.17.21", + "reka-ui": "2.5.0", + "sass": "1.77.8", + "vue": "3.5.18", + "vue-i18n": "11.1.11", "vuedraggable": "4.1.0" }, "devDependencies": { - "@directus/extensions-sdk": "13.0.0", + "@directus/extensions-sdk": "16.0.2", "@directus/types": "^13.0.0", + "@types/lodash": "^4.17.20", "typescript": "^5.5.4" } } diff --git a/packages/inline-repeater-interface/src/list.vue b/packages/inline-repeater-interface/src/list.vue index ad7ec96a..84373c37 100644 --- a/packages/inline-repeater-interface/src/list.vue +++ b/packages/inline-repeater-interface/src/list.vue @@ -111,11 +111,54 @@ const internalValue = computed({ }); const expandedItems = ref([]); +const draggedItemIndex = ref(null); +const isDragging = ref(false); function isExpanded(index: number) { return expandedItems.value.includes(index); } +function onDragStart(evt: any) { + draggedItemIndex.value = evt.oldIndex; + isDragging.value = true; +} + +function onDragEnd(evt: any) { + if (draggedItemIndex.value !== null && evt.newIndex !== evt.oldIndex) { + const oldIndex = draggedItemIndex.value; + const newIndex = evt.newIndex; + + // Update expanded items to reflect the new positions + const updatedExpandedItems = expandedItems.value.map((expandedIndex) => { + // If the dragged item was expanded, update its index to the new position + if (expandedIndex === oldIndex) { + return newIndex; + } + + // Adjust other expanded items that were shifted by the drag + if (oldIndex < newIndex) { + // Item moved down: shift items between oldIndex and newIndex up + if (expandedIndex > oldIndex && expandedIndex <= newIndex) { + return expandedIndex - 1; + } + } + else { + // Item moved up: shift items between newIndex and oldIndex down + if (expandedIndex >= newIndex && expandedIndex < oldIndex) { + return expandedIndex + 1; + } + } + + return expandedIndex; + }); + + expandedItems.value = updatedExpandedItems; + } + + draggedItemIndex.value = null; + isDragging.value = false; +} + const itemToRemove = ref(null); // eslint-disable-next-line unused-imports/no-unused-vars @@ -160,7 +203,7 @@ function addNew() { // Focus the first input of the last form nextTick(() => { const forms = document.querySelectorAll('.list-item-form'); - const lastForm = forms.at(-1); + const lastForm = Array.from(forms).at(-1); const firstInput = lastForm?.querySelector('input, select, textarea'); if (firstInput instanceof HTMLElement) { @@ -231,11 +274,14 @@ function discardAndLeave() { handle=".drag-handle" v-bind="{ 'force-fallback': true }" class="v-list" + :class="{ dragging: isDragging }" + @start="onDragStart" + @end="onDragEnd" @update:model-value="$emit('input', $event)" >