### Major Features - Add comprehensive ORCID validation with checksum verification - Implement unsaved changes detection and auto-save functionality - Enhanced form component reactivity and state management ### ORCID Implementation - Create custom VineJS ORCID validation rule with MOD-11-2 algorithm - Add ORCID fields to Person model and TablePersons component - Update dataset validators to include ORCID validation - Add descriptive placeholder text for ORCID input fields ### UI/UX Improvements - Add UnsavedChangesWarning component with detailed change tracking - Improve FormCheckRadio and FormCheckRadioGroup reactivity - Enhanced BaseButton with proper disabled state handling - Better error handling and user feedback in file validation ### Data Management - Implement sophisticated change detection for all dataset fields - Add proper handling of array ordering for authors/contributors - Improve license selection with better state management - Enhanced subject/keyword processing with duplicate detection ### Technical Improvements - Optimize search indexing with conditional updates based on modification dates - Update person model column mapping for ORCID - Improve validation error messages and user guidance - Better handling of file uploads and deletion tracking ### Dependencies - Update various npm packages (AWS SDK, Babel, Vite, etc.) - Add baseline-browser-mapping for better browser compatibility ### Bug Fixes - Fix form reactivity issues with checkbox/radio groups - Improve error handling in file validation rules - Better handling of edge cases in change detection
165 lines
5.8 KiB
Vue
165 lines
5.8 KiB
Vue
<script setup lang="ts">
|
|
import { computed, watch, ref } from 'vue';
|
|
|
|
interface Props {
|
|
name: string;
|
|
type?: 'checkbox' | 'radio' | 'switch';
|
|
label?: string | null;
|
|
modelValue: Array<any> | string | number | boolean | null;
|
|
inputValue: string | number | boolean;
|
|
}
|
|
|
|
const props = defineProps<Props>();
|
|
|
|
const emit = defineEmits<{ (e: 'update:modelValue', value: Props['modelValue']): void }>();
|
|
|
|
// const computedValue = computed({
|
|
// get: () => props.modelValue,
|
|
// set: (value) => {
|
|
// emit('update:modelValue', props.type === 'radio' ? [value] : value);
|
|
// },
|
|
// });
|
|
const computedValue = computed({
|
|
get: () => {
|
|
if (props.type === 'radio') {
|
|
// For radio buttons, return boolean indicating if this option is selected
|
|
if (Array.isArray(props.modelValue)) {
|
|
return props.modelValue;
|
|
}
|
|
return [props.modelValue];
|
|
} else {
|
|
// For checkboxes, return boolean indicating if this option is included
|
|
if (Array.isArray(props.modelValue)) {
|
|
return props.modelValue.includes(props.inputValue);
|
|
}
|
|
return props.modelValue == props.inputValue;
|
|
}
|
|
},
|
|
set: (value: boolean) => {
|
|
if (props.type === 'radio') {
|
|
// When radio is selected, emit the new value as array
|
|
emit('update:modelValue', [value]);
|
|
} else {
|
|
// Handle checkboxes
|
|
let updatedValue = Array.isArray(props.modelValue) ? [...props.modelValue] : [];
|
|
if (value) {
|
|
if (!updatedValue.includes(props.inputValue)) {
|
|
updatedValue.push(props.inputValue);
|
|
}
|
|
} else {
|
|
updatedValue = updatedValue.filter(item => item != props.inputValue);
|
|
}
|
|
emit('update:modelValue', updatedValue);
|
|
}
|
|
|
|
}
|
|
});
|
|
|
|
const inputType = computed(() => (props.type === 'radio' ? 'radio' : 'checkbox'));
|
|
|
|
// Define isChecked for radio inputs: it's true when the current modelValue equals the inputValue
|
|
// const isChecked = computed(() => {
|
|
// if (Array.isArray(computedValue.value) && computedValue.value.length > 0) {
|
|
// return props.type === 'radio'
|
|
// ? computedValue.value[0] === props.inputValue
|
|
// : computedValue.value.includes(props.inputValue);
|
|
// }
|
|
// return computedValue.value === props.inputValue;
|
|
// });
|
|
// const isChecked = computed(() => {
|
|
// return computedValue.value[0] === props.inputValue;
|
|
// });
|
|
// Fix the isChecked computation with proper type handling
|
|
// const isChecked = computed(() => {
|
|
// if (props.type === 'radio') {
|
|
// // Use loose equality to handle string/number conversion
|
|
// return computedValue.value == props.inputValue;
|
|
// }
|
|
// return computedValue.value === true;
|
|
// });
|
|
|
|
// const isChecked = computed(() => {
|
|
// if (props.type === 'radio') {
|
|
// if (Array.isArray(props.modelValue)) {
|
|
// return props.modelValue.length > 0 && props.modelValue[0] == props.inputValue;
|
|
// }
|
|
// return props.modelValue == props.inputValue;
|
|
// }
|
|
|
|
// // For checkboxes
|
|
// if (Array.isArray(props.modelValue)) {
|
|
// return props.modelValue.includes(props.inputValue);
|
|
// }
|
|
// return props.modelValue == props.inputValue;
|
|
// });
|
|
// Use a ref for isChecked and update it with a watcher
|
|
const isChecked = ref(false);
|
|
// Calculate initial isChecked value
|
|
const calculateIsChecked = () => {
|
|
if (props.type === 'radio') {
|
|
if (Array.isArray(props.modelValue)) {
|
|
return props.modelValue.length > 0 && props.modelValue[0] == props.inputValue;
|
|
}
|
|
return props.modelValue == props.inputValue;
|
|
}
|
|
|
|
// For checkboxes
|
|
if (Array.isArray(props.modelValue)) {
|
|
return props.modelValue.includes(props.inputValue);
|
|
}
|
|
return props.modelValue == props.inputValue;
|
|
};
|
|
|
|
// Set initial value
|
|
isChecked.value = calculateIsChecked();
|
|
|
|
// Watch for changes in modelValue and recalculate isChecked
|
|
watch(
|
|
() => props.modelValue,
|
|
(newValue) => {
|
|
console.log('modelValue changed:', {
|
|
newValue,
|
|
inputValue: props.inputValue,
|
|
type: props.type
|
|
});
|
|
isChecked.value = calculateIsChecked();
|
|
},
|
|
{ immediate: true, deep: true }
|
|
);
|
|
|
|
// Also watch inputValue in case it changes
|
|
watch(
|
|
() => props.inputValue,
|
|
() => {
|
|
isChecked.value = calculateIsChecked();
|
|
}
|
|
);
|
|
</script>
|
|
|
|
<template>
|
|
<label v-if="type === 'radio'" :class="[type]"
|
|
class="mr-6 mb-3 last:mr-0 inline-flex items-center cursor-pointer relative">
|
|
<input
|
|
v-model="computedValue"
|
|
:type="inputType"
|
|
:name="name"
|
|
:value="inputValue"
|
|
class="absolute left-0 opacity-0 -z-1 focus:outline-none focus:ring-0" />
|
|
<span class="check border transition-colors duration-200 dark:bg-slate-800 block w-5 h-5 rounded-full" :class="{
|
|
'border-gray-700': !isChecked,
|
|
'bg-radio-checked bg-no-repeat bg-center bg-lime-600 border-lime-600 border-4': isChecked
|
|
}" />
|
|
<span class="pl-2 control-label">{{ label }}</span>
|
|
</label>
|
|
|
|
<label v-else-if="type === 'checkbox'" :class="[type]"
|
|
class="mr-6 mb-3 last:mr-0 inline-flex items-center cursor-pointer relative">
|
|
<input v-model="computedValue" :type="inputType" :name="name" :value="inputValue"
|
|
class="absolute left-0 opacity-0 -z-1 focus:outline-none focus:ring-0" />
|
|
<span class="check border transition-colors duration-200 dark:bg-slate-800 block w-5 h-5 rounded" :class="{
|
|
'border-gray-700': !isChecked,
|
|
'bg-checkbox-checked bg-no-repeat bg-center bg-lime-600 border-lime-600 border-4': isChecked
|
|
}" />
|
|
<span class="pl-2 control-label">{{ label }}</span>
|
|
</label>
|
|
</template>
|