Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion vue/src/js/components/inputs/Browser.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { makeSelectProps } from '@/hooks/utils/useSelect'
import { makePaginationProps, usePagination } from '@/hooks/utils/usePagination'
import axios from 'axios'
import { cloneDeep, pick, omit, isEmpty } from 'lodash-es'
import { cloneDeep, pick, omit, isEmpty, isObject } from 'lodash-es'

const searchTextFieldRef = ref(null)

Expand Down Expand Up @@ -40,6 +40,10 @@
preserveInitialValues: {
type: Boolean,
default: true
},
lockInitialItems: {
type: Boolean,
default: false
}
})

Expand Down Expand Up @@ -84,6 +88,35 @@

const comboboxModel = ref(input.value)

// Snapshot of the ids that already existed when the form first loaded a value.
// Captured directly from modelValue (independent of fetchInitialItems timing) so
// that pre-existing items can be locked from removal regardless of fetch order.
const lockedIds = ref(null)
const lockObjectKey = computed(() => props.objectIdDefiner ?? props.itemValue)

const captureLockedIds = (val) => {
if (!props.lockInitialItems || lockedIds.value !== null) return

const values = props.multiple ? val : (val != null ? [val] : [])
if (!Array.isArray(values) || values.length === 0) return

// Capture both possible id keys so list items can be matched whichever way they are keyed
lockedIds.value = values.map(v => isObject(v)
? (v[lockObjectKey.value] ?? v[props.itemValue])
: v
)
}

watch(() => props.modelValue, captureLockedIds, { immediate: true, deep: true })

const isLockedItem = (item) => {
if (!props.lockInitialItems || !Array.isArray(lockedIds.value)) return false

const candidate = item[props.itemValue]
// Loose comparison guards against number/string id type mismatches from the API
return lockedIds.value.some(lockedId => lockedId == candidate)
}

// Fetch initial items if modelValue is provided
const fetchInitialItems = async () => {
if (input.value) {
Expand Down Expand Up @@ -318,6 +351,9 @@
}

const isNewSelectedItem = (item) => {
if (isLockedItem(item)) {
return false
}
if(props.multiple) {
return selectedItems.value.some(selectedItem => selectedItem[props.objectIdDefiner ?? props.itemValue] === item[props.itemValue]) && !isInitialItem(item)
} else {
Expand All @@ -326,6 +362,10 @@
}

const itemIsClickable = (item) => {
// Locked items cannot be toggled (existing add-ons can't be removed)
if (isLockedItem(item)) {
return false
}
return canSelectable.value || itemIsSelected(item)
}

Expand All @@ -339,6 +379,12 @@
// return
// }

// Prevent removing existing add-ons when initial items are locked
if (isLockedItem(item)) {
return
}


const objectDefinerKey = props.objectIdDefiner ?? props.itemValue

if (props.multiple) {
Expand Down
Loading