Skip to content

Commit

Permalink
pkp/pkp-lib#10624 Update Autosuggest component
Browse files Browse the repository at this point in the history
  • Loading branch information
blesildaramirez committed Dec 2, 2024
1 parent 7e4ae3c commit 09fe673
Show file tree
Hide file tree
Showing 3 changed files with 219 additions and 53 deletions.
147 changes: 147 additions & 0 deletions src/components/Form/fields/Autosuggest.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
<template>
<span class="-screenReader">{{ selectedLabel }}</span>
<span v-if="!currentSelected.length" class="-screenReader">
{{ t('common.none') }}
</span>
<PkpBadge
v-for="item in currentSelected"
v-else
:key="item.value"
class="pkpAutosuggest__selection"
>
{{ item.label }}
<slot v-if="item.hasSlot" name="input-slot" />
<button
v-if="!isDisabled"
class="pkpAutosuggest__deselect text-negative hover:text-on-dark"
@click.stop.prevent="deselect(item)"
>
<Icon icon="times" />
<span class="-screenReader">
{{ deselectLabel.replace('{$item}', item.label) }}
</span>
</button>
</PkpBadge>
<Combobox
:id="id"
:key="id"
:model-value="null"
class="pkpAutosuggest__autosuggester"
as="div"
@update:model-value="selectSuggestion"
>
<ComboboxInput
ref="autosuggestInput"
class="pkpAutosuggest__input"
v-bind="inputProps"
@change="(event) => handleChange(event, emit)"
@focus="() => handleFocus(emit)"
@blur="() => handleBlur(emit)"
/>
<ComboboxOptions
v-if="suggestions.length || (allowCustom && localInputValue?.length)"
class="autosuggest__results-container autosuggest__results"
>
<ComboboxOption
v-if="
allowCustom &&
localInputValue?.length &&
!suggestions.includes(localInputValue)
"
v-slot="{active}"
as="template"
>
<li
class="autosuggest__results-item"
:class="active && 'autosuggest__results-item--highlighted'"
>
{{ localInputValue }}
</li>
</ComboboxOption>
<ComboboxOption
v-for="suggestion in suggestions"
:key="suggestion.value"
v-slot="{active}"
:value="suggestion"
as="template"
>
<li
class="autosuggest__results-item flex items-center"
:class="active && 'autosuggest__results-item--highlighted'"
>
<slot v-if="slots['option']" name="option" :suggestion="suggestion" />
<span v-else>{{ suggestion.label }}</span>
</li>
</ComboboxOption>
</ComboboxOptions>
</Combobox>
</template>
<script setup>
import {useSlots} from 'vue';
import {
Combobox,
ComboboxInput,
ComboboxOption,
ComboboxOptions,
} from '@headlessui/vue';
import PkpBadge from '@/components/Badge/Badge.vue';
import Icon from '@/components/Icon/Icon.vue';
import {useAutosuggest} from '@/composables/useAutosuggest';
const slots = useSlots();
defineProps({
id: {
type: String,
required: true,
},
inputProps: {
type: Object,
required: true,
},
suggestions: {
type: Array,
default: () => [],
},
selectedLabel: {
type: String,
required: true,
},
currentValue: {
type: Array,
default: () => [],
},
currentSelected: {
type: Array,
default: () => [],
},
isDisabled: {
type: Boolean,
default() {
return false;
},
},
deselectLabel: {
type: String,
required: true,
},
});
const emit = defineEmits([
'update:inputValue',
'update:isFocused',
'select-suggestion',
'deselect',
]);
const {allowCustom, localInputValue, handleChange, handleFocus, handleBlur} =
useAutosuggest();
function selectSuggestion(suggestion) {
emit('select-suggestion', suggestion);
}
function deselect(item) {
emit('deselect', item);
}
</script>
93 changes: 41 additions & 52 deletions src/components/Form/fields/FieldBaseAutosuggest.vue
Original file line number Diff line number Diff line change
Expand Up @@ -80,43 +80,22 @@
/>
</div>
<!-- End Heading part repeated for inline scenario -->
<span class="-screenReader">{{ selectedLabel }}</span>
<span v-if="!currentValue.length" class="-screenReader">
{{ t('common.none') }}
</span>
<PkpBadge
v-for="item in currentSelected"
v-else
:key="item.value"
class="pkpAutosuggest__selection"
>
{{ item.label }}
<slot v-if="item.hasSlot" name="input-slot" />
<button
v-if="!isDisabled"
class="pkpAutosuggest__deselect text-negative hover:text-on-dark"
@click.stop.prevent="deselect(item)"
>
<Icon icon="Cancel" class="h-3 w-3" />
<span class="-screenReader">
{{ deselectLabel.replace('{$item}', item.label) }}
</span>
</button>
</PkpBadge>
<FieldComboBox
<Autosuggest
v-if="!isDisabled"
:id="autosuggestId"
v-bind="autoSuggestProps"
ref="cb"
v-model:inputValue="inputValue"
v-model:isFocused="isFocused"
:input-props="inputProps"
:suggestions="suggestions"
@update:model-value="selectSuggestion"
@select-suggestion="selectSuggestion"
@deselect="deselect"
>
<template v-if="$slots['input-slot']" #input-slot>
<slot name="input-slot"></slot>
</template>
<template v-if="$slots.option" #option="{suggestion}">
<slot name="option" :suggestion="suggestion"></slot>
</template>
</FieldComboBox>
</Autosuggest>
<span class="pkpAutosuggest__endslot">
<slot name="end"></slot>
</span>
Expand All @@ -138,29 +117,26 @@

<script>
import FieldBase from './FieldBase.vue';
import FieldComboBox from './FieldComboBox.vue';
import PkpBadge from '@/components/Badge/Badge.vue';
import Autosuggest from './Autosuggest.vue';
import FormFieldLabel from '@/components/Form/FormFieldLabel.vue';
import Tooltip from '@/components/Tooltip/Tooltip.vue';
import HelpButton from '@/components/HelpButton/HelpButton.vue';
import FieldError from '@/components/Form/FieldError.vue';
import MultilingualProgress from '@/components/MultilingualProgress/MultilingualProgress.vue';
import Icon from '@/components/Icon/Icon.vue';
import {useAutosuggest} from '@/composables/useAutosuggest';
import ajaxError from '@/mixins/ajaxError';
import debounce from 'debounce';
export default {
name: 'FieldBaseAutosuggest',
components: {
PkpBadge,
FormFieldLabel,
Tooltip,
HelpButton,
FieldError,
Icon,
MultilingualProgress,
FieldComboBox,
Autosuggest,
},
extends: FieldBase,
mixins: [ajaxError],
Expand Down Expand Up @@ -292,6 +268,36 @@ export default {
var direction = document.body.getAttribute('dir');
return direction === 'rtl';
},
autoSuggestProps() {
const {autoSuggestProps} = useAutosuggest(
this.inputProps,
this.autosuggestId,
this.suggestions,
this.selectedLabel,
this.currentValue,
this.currentSelected,
this.isDisabled,
this.deselectLabel,
);
return autoSuggestProps;
},
},
watch: {
inputValue(newVal, oldVal) {
if (newVal === oldVal) {
return;
}
this.getSuggestions();
},
},
mounted() {
// Inline labels can not be used with multilingual fields
if (this.isMultilingual && this.isLabelInline) {
throw new Error(
'An inline label can not be used with a multilingual autosuggest field. This error encountered in the field ' +
this.name,
);
}
},
methods: {
/**
Expand Down Expand Up @@ -407,23 +413,6 @@ export default {
);
},
},
watch: {
inputValue(newVal, oldVal) {
if (newVal === oldVal) {
return;
}
this.getSuggestions();
},
},
mounted() {
// Inline labels can not be used with multilingual fields
if (this.isMultilingual && this.isLabelInline) {
throw new Error(
'An inline label can not be used with a multilingual autosuggest field. This error encountered in the field ' +
this.name,
);
}
},
};
</script>
Expand Down
32 changes: 31 additions & 1 deletion src/composables/useAutosuggest.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
import {ref, inject} from 'vue';

export function useAutosuggest(emit) {
export function useAutosuggest(
_inputProps = {},
_id = '',
_suggestions = [],
_selectedLabel = '',
_currentValue = '',
_currentSelected = [],
_isDisabled = false,
_deselectLabel = '',
) {
const allowCustom = inject('allowCustom', false);
const localInputValue = ref('');
const isFocused = ref(false);

const inputProps = ref(_inputProps);
const id = ref(_id);
const suggestions = ref(_suggestions);
const selectedLabel = ref(_selectedLabel);
const currentValue = ref(_currentValue);
const currentSelected = ref(_currentSelected);
const isDisabled = ref(_isDisabled);
const deselectLabel = ref(_deselectLabel);

function handleChange(event, emit) {
localInputValue.value = event.target.value.trim();
emit('update:inputValue', localInputValue.value);
Expand All @@ -20,12 +38,24 @@ export function useAutosuggest(emit) {
emit('update:isFocused', isFocused.value);
}

const autoSuggestProps = {
inputProps: inputProps.value,
id: id.value,
suggestions: suggestions.value,
selectedLabel: selectedLabel.value,
currentValue: currentValue.value,
currentSelected: currentSelected.value,
isDisabled: isDisabled.value,
deselectLabel: deselectLabel.value,
};

return {
allowCustom,
localInputValue,
isFocused,
handleChange,
handleFocus,
handleBlur,
autoSuggestProps,
};
}

0 comments on commit 09fe673

Please sign in to comment.