diff --git a/Tithe-Vue/src/components/AddressForm.vue b/Tithe-Vue/src/components/AddressForm.vue
index e333c2b..17cf0bc 100644
--- a/Tithe-Vue/src/components/AddressForm.vue
+++ b/Tithe-Vue/src/components/AddressForm.vue
@@ -20,6 +20,7 @@ import {
import FormField from "@/components/FormField.vue";
import SearchBox from "@/components/SearchBox.vue";
+import SingleSelectBox from "@/components/SearchBoxes/SingleSelectBox.vue";
const emit = defineEmits(["addressFormChange"]);
@@ -54,21 +55,50 @@ const {
})
);
-const loadStreets = (query, setOptions) => {
- streetListQueryEnabled.value = true;
- similarStreetListVariables.value = {
- streetName: query,
- };
- similarStreetListOnResult((queryResult) => {
- setOptions(
- queryResult.data?.getSimilarStreets?.map((entity) => {
- return {
- id: entity.streetId,
- label: entity.streetName,
- };
- }) ?? []
- );
- });
+// const loadStreets = (query, setOptions) => {
+// streetListQueryEnabled.value = true;
+// similarStreetListVariables.value = {
+// streetName: query,
+// };
+// similarStreetListOnResult((queryResult) => {
+// setOptions(
+// queryResult.data?.getSimilarStreets?.map((entity) => {
+// return {
+// id: entity.streetId,
+// label: entity.streetName,
+// };
+// }) ?? []
+// );
+// });
+// };
+
+const streetOptions = ref({});
+
+const streetSearchChange = (query) => {
+ if (query != "") {
+ streetListQueryEnabled.value = true;
+ similarStreetListVariables.value = {
+ streetName: query,
+ };
+
+ similarStreetListOnResult((queryResult) => {
+ streetOptions.value =
+ queryResult.data?.getSimilarStreets?.map((entity) => {
+ return {
+ id: entity.streetId,
+ label: entity.streetName,
+ value: {
+ id: entity.streetId,
+ label: entity.streetName,
+ },
+ };
+ }) ?? [];
+ });
+ }
+};
+
+const changeInStreet = (entity) => {
+ street.value = entity;
};
watch(street, (value) => {
@@ -123,21 +153,50 @@ const {
})
);
-const loadCities = (query, setOptions) => {
- cityListQueryEnabled.value = true;
- similarCityListVariables.value = {
- cityName: query,
- };
- similarCityListOnResult((queryResult) => {
- setOptions(
- queryResult.data?.getSimilarCities?.map((entity) => {
- return {
- id: entity.cityId,
- label: entity.cityName,
- };
- }) ?? []
- );
- });
+// const loadCities = (query, setOptions) => {
+// cityListQueryEnabled.value = true;
+// similarCityListVariables.value = {
+// cityName: query,
+// };
+// similarCityListOnResult((queryResult) => {
+// setOptions(
+// queryResult.data?.getSimilarCities?.map((entity) => {
+// return {
+// id: entity.cityId,
+// label: entity.cityName,
+// };
+// }) ?? []
+// );
+// });
+// };
+
+const cityOptions = ref({});
+
+const citySearchChange = (query) => {
+ if (query != "") {
+ cityListQueryEnabled.value = true;
+ similarCityListVariables.value = {
+ cityName: query,
+ };
+
+ similarCityListOnResult((queryResult) => {
+ cityOptions.value =
+ queryResult.data?.getSimilarCities?.map((entity) => {
+ return {
+ id: entity.cityId,
+ label: entity.cityName,
+ value: {
+ id: entity.cityId,
+ label: entity.cityName,
+ },
+ };
+ }) ?? [];
+ });
+ }
+};
+
+const changeInCity = (entity) => {
+ city.value = entity;
};
watch(city, (value) => {
@@ -192,21 +251,33 @@ const {
})
);
-const loadDistricts = (query, setOptions) => {
- districtListQueryEnabled.value = true;
- similarDistrictListVariables.value = {
- districtName: query,
- };
- similarDistrictListOnResult((queryResult) => {
- setOptions(
- queryResult.data?.getSimilarDistricts?.map((entity) => {
- return {
- id: entity.districtId,
- label: entity.districtName,
- };
- }) ?? []
- );
- });
+const districtOptions = ref({});
+
+const districtSearchChange = (query) => {
+ if (query != "") {
+ districtListQueryEnabled.value = true;
+ similarDistrictListVariables.value = {
+ districtName: query,
+ };
+
+ similarDistrictListOnResult((queryResult) => {
+ districtOptions.value =
+ queryResult.data?.getSimilarDistricts?.map((entity) => {
+ return {
+ id: entity.districtId,
+ label: entity.districtName,
+ value: {
+ id: entity.districtId,
+ label: entity.districtName,
+ },
+ };
+ }) ?? [];
+ });
+ }
+};
+
+const changeInDistrict = (entity) => {
+ district.value = entity;
};
watch(district, (value) => {
@@ -261,21 +332,33 @@ const {
})
);
-const loadStates = (query, setOptions) => {
- stateListQueryEnabled.value = true;
- similarStateListVariables.value = {
- stateName: query,
- };
- similarStateListOnResult((queryResult) => {
- setOptions(
- queryResult.data?.getSimilarStates?.map((entity) => {
- return {
- id: entity.stateId,
- label: entity.stateName,
- };
- }) ?? []
- );
- });
+const stateOptions = ref({});
+
+const stateSearchChange = (query) => {
+ if (query != "") {
+ stateListQueryEnabled.value = true;
+ similarStateListVariables.value = {
+ stateName: query,
+ };
+
+ similarStateListOnResult((queryResult) => {
+ stateOptions.value =
+ queryResult.data?.getSimilarStates?.map((entity) => {
+ return {
+ id: entity.stateId,
+ label: entity.stateName,
+ value: {
+ id: entity.stateId,
+ label: entity.stateName,
+ },
+ };
+ }) ?? [];
+ });
+ }
+};
+
+const changeInState = (entity) => {
+ state.value = entity;
};
watch(state, (value) => {
@@ -330,21 +413,33 @@ const {
})
);
-const loadPincodes = (query, setOptions) => {
- pincodeListQueryEnabled.value = true;
- similarPincodeListVariables.value = {
- pincode: query,
- };
- similarPincodeListOnResult((queryResult) => {
- setOptions(
- queryResult.data?.getSimilarPincodes?.map((entity) => {
- return {
- id: entity.pincodeId,
- label: entity.pincode,
- };
- }) ?? []
- );
- });
+const pincodeOptions = ref({});
+
+const pincodeSearchChange = (query) => {
+ if (query != "") {
+ pincodeListQueryEnabled.value = true;
+ similarPincodeListVariables.value = {
+ pincode: query,
+ };
+
+ similarPincodeListOnResult((queryResult) => {
+ pincodeOptions.value =
+ queryResult.data?.getSimilarPincodes?.map((entity) => {
+ return {
+ id: entity.pincodeId,
+ label: entity.pincode,
+ value: {
+ id: entity.pincodeId,
+ label: entity.pincode,
+ },
+ };
+ }) ?? [];
+ });
+ }
+};
+
+const changeInPincode = (entity) => {
+ pincode.value = entity;
};
watch(pincode, (value) => {
@@ -392,7 +487,7 @@ defineExpose({
-
+
+
+
+
-
-
-
-
+
+
diff --git a/Tithe-Vue/src/components/MultiSelectBox/Multiselect.vue b/Tithe-Vue/src/components/MultiSelectBox/Multiselect.vue
new file mode 100644
index 0000000..bec7d03
--- /dev/null
+++ b/Tithe-Vue/src/components/MultiSelectBox/Multiselect.vue
@@ -0,0 +1,728 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ localize(option[label]) }}
+
+
+
+
+
+
+
+
+ {{ search }}
+
+
+
+
+
+
+
+
+
+
+
+ {{
+ localize(iv[label])
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ placeholder }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+ {{ localize(option[label]) }}
+
+
+
+
+
+
+ -
+
+ {{ localize(option[label]) }}
+ {{ Object.values(option?.meta ?? {}).join(",") }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ ariaLabel }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tithe-Vue/src/components/MultiSelectBox/composables/useA11y.js b/Tithe-Vue/src/components/MultiSelectBox/composables/useA11y.js
new file mode 100644
index 0000000..01272c2
--- /dev/null
+++ b/Tithe-Vue/src/components/MultiSelectBox/composables/useA11y.js
@@ -0,0 +1,182 @@
+import { toRefs, onMounted, ref, computed } from 'vue'
+
+export default function useA11y (props, context, dep)
+{
+ const {
+ placeholder, id, valueProp, label: labelProp, mode, groupLabel, aria, searchable ,
+ } = toRefs(props)
+
+ // ============ DEPENDENCIES ============
+
+ const pointer = dep.pointer
+ const iv = dep.iv
+ const hasSelected = dep.hasSelected
+ const multipleLabelText = dep.multipleLabelText
+
+ // ================ DATA ================
+
+ const label = ref(null)
+
+ // ============== COMPUTED ==============
+
+ const ariaAssist = computed(() => {
+ let texts = []
+
+ if (id && id.value) {
+ texts.push(id.value)
+ }
+
+ texts.push('assist')
+
+ return texts.join('-')
+ })
+
+ const ariaControls = computed(() => {
+ let texts = []
+
+ if (id && id.value) {
+ texts.push(id.value)
+ }
+
+ texts.push('multiselect-options')
+
+ return texts.join('-')
+ })
+
+ const ariaActiveDescendant = computed(() => {
+ let texts = []
+
+ if (id && id.value) {
+ texts.push(id.value)
+ }
+
+ if (pointer.value) {
+ texts.push(pointer.value.group ? 'multiselect-group' : 'multiselect-option')
+
+ texts.push(pointer.value.group ? pointer.value.index : pointer.value[valueProp.value])
+
+ return texts.join('-')
+ }
+ })
+
+
+
+ const ariaPlaceholder = computed(() => {
+ return placeholder.value
+ })
+
+ const ariaMultiselectable = computed(() => {
+ return mode.value !== 'single'
+ })
+
+ const ariaLabel = computed(() => {
+ let ariaLabel = ''
+
+ if (mode.value === 'single' && hasSelected.value) {
+ ariaLabel += iv.value[labelProp.value]
+ }
+
+ if (mode.value === 'multiple' && hasSelected.value) {
+ ariaLabel += multipleLabelText.value
+ }
+
+ if (mode.value === 'tags' && hasSelected.value) {
+ ariaLabel += iv.value.map(v => v[labelProp.value]).join(', ')
+ }
+
+ return ariaLabel
+ })
+
+ const arias = computed(() => {
+ let arias = { ...aria.value }
+
+ // Need to add manually because focusing
+ // the input won't read the selected value
+ if (searchable.value) {
+ arias['aria-labelledby'] = arias['aria-labelledby']
+ ? `${ariaAssist.value} ${arias['aria-labelledby']}`
+ : ariaAssist.value
+
+ if (ariaLabel.value && arias['aria-label']) {
+ arias['aria-label'] = `${ariaLabel.value}, ${arias['aria-label']}`
+ }
+ }
+
+ return arias
+ })
+
+ // =============== METHODS ==============
+
+ const ariaOptionId = (option) => {
+ let texts = []
+
+ if (id && id.value) {
+ texts.push(id.value)
+ }
+
+ texts.push('multiselect-option')
+
+ texts.push(option[valueProp.value])
+
+ return texts.join('-')
+ }
+
+ const ariaGroupId = (option) => {
+ let texts = []
+
+ if (id && id.value) {
+ texts.push(id.value)
+ }
+
+ texts.push('multiselect-group')
+
+ texts.push(option.index)
+
+ return texts.join('-')
+ }
+
+ const ariaOptionLabel = (label) => {
+ let texts = []
+
+ texts.push(label)
+
+ return texts.join(' ')
+ }
+
+ const ariaGroupLabel = (label) => {
+ let texts = []
+
+ texts.push(label)
+
+ return texts.join(' ')
+ }
+
+ const ariaTagLabel = (label) => {
+ return `${label} ❎`
+ }
+
+ // =============== HOOKS ================
+
+ onMounted(() => {
+ /* istanbul ignore next */
+ if (id && id.value && document && document.querySelector) {
+ let forTag = document.querySelector(`[for="${id.value}"]`)
+ label.value = forTag ? forTag.innerText : null
+ }
+ })
+
+ return {
+ arias,
+ ariaLabel,
+ ariaAssist,
+ ariaControls,
+ ariaPlaceholder,
+ ariaMultiselectable,
+ ariaActiveDescendant,
+ ariaOptionId,
+ ariaOptionLabel,
+ ariaGroupId,
+ ariaGroupLabel,
+ ariaTagLabel,
+ }
+}
\ No newline at end of file
diff --git a/Tithe-Vue/src/components/MultiSelectBox/composables/useClasses.js b/Tithe-Vue/src/components/MultiSelectBox/composables/useClasses.js
new file mode 100644
index 0000000..46eb320
--- /dev/null
+++ b/Tithe-Vue/src/components/MultiSelectBox/composables/useClasses.js
@@ -0,0 +1,160 @@
+import { computed, toRefs } from 'vue'
+
+export default function useClasses (props, context, dependencies)
+{const {
+ classes: classes_, disabled, openDirection, showOptions
+ } = toRefs(props)
+
+ // ============ DEPENDENCIES ============
+
+ const isOpen = dependencies.isOpen
+ const isPointed = dependencies.isPointed
+ const isSelected = dependencies.isSelected
+ const isDisabled = dependencies.isDisabled
+ const isActive = dependencies.isActive
+ const canPointGroups = dependencies.canPointGroups
+ const resolving = dependencies.resolving
+ const fo = dependencies.fo
+
+ const classes = computed(() => ({
+ container: 'multiselect',
+ containerDisabled: 'is-disabled',
+ containerOpen: 'is-open',
+ containerOpenTop: 'is-open-top',
+ containerActive: 'is-active',
+ wrapper: 'multiselect-wrapper',
+ singleLabel: 'multiselect-single-label',
+ singleLabelText: 'multiselect-single-label-text',
+ multipleLabel: 'multiselect-multiple-label',
+ search: 'multiselect-search',
+ tags: 'multiselect-tags',
+ tag: 'multiselect-tag',
+ tagDisabled: 'is-disabled',
+ tagRemove: 'multiselect-tag-remove',
+ tagRemoveIcon: 'multiselect-tag-remove-icon',
+ tagsSearchWrapper: 'multiselect-tags-search-wrapper',
+ tagsSearch: 'multiselect-tags-search',
+ tagsSearchCopy: 'multiselect-tags-search-copy',
+ placeholder: 'multiselect-placeholder',
+ caret: 'multiselect-caret',
+ caretOpen: 'is-open',
+ clear: 'multiselect-clear',
+ clearIcon: 'multiselect-clear-icon',
+ spinner: 'multiselect-spinner',
+ inifinite: 'multiselect-inifite',
+ inifiniteSpinner: 'multiselect-inifite-spinner',
+ dropdown: 'multiselect-dropdown',
+ dropdownTop: 'is-top',
+ dropdownHidden: 'is-hidden',
+ options: 'multiselect-options',
+ optionsTop: 'is-top',
+ group: 'multiselect-group',
+ groupLabel: 'multiselect-group-label',
+ groupLabelPointable: 'is-pointable',
+ groupLabelPointed: 'is-pointed',
+ groupLabelSelected: 'is-selected',
+ groupLabelDisabled: 'is-disabled',
+ groupLabelSelectedPointed: 'is-selected is-pointed',
+ groupLabelSelectedDisabled: 'is-selected is-disabled',
+ groupOptions: 'multiselect-group-options',
+ option: 'multiselect-option',
+ optionPointed: 'is-pointed',
+ optionSelected: 'is-selected',
+ optionDisabled: 'is-disabled',
+ optionSelectedPointed: 'is-selected is-pointed',
+ optionSelectedDisabled: 'is-selected is-disabled',
+ noOptions: 'multiselect-no-options',
+ noResults: 'multiselect-no-results',
+ fakeInput: 'multiselect-fake-input',
+ assist: 'multiselect-assistive-text',
+ spacer: 'multiselect-spacer',
+ ...classes_.value,
+ }))
+
+ // ============== COMPUTED ==============
+
+ const showDropdown = computed(() => {
+ return !!(isOpen.value && showOptions.value && (!resolving.value || (resolving.value && fo.value.length)))
+ })
+
+ const classList = computed(() => {
+ const c = classes.value
+
+ return {
+ container: [c.container]
+ .concat(disabled.value ? c.containerDisabled : [])
+ .concat(showDropdown.value && openDirection.value === 'top' ? c.containerOpenTop : [])
+ .concat(showDropdown.value && openDirection.value !== 'top' ? c.containerOpen : [])
+ .concat(isActive.value ? c.containerActive : []),
+ wrapper: c.wrapper,
+ spacer: c.spacer,
+ singleLabel: c.singleLabel,
+ singleLabelText: c.singleLabelText,
+ multipleLabel: c.multipleLabel,
+ search: c.search,
+ tags: c.tags,
+ tag: [c.tag]
+ .concat(disabled.value ? c.tagDisabled : []),
+ tagDisabled: c.tagDisabled,
+ tagRemove: c.tagRemove,
+ tagRemoveIcon: c.tagRemoveIcon,
+ tagsSearchWrapper: c.tagsSearchWrapper,
+ tagsSearch: c.tagsSearch,
+ tagsSearchCopy: c.tagsSearchCopy,
+ placeholder: c.placeholder,
+ caret: [c.caret]
+ .concat(isOpen.value ? c.caretOpen : []),
+ clear: c.clear,
+ clearIcon: c.clearIcon,
+ spinner: c.spinner,
+ inifinite: c.inifinite,
+ inifiniteSpinner: c.inifiniteSpinner,
+ dropdown: [c.dropdown]
+ .concat(openDirection.value === 'top' ? c.dropdownTop : [])
+ .concat(!isOpen.value || !showOptions.value || !showDropdown.value ? c.dropdownHidden : []),
+ options: [c.options]
+ .concat(openDirection.value === 'top' ? c.optionsTop : []),
+ group: c.group,
+ groupLabel: (g) => {
+ let groupLabel = [c.groupLabel]
+
+ if (isPointed(g)) {
+ groupLabel.push(isSelected(g) ? c.groupLabelSelectedPointed : c.groupLabelPointed)
+ } else if (isSelected(g) && canPointGroups.value) {
+ groupLabel.push(isDisabled(g) ? c.groupLabelSelectedDisabled : c.groupLabelSelected)
+ } else if (isDisabled(g)) {
+ groupLabel.push(c.groupLabelDisabled)
+ }
+
+ if (canPointGroups.value) {
+ groupLabel.push(c.groupLabelPointable)
+ }
+
+ return groupLabel
+ },
+ groupOptions: c.groupOptions,
+ option: (o, g) => {
+ let option = [c.option]
+
+ if (isPointed(o)) {
+ option.push(isSelected(o) ? c.optionSelectedPointed : c.optionPointed)
+ } else if (isSelected(o)) {
+ option.push(isDisabled(o) ? c.optionSelectedDisabled : c.optionSelected)
+ } else if (isDisabled(o) || (g && isDisabled(g))) {
+ option.push(c.optionDisabled)
+ }
+
+ return option
+ },
+ noOptions: c.noOptions,
+ noResults: c.noResults,
+ assist: c.assist,
+ fakeInput: c.fakeInput,
+ }
+ })
+
+ return {
+ classList,
+ showDropdown,
+ }
+}
\ No newline at end of file
diff --git a/Tithe-Vue/src/components/MultiSelectBox/composables/useData.js b/Tithe-Vue/src/components/MultiSelectBox/composables/useData.js
new file mode 100644
index 0000000..0ac50bd
--- /dev/null
+++ b/Tithe-Vue/src/components/MultiSelectBox/composables/useData.js
@@ -0,0 +1,62 @@
+import { toRefs, getCurrentInstance } from 'vue'
+import isNullish from '../utils/isNullish'
+
+export default function useData (props, context, dep)
+{
+ const { object, valueProp, mode } = toRefs(props)
+
+ const $this = getCurrentInstance().proxy
+
+ // ============ DEPENDENCIES ============
+
+ const iv = dep.iv
+
+ // =============== METHODS ==============
+
+ const update = (val, triggerInput = true) => {
+ // Setting object(s) as internal value
+ iv.value = makeInternal(val)
+
+ // Setting object(s) or plain value as external
+ // value based on `option` setting
+ const externalVal = makeExternal(val)
+
+ context.emit('change', externalVal, $this)
+
+ if (triggerInput) {
+ context.emit('input', externalVal)
+ context.emit('update:modelValue', externalVal)
+ }
+ }
+
+ // no export
+ const makeExternal = (val) => {
+ // If external value should be object
+ // no transformation is required
+ if (object.value) {
+ return val
+ }
+
+ // No need to transform if empty value
+ if (isNullish(val)) {
+ return val
+ }
+
+ // If external should be plain transform
+ // value object to plain values
+ return !Array.isArray(val) ? val[valueProp.value] : val.map(v => v[valueProp.value])
+ }
+
+ // no export
+ const makeInternal = (val) => {
+ if (isNullish(val)) {
+ return mode.value === 'single' ? {} : []
+ }
+
+ return val
+ }
+
+ return {
+ update,
+ }
+}
\ No newline at end of file
diff --git a/Tithe-Vue/src/components/MultiSelectBox/composables/useDropdown.js b/Tithe-Vue/src/components/MultiSelectBox/composables/useDropdown.js
new file mode 100644
index 0000000..2030f63
--- /dev/null
+++ b/Tithe-Vue/src/components/MultiSelectBox/composables/useDropdown.js
@@ -0,0 +1,38 @@
+import { ref, toRefs, getCurrentInstance } from 'vue'
+
+export default function useDropdown (props, context, dep)
+{
+ const { disabled } = toRefs(props)
+
+ const $this = getCurrentInstance().proxy
+
+ // ================ DATA ================
+
+ const isOpen = ref(false)
+
+ // =============== METHODS ==============
+
+ const open = () => {
+ if (isOpen.value || disabled.value) {
+ return
+ }
+
+ isOpen.value = true
+ context.emit('open', $this)
+ }
+
+ const close = () => {
+ if (!isOpen.value) {
+ return
+ }
+
+ isOpen.value = false
+ context.emit('close', $this)
+ }
+
+ return {
+ isOpen,
+ open,
+ close,
+ }
+}
\ No newline at end of file
diff --git a/Tithe-Vue/src/components/MultiSelectBox/composables/useI18n.js b/Tithe-Vue/src/components/MultiSelectBox/composables/useI18n.js
new file mode 100644
index 0000000..796df86
--- /dev/null
+++ b/Tithe-Vue/src/components/MultiSelectBox/composables/useI18n.js
@@ -0,0 +1,34 @@
+import { toRefs } from 'vue'
+
+export default function useI18n (props, context, dep)
+{
+ const {
+ locale, fallbackLocale,
+ } = toRefs(props)
+
+ // =============== METHODS ==============
+
+ const localize = (target) => {
+ if (!target || typeof target !== 'object') {
+ return target
+ }
+
+ if (target && target[locale.value]) {
+ return target[locale.value]
+ } else if (target && locale.value && target[locale.value.toUpperCase()]) {
+ return target[locale.value.toUpperCase()]
+ } else if (target && target[fallbackLocale.value]) {
+ return target[fallbackLocale.value]
+ } else if (target && fallbackLocale.value && target[fallbackLocale.value.toUpperCase()]) {
+ return target[fallbackLocale.value.toUpperCase()]
+ } else if (target && Object.keys(target)[0]) {
+ return target[Object.keys(target)[0]]
+ } else {
+ return ''
+ }
+ }
+
+ return {
+ localize,
+ }
+}
\ No newline at end of file
diff --git a/Tithe-Vue/src/components/MultiSelectBox/composables/useKeyboard.js b/Tithe-Vue/src/components/MultiSelectBox/composables/useKeyboard.js
new file mode 100644
index 0000000..6fb5d31
--- /dev/null
+++ b/Tithe-Vue/src/components/MultiSelectBox/composables/useKeyboard.js
@@ -0,0 +1,258 @@
+import { toRefs, computed, getCurrentInstance } from 'vue'
+
+export default function useKeyboard (props, context, dep)
+{
+ const {
+ mode, addTagOn, openDirection, searchable,
+ showOptions, valueProp, groups: groupped,
+ addOptionOn: addOptionOn_, createTag, createOption: createOption_,
+ reverse,
+ } = toRefs(props)
+
+ const $this = getCurrentInstance().proxy
+
+ // ============ DEPENDENCIES ============
+
+ const iv = dep.iv
+ const update = dep.update
+ const search = dep.search
+ const setPointer = dep.setPointer
+ const selectPointer = dep.selectPointer
+ const backwardPointer = dep.backwardPointer
+ const forwardPointer = dep.forwardPointer
+ const multiselect = dep.multiselect
+ const wrapper = dep.wrapper
+ const tags = dep.tags
+ const isOpen = dep.isOpen
+ const open = dep.open
+ const blur = dep.blur
+ const fo = dep.fo
+
+ // ============== COMPUTED ==============
+
+ // no export
+ const createOption = computed(() => {
+ return createTag.value || createOption_.value || false
+ })
+
+ // no export
+ const addOptionOn = computed(() => {
+ if (addTagOn.value !== undefined) {
+ return addTagOn.value
+ }
+ else if (addOptionOn_.value !== undefined) {
+ return addOptionOn_.value
+ }
+
+ return ['enter']
+ })
+
+ // =============== METHODS ==============
+
+ // no export
+ const preparePointer = () => {
+ // When options are hidden and creating tags is allowed
+ // no pointer will be set (because options are hidden).
+ // In such case we need to set the pointer manually to the
+ // first option, which equals to the option created from
+ // the search value.
+ if (mode.value === 'tags' && !showOptions.value && createOption.value && searchable.value && !groupped.value) {
+ setPointer(fo.value[fo.value.map(o => o[valueProp.value]).indexOf(search.value)])
+ }
+ }
+
+ const removeLastRemovable = (arr) => {
+ // Find the index of the last object in the array that doesn't have a "remove" property set to false
+ let indexToRemove = arr.length - 1
+ while (indexToRemove >= 0 && (arr[indexToRemove].remove === false || arr[indexToRemove].disabled)) {
+ indexToRemove--
+ }
+
+ // If all objects have a "remove" property set to false, don't remove anything and return the original array
+ if (indexToRemove < 0) {
+ return arr
+ }
+
+ // Remove the object at the found index and return the updated array
+ arr.splice(indexToRemove, 1);
+ return arr
+ }
+
+ const handleKeydown = (e) => {
+ context.emit('keydown', e, $this)
+
+ let tagList
+ let activeIndex
+
+ if (['ArrowLeft', 'ArrowRight', 'Enter'].indexOf(e.key) !== -1 && mode.value === 'tags') {
+ tagList = [...(multiselect.value.querySelectorAll(`[data-tags] > *`))].filter(e => e !== tags.value)
+ activeIndex = tagList.findIndex(e => e === document.activeElement)
+ }
+
+ switch (e.key) {
+ case 'Backspace':
+ if (mode.value === 'single') {
+ return
+ }
+
+ if (searchable.value && [null, ''].indexOf(search.value) === -1) {
+ return
+ }
+
+ if (iv.value.length === 0) {
+ return
+ }
+
+ update(removeLastRemovable([...iv.value]))
+ break
+
+ case 'Enter':
+ e.preventDefault()
+
+ if (e.keyCode === 229) {
+ // ignore IME confirmation
+ return
+ }
+
+ if (activeIndex !== -1 && activeIndex !== undefined) {
+ update([...iv.value].filter((v, k) => k !== activeIndex))
+
+ if (activeIndex === tagList.length - 1) {
+ if (tagList.length - 1) {
+ tagList[tagList.length - 2].focus()
+ } else if (searchable.value) {
+ tags.value.querySelector('input').focus()
+ } else {
+ wrapper.value.focus()
+ }
+ }
+ return
+ }
+
+ if (addOptionOn.value.indexOf('enter') === -1 && createOption.value) {
+ return
+ }
+
+ preparePointer()
+ selectPointer()
+ break
+
+ case ' ':
+ if (!createOption.value && !searchable.value) {
+ e.preventDefault()
+
+ preparePointer()
+ selectPointer()
+ return
+ }
+
+ if (!createOption.value) {
+ return false
+ }
+
+ if (addOptionOn.value.indexOf('space') === -1 && createOption.value) {
+ return
+ }
+
+ e.preventDefault()
+
+ preparePointer()
+ selectPointer()
+ break
+
+ case 'Tab':
+ case ';':
+ case ',':
+ if (addOptionOn.value.indexOf(e.key.toLowerCase()) === -1 || !createOption.value) {
+ return
+ }
+
+ preparePointer()
+ selectPointer()
+ e.preventDefault()
+ break
+
+ case 'Escape':
+ blur()
+ break
+
+ case 'ArrowUp':
+ e.preventDefault()
+
+ if (!showOptions.value) {
+ return
+ }
+
+ /* istanbul ignore else */
+ if (!isOpen.value) {
+ open()
+ }
+
+ backwardPointer()
+ break
+
+ case 'ArrowDown':
+ e.preventDefault()
+
+ if (!showOptions.value) {
+ return
+ }
+
+ /* istanbul ignore else */
+ if (!isOpen.value) {
+ open()
+ }
+
+ forwardPointer()
+ break
+
+ case 'ArrowLeft':
+ if (
+ (searchable.value && tags.value && tags.value.querySelector('input').selectionStart)
+ || e.shiftKey || mode.value !== 'tags' || !iv.value || !iv.value.length
+ ) {
+ return
+ }
+
+ e.preventDefault()
+
+ if (activeIndex === -1) {
+ tagList[tagList.length-1].focus()
+ }
+ else if (activeIndex > 0) {
+ tagList[activeIndex-1].focus()
+ }
+ break
+
+ case 'ArrowRight':
+ if (activeIndex === -1 || e.shiftKey || mode.value !== 'tags' || !iv.value || !iv.value.length) {
+ return
+ }
+
+ e.preventDefault()
+
+ /* istanbul ignore else */
+ if (tagList.length > activeIndex + 1) {
+ tagList[activeIndex+1].focus()
+ }
+ else if (searchable.value) {
+ tags.value.querySelector('input').focus()
+ }
+ else if (!searchable.value) {
+ wrapper.value.focus()
+ }
+
+ break
+ }
+ }
+
+ const handleKeyup = (e) => {
+ context.emit('keyup', e, $this)
+ }
+
+ return {
+ handleKeydown,
+ handleKeyup,
+ preparePointer,
+ }
+}
\ No newline at end of file
diff --git a/Tithe-Vue/src/components/MultiSelectBox/composables/useMultiselect.js b/Tithe-Vue/src/components/MultiSelectBox/composables/useMultiselect.js
new file mode 100644
index 0000000..267ac6f
--- /dev/null
+++ b/Tithe-Vue/src/components/MultiSelectBox/composables/useMultiselect.js
@@ -0,0 +1,125 @@
+import { ref, toRefs, computed, nextTick } from 'vue'
+
+export default function useMultiselect (props, context, dep)
+{
+ const { searchable, disabled, clearOnBlur } = toRefs(props)
+
+ // ============ DEPENDENCIES ============
+
+ const input = dep.input
+ const open = dep.open
+ const close = dep.close
+ const clearSearch = dep.clearSearch
+ const isOpen = dep.isOpen
+
+ // ================ DATA ================
+
+ const multiselect = ref(null)
+
+ const wrapper = ref(null)
+
+ const tags = ref(null)
+
+ const isActive = ref(false)
+
+ const mouseClicked = ref(false)
+
+ // ============== COMPUTED ==============
+
+ const tabindex = computed(() => {
+ return searchable.value || disabled.value ? -1 : 0
+ })
+
+ // =============== METHODS ==============
+
+ const blur = () => {
+ if (searchable.value) {
+ input.value.blur()
+ }
+
+ wrapper.value.blur()
+ }
+
+ const focus = () => {
+ if (searchable.value && !disabled.value) {
+ input.value.focus()
+ }
+ }
+
+ const activate = (shouldOpen = true) => {
+ if (disabled.value) {
+ return
+ }
+
+ isActive.value = true
+
+ if (shouldOpen) {
+ open()
+ }
+ }
+
+ const deactivate = () => {
+ isActive.value = false
+
+ setTimeout(() => {
+ if (!isActive.value) {
+ close()
+
+ if (clearOnBlur.value) {
+ clearSearch()
+ }
+ }
+ }, 1)
+ }
+
+ const handleFocusIn = (e) => {
+ if ((e.target.closest('[data-tags]') && e.target.nodeName !== 'INPUT') || e.target.closest('[data-clear]')) {
+ return
+ }
+
+ activate(mouseClicked.value)
+ }
+
+ const handleFocusOut = () => {
+ deactivate()
+ }
+
+ const handleCaretClick = () => {
+ deactivate()
+ blur()
+ }
+
+ /* istanbul ignore next */
+ const handleMousedown = (e) => {
+ mouseClicked.value = true
+
+ if (isOpen.value && (e.target.isEqualNode(wrapper.value) || e.target.isEqualNode(tags.value))) {
+ setTimeout(() => {
+ deactivate()
+ }, 0)
+ } else if (document.activeElement.isEqualNode(wrapper.value) && !isOpen.value) {
+ activate()
+ }
+
+ setTimeout(() => {
+ mouseClicked.value = false
+ }, 0)
+ }
+
+ return {
+ multiselect,
+ wrapper,
+ tags,
+ tabindex,
+ isActive,
+ mouseClicked,
+ blur,
+ focus,
+ activate,
+ deactivate,
+ handleFocusIn,
+ handleFocusOut,
+ handleCaretClick,
+ handleMousedown,
+ }
+}
\ No newline at end of file
diff --git a/Tithe-Vue/src/components/MultiSelectBox/composables/useOptions.js b/Tithe-Vue/src/components/MultiSelectBox/composables/useOptions.js
new file mode 100644
index 0000000..6cc928f
--- /dev/null
+++ b/Tithe-Vue/src/components/MultiSelectBox/composables/useOptions.js
@@ -0,0 +1,856 @@
+import { ref, toRefs, computed, watch, getCurrentInstance } from 'vue'
+import normalize from '../utils/normalize'
+import isObject from '../utils/isObject'
+import isNullish from '../utils/isNullish'
+import arraysEqual from '../utils/arraysEqual'
+
+export default function useOptions (props, context, dep)
+{
+ const {
+ options, mode, trackBy: trackBy_, limit, hideSelected, createTag, createOption: createOption_, label,
+ appendNewTag, appendNewOption: appendNewOption_, multipleLabel, object, loading, delay, resolveOnLoad,
+ minChars, filterResults, clearOnSearch, clearOnSelect, valueProp, allowAbsent, groupLabel,
+ canDeselect, max, strict, closeOnSelect, closeOnDeselect, groups: groupped, reverse, infinite,
+ groupOptions, groupHideEmpty, groupSelect, onCreate, disabledProp, searchStart, searchFilter,
+ } = toRefs(props)
+
+ const $this = getCurrentInstance().proxy
+
+ // ============ DEPENDENCIES ============
+
+ const iv = dep.iv
+ const ev = dep.ev
+ const search = dep.search
+ const clearSearch = dep.clearSearch
+ const update = dep.update
+ const pointer = dep.pointer
+ const clearPointer = dep.clearPointer
+ const focus = dep.focus
+ const deactivate = dep.deactivate
+ const close = dep.close
+ const localize = dep.localize
+
+ // ================ DATA ================
+
+ // no export
+ // appendedOptions
+ const ap = ref([])
+
+ // no export
+ // resolvedOptions
+ const ro = ref([])
+
+ const resolving = ref(false)
+
+ // no export
+ const searchWatcher = ref(null)
+
+ const offset = ref(infinite.value && limit.value === -1 ? 10 : limit.value)
+
+ // ============== COMPUTED ==============
+
+ // no export
+ const createOption = computed(() => {
+ return createTag.value || createOption_.value || false
+ })
+
+ // no export
+ const appendNewOption = computed(() => {
+ if (appendNewTag.value !== undefined) {
+ return appendNewTag.value
+ } else if (appendNewOption_.value !== undefined) {
+ return appendNewOption_.value
+ }
+
+ return true
+ })
+
+ // no export
+ // extendedOptions
+ const eo = computed(() => {
+ if (groupped.value) {
+ let groups = eg.value || /* istanbul ignore next */ []
+
+ let eo = []
+
+ groups.forEach((group) => {
+ optionsToArray(group[groupOptions.value]).forEach((option) => {
+ eo.push(Object.assign({}, option, group[disabledProp.value] ? { [disabledProp.value]: true } : {}))
+ })
+ })
+
+ return eo
+ } else {
+ let eo = optionsToArray(ro.value || /* istanbul ignore next */ [])
+
+ if (ap.value.length) {
+ eo = eo.concat(ap.value)
+ }
+
+ return eo
+ }
+ })
+
+ // preFilteredOptions
+ const pfo = computed(() => {
+ let options = eo.value
+
+ if (reverse.value) {
+ options = options.reverse()
+ }
+
+ if (createdOption.value.length) {
+ options = createdOption.value.concat(options)
+ }
+
+ return filterOptions(options)
+ })
+
+ // filteredOptions
+ const fo = computed(() => {
+ let options = pfo.value
+
+ if (offset.value > 0) {
+ options = options.slice(0, offset.value)
+ }
+
+ return options
+ })
+
+ // no export
+ // extendedGroups
+ const eg = computed(() => {
+ if (!groupped.value) {
+ return []
+ }
+
+ let eg = []
+ let groups = ro.value || /* istanbul ignore next */ []
+
+ if (ap.value.length) {
+ eg.push({
+ [groupLabel.value]: ' ',
+ [groupOptions.value]: [...ap.value],
+ __CREATE__: true
+ })
+ }
+
+ return eg.concat(groups)
+ })
+
+ // preFilteredGroups
+ const pfg = computed(() => {
+ let groups = [...eg.value].map(g => ({...g}))
+
+ if (createdOption.value.length) {
+ if (groups[0] && groups[0].__CREATE__) {
+ groups[0][groupOptions.value] = [...createdOption.value, ...groups[0][groupOptions.value]]
+ } else {
+ groups = [{
+ [groupLabel.value]: ' ',
+ [groupOptions.value]: [...createdOption.value],
+ __CREATE__: true
+ }].concat(groups)
+ }
+ }
+
+ return groups
+ })
+
+ // filteredGroups
+ const fg = computed(() => {
+ if (!groupped.value) {
+ return []
+ }
+
+ let options = pfg.value
+
+ return filterGroups((options || /* istanbul ignore next */ []).map((group, index) => {
+ const arrayOptions = optionsToArray(group[groupOptions.value])
+
+ return {
+ ...group,
+ index,
+ group: true,
+ [groupOptions.value]: filterOptions(arrayOptions, false).map(o => Object.assign({}, o, group[disabledProp.value] ? { [disabledProp.value]: true } : {})),
+ __VISIBLE__: filterOptions(arrayOptions).map(o => Object.assign({}, o, group[disabledProp.value] ? { [disabledProp.value]: true } : {})),
+ }
+ // Difference between __VISIBLE__ and {groupOptions}: visible does not contain selected options when hideSelected=true
+ }))
+ })
+
+ const hasSelected = computed(() => {
+ switch (mode.value) {
+ case 'single':
+ return !isNullish(iv.value[valueProp.value])
+
+ case 'multiple':
+ case 'tags':
+ return !isNullish(iv.value) && iv.value.length > 0
+ }
+ })
+
+ const multipleLabelText = computed(() => {
+ return multipleLabel !== undefined && multipleLabel.value !== undefined
+ ? multipleLabel.value(iv.value, $this)
+ : (iv.value && iv.value.length > 1 ? `${iv.value.length} options selected` : `1 option selected`)
+ })
+
+ const noOptions = computed(() => {
+ return !eo.value.length && !resolving.value && !createdOption.value.length
+ })
+
+
+ const noResults = computed(() => {
+ return eo.value.length > 0 && fo.value.length == 0 && ((search.value && groupped.value) || !groupped.value)
+ })
+
+ // no export
+ const createdOption = computed(() => {
+ if (createOption.value === false || !search.value) {
+ return []
+ }
+
+ if (getOptionByTrackBy(search.value) !== -1) {
+ return []
+ }
+
+ return [{
+ [valueProp.value]: search.value,
+ [trackBy.value]: search.value,
+ [label.value]: search.value,
+ __CREATE__: true,
+ }]
+ })
+
+ const trackBy = computed(() => {
+ return trackBy_.value || label.value
+ })
+
+ // no export
+ const nullValue = computed(() => {
+ switch (mode.value) {
+ case 'single':
+ return null
+
+ case 'multiple':
+ case 'tags':
+ return []
+ }
+ })
+
+ const busy = computed(() => {
+ return loading.value || resolving.value
+ })
+
+ // =============== METHODS ==============
+
+ /**
+ * @param {array|object|string|number} option
+ */
+ const select = (option) => {
+ if (typeof option !== 'object') {
+ option = getOption(option)
+ }
+
+ switch (mode.value) {
+ case 'single':
+ update(option)
+ break
+
+ case 'multiple':
+ case 'tags':
+ update((iv.value).concat(option))
+ break
+ }
+
+ context.emit('select', finalValue(option), option, $this)
+ }
+
+ const deselect = (option) => {
+ if (typeof option !== 'object') {
+ option = getOption(option)
+ }
+
+ switch (mode.value) {
+ case 'single':
+ clear()
+ break
+
+ case 'tags':
+ case 'multiple':
+ update(Array.isArray(option)
+ ? iv.value.filter(v => option.map(o => o[valueProp.value]).indexOf(v[valueProp.value]) === -1)
+ : iv.value.filter(v => v[valueProp.value] != option[valueProp.value]))
+ break
+ }
+
+ context.emit('deselect', finalValue(option), option, $this)
+ }
+
+ // no export
+ const finalValue = (option) => {
+ return object.value ? option : option[valueProp.value]
+ }
+
+ const remove = (option) => {
+ deselect(option)
+ }
+
+ const handleTagRemove = (option, e) => {
+ if (e.button !== 0) {
+ e.preventDefault()
+ return
+ }
+
+ remove(option)
+ }
+
+ const clear = () => {
+ context.emit('clear', $this)
+ update(nullValue.value)
+ }
+
+ const isSelected = (option) => {
+ if (option.group !== undefined) {
+ return mode.value === 'single' ? false : areAllSelected(option[groupOptions.value]) && option[groupOptions.value].length
+ }
+
+ switch (mode.value) {
+ case 'single':
+ return !isNullish(iv.value) && iv.value[valueProp.value] == option[valueProp.value]
+
+ case 'tags':
+ case 'multiple':
+ return !isNullish(iv.value) && iv.value.map(o => o[valueProp.value]).indexOf(option[valueProp.value]) !== -1
+ }
+ }
+
+ const isDisabled = (option) => {
+ return option[disabledProp.value] === true
+ }
+
+ const isMax = () => {
+ if (max === undefined || max.value === -1 || (!hasSelected.value && max.value > 0)) {
+ return false
+ }
+
+ return iv.value.length >= max.value
+ }
+
+ const handleOptionClick = (option) => {
+ if (isDisabled(option)) {
+ return
+ }
+
+ if (onCreate && onCreate.value && !isSelected(option) && option.__CREATE__) {
+ option = { ...option }
+ delete option.__CREATE__
+
+ option = onCreate.value(option, $this)
+
+ if (option instanceof Promise) {
+ resolving.value = true
+ option.then((result) => {
+ resolving.value = false
+ handleOptionSelect(result)
+ })
+
+ return
+ }
+ }
+
+ handleOptionSelect(option)
+ }
+
+ const handleOptionSelect = (option) => {
+ if (option.__CREATE__) {
+ option = { ...option }
+ delete option.__CREATE__
+ }
+
+ switch (mode.value) {
+ case 'single':
+ if (option && isSelected(option)) {
+ if (canDeselect.value) {
+ deselect(option)
+ }
+
+ if (closeOnDeselect.value) {
+ clearPointer()
+ close()
+ }
+ return
+ }
+
+ if (option) {
+ handleOptionAppend(option)
+ }
+
+ /* istanbul ignore else */
+ if (clearOnSelect.value) {
+ clearSearch()
+ }
+
+ if (closeOnSelect.value) {
+ clearPointer()
+ close()
+ }
+
+ if (option) {
+ select(option)
+ }
+ break
+
+ case 'multiple':
+ if (option && isSelected(option)) {
+ deselect(option)
+
+ if (closeOnDeselect.value) {
+ clearPointer()
+ close()
+ }
+ return
+ }
+
+ if (isMax()) {
+ context.emit('max', $this)
+ return
+ }
+
+ if (option) {
+ handleOptionAppend(option)
+ select(option)
+ }
+
+ if (clearOnSelect.value) {
+ clearSearch()
+ }
+
+ if (hideSelected.value) {
+ clearPointer()
+ }
+
+ if (closeOnSelect.value) {
+ close()
+ }
+ break
+
+ case 'tags':
+ if (option && isSelected(option)) {
+ deselect(option)
+
+ if (closeOnDeselect.value) {
+ clearPointer()
+ close()
+ }
+ return
+ }
+
+ if (isMax()) {
+ context.emit('max', $this)
+ return
+ }
+
+ if (option) {
+ handleOptionAppend(option)
+ }
+
+ if (clearOnSelect.value) {
+ clearSearch()
+ }
+
+ if (option) {
+ select(option)
+ }
+
+ if (hideSelected.value) {
+ clearPointer()
+ }
+
+ if (closeOnSelect.value) {
+ close()
+ }
+ break
+ }
+
+ if (!closeOnSelect.value) {
+ focus()
+ }
+ }
+
+ const handleGroupClick = (group) => {
+ if (isDisabled(group) || mode.value === 'single' || !groupSelect.value) {
+ return
+ }
+
+ switch (mode.value) {
+ case 'multiple':
+ case 'tags':
+ if (areAllEnabledSelected(group[groupOptions.value])) {
+ deselect(group[groupOptions.value])
+ } else {
+ select(group[groupOptions.value]
+ .filter(o => iv.value.map(v => v[valueProp.value]).indexOf(o[valueProp.value]) === -1)
+ .filter(o => !o[disabledProp.value])
+ .filter((o, k) => iv.value.length + 1 + k <= max.value || max.value === -1)
+ )
+ }
+ break
+ }
+
+ if (closeOnSelect.value) {
+ deactivate()
+ }
+ }
+
+ const handleOptionAppend = (option) => {
+ if (getOption(option[valueProp.value]) === undefined && createOption.value) {
+ context.emit('tag', option[valueProp.value], $this)
+ context.emit('option', option[valueProp.value], $this)
+ context.emit('create', option[valueProp.value], $this)
+
+ if (appendNewOption.value) {
+ appendOption(option)
+ }
+
+ clearSearch()
+ }
+ }
+
+ const selectAll = () => {
+ if (mode.value === 'single') {
+ return
+ }
+
+ select(fo.value.filter(o => !o.disabled && !isSelected(o)))
+ }
+
+ // no export
+ const areAllEnabledSelected = (options) => {
+ return options.find(o => !isSelected(o) && !o[disabledProp.value]) === undefined
+ }
+
+ // no export
+ const areAllSelected = (options) => {
+ return options.find(o => !isSelected(o)) === undefined
+ }
+
+ const getOption = (val) => {
+ return eo.value[eo.value.map(o => String(o[valueProp.value])).indexOf(String(val))]
+ }
+
+ // no export
+ const getOptionByTrackBy = (val, norm = true) => {
+ return eo.value.map(o => parseInt(o[trackBy.value]) == o[trackBy.value] ? parseInt(o[trackBy.value]) : o[trackBy.value]).indexOf(
+ parseInt(val) == val ? parseInt(val) : val
+ )
+ }
+
+ // no export
+ const shouldHideOption = (option) => {
+ return ['tags', 'multiple'].indexOf(mode.value) !== -1 && hideSelected.value && isSelected(option)
+ }
+
+ // no export
+ const appendOption = (option) => {
+ ap.value.push(option)
+ }
+
+ // no export
+ const filterGroups = (groups) => {
+ // If the search has value we need to filter among
+ // the ones that are visible to the user to avoid
+ // displaying groups which technically have options
+ // based on search but that option is already selected.
+ return groupHideEmpty.value
+ ? groups.filter(g => search.value
+ ? g.__VISIBLE__.length
+ : g[groupOptions.value].length
+ )
+ : groups.filter(g => search.value ? g.__VISIBLE__.length : true)
+ }
+
+ // no export
+ const filterOptions = (options, excludeHideSelected = true) => {
+ let fo = options
+
+ if (search.value && filterResults.value) {
+ let filter = searchFilter.value
+
+ if (!filter) {
+ filter = (option, $this) => {
+ let target = normalize(localize(option[trackBy.value]), strict.value)
+
+ return searchStart.value
+ ? target.startsWith(normalize(search.value, strict.value))
+ : target.indexOf(normalize(search.value, strict.value)) !== -1
+ }
+ }
+
+ fo = fo.filter(filter)
+ }
+
+ if (hideSelected.value && excludeHideSelected) {
+ fo = fo.filter((option) => !shouldHideOption(option))
+ }
+
+ return fo
+ }
+
+ // no export
+ const optionsToArray = (options) => {
+ let uo = options
+
+ // Transforming an object to an array of objects
+ if (isObject(uo)) {
+ uo = Object.keys(uo).map((key) => {
+ let val = uo[key]
+
+ return { [valueProp.value]: key, [trackBy.value]: val, [label.value]: val}
+ })
+ }
+
+ // Transforming an plain arrays to an array of objects
+ uo = uo.map((val) => {
+ return typeof val === 'object' ? val : { [valueProp.value]: val, [trackBy.value]: val, [label.value]: val}
+ })
+
+ return uo
+ }
+
+ // no export
+ const initInternalValue = () => {
+ if (!isNullish(ev.value)) {
+ iv.value = makeInternal(ev.value)
+ }
+ }
+
+ const resolveOptions = (callback) => {
+ resolving.value = true
+
+ return new Promise((resolve, reject) => {
+ options.value(search.value, $this).then((response) => {
+ ro.value = response || []
+
+ if (typeof callback == 'function') {
+ callback(response)
+ }
+
+ resolving.value = false
+ }).catch((e) => {
+ console.error(e)
+
+ ro.value = []
+
+ resolving.value = false
+ }).finally(() => {
+ resolve()
+ })
+ })
+ }
+
+ // no export
+ const refreshLabels = () => {
+ if (!hasSelected.value) {
+ return
+ }
+
+ if (mode.value === 'single') {
+ let option = getOption(iv.value[valueProp.value])
+
+ /* istanbul ignore else */
+ if (option !== undefined) {
+ let newLabel = option[label.value]
+
+ iv.value[label.value] = newLabel
+
+ if (object.value) {
+ ev.value[label.value] = newLabel
+ }
+ }
+ } else {
+ iv.value.forEach((val, i) => {
+ let option = getOption(iv.value[i][valueProp.value])
+
+ /* istanbul ignore else */
+ if (option !== undefined) {
+ let newLabel = option[label.value]
+
+ iv.value[i][label.value] = newLabel
+
+ if (object.value) {
+ ev.value[i][label.value] = newLabel
+ }
+ }
+ })
+ }
+ }
+
+ const refreshOptions = (callback) => {
+ resolveOptions(callback)
+ }
+
+ // no export
+ const makeInternal = (val) => {
+ if (isNullish(val)) {
+ return mode.value === 'single' ? {} : []
+ }
+
+ if (object.value) {
+ return val
+ }
+
+ // If external should be plain transform value object to plain values
+ return mode.value === 'single' ? getOption(val) || (allowAbsent.value ? {
+ [label.value]: val,
+ [valueProp.value]: val,
+ [trackBy.value]: val,
+ } : {}) : val.filter(v => !!getOption(v) || allowAbsent.value).map(v => getOption(v) || {
+ [label.value]: v,
+ [valueProp.value]: v,
+ [trackBy.value]: v,
+ })
+ }
+
+ // no export
+ const initSearchWatcher = () => {
+ searchWatcher.value = watch(search, (query) => {
+ if (query.length < minChars.value || (!query && minChars.value !== 0)) {
+ return
+ }
+
+ resolving.value = true
+
+ if (clearOnSearch.value) {
+ ro.value = []
+ }
+ setTimeout(() => {
+ if (query != search.value) {
+ return
+ }
+
+ options.value(search.value, $this).then((response) => {
+ if (query == search.value || !search.value) {
+ ro.value = response
+ pointer.value = fo.value.filter(o => o[disabledProp.value] !== true)[0] || null
+ resolving.value = false
+ }
+ }).catch( /* istanbul ignore next */ (e) => {
+ console.error(e)
+ })
+ }, delay.value)
+
+ }, { flush: 'sync' })
+ }
+
+ // ================ HOOKS ===============
+
+ if (mode.value !== 'single' && !isNullish(ev.value) && !Array.isArray(ev.value)) {
+ throw new Error(`v-model must be an array when using "${mode.value}" mode`)
+ }
+
+ if (options && typeof options.value == 'function') {
+ if (resolveOnLoad.value) {
+ resolveOptions(initInternalValue)
+ } else if (object.value == true) {
+ initInternalValue()
+ }
+ }
+ else {
+ ro.value = options.value
+
+ initInternalValue()
+ }
+
+ // ============== WATCHERS ==============
+
+ if (delay.value > -1) {
+ initSearchWatcher()
+ }
+
+ watch(delay, (value, old) => {
+ /* istanbul ignore else */
+ if (searchWatcher.value) {
+ searchWatcher.value()
+ }
+
+ if (value >= 0) {
+ initSearchWatcher()
+ }
+ })
+
+ watch(ev, (newValue) => {
+ if (isNullish(newValue)) {
+ update(makeInternal(newValue), false)
+ return
+ }
+
+ switch (mode.value) {
+ case 'single':
+ if (object.value ? newValue[valueProp.value] != iv.value[valueProp.value] : newValue != iv.value[valueProp.value]) {
+ update(makeInternal(newValue), false)
+ }
+ break
+
+ case 'multiple':
+ case 'tags':
+ if (!arraysEqual(object.value ? newValue.map(o => o[valueProp.value]) : newValue, iv.value.map(o => o[valueProp.value]))) {
+ update(makeInternal(newValue), false)
+ }
+ break
+ }
+ }, { deep: true })
+
+ watch(options, (n, o) => {
+ if (typeof props.options === 'function') {
+ if (resolveOnLoad.value && (!o || (n && n.toString() !== o.toString()))) {
+ resolveOptions()
+ }
+ } else {
+ ro.value = props.options
+
+ if (!Object.keys(iv.value).length) {
+ initInternalValue()
+ }
+
+ refreshLabels()
+ }
+ })
+
+ watch(label, refreshLabels)
+
+ return {
+ pfo,
+ fo,
+ filteredOptions: fo,
+ hasSelected,
+ multipleLabelText,
+ eo,
+ extendedOptions: eo,
+ eg,
+ extendedGroups: eg,
+ fg,
+ filteredGroups: fg,
+ noOptions,
+ noResults,
+ resolving,
+ busy,
+ offset,
+ select,
+ deselect,
+ remove,
+ selectAll,
+ clear,
+ isSelected,
+ isDisabled,
+ isMax,
+ getOption,
+ handleOptionClick,
+ handleGroupClick,
+ handleTagRemove,
+ refreshOptions,
+ resolveOptions,
+ refreshLabels,
+ }
+}
\ No newline at end of file
diff --git a/Tithe-Vue/src/components/MultiSelectBox/composables/usePointer.js b/Tithe-Vue/src/components/MultiSelectBox/composables/usePointer.js
new file mode 100644
index 0000000..7cd2b91
--- /dev/null
+++ b/Tithe-Vue/src/components/MultiSelectBox/composables/usePointer.js
@@ -0,0 +1,34 @@
+import { ref, toRefs } from 'vue'
+
+export default function usePointer (props, context, dep)
+{
+ const { groupSelect, mode, groups, disabledProp } = toRefs(props)
+
+ // ================ DATA ================
+
+ const pointer = ref(null)
+
+ // =============== METHODS ==============
+
+ const setPointer = (option) => {
+ if (option === undefined || (option !== null && option[disabledProp.value])) {
+ return
+ }
+
+ if (groups.value && option && option.group && (mode.value === 'single' || !groupSelect.value)) {
+ return
+ }
+
+ pointer.value = option
+ }
+
+ const clearPointer = () => {
+ setPointer(null)
+ }
+
+ return {
+ pointer,
+ setPointer,
+ clearPointer,
+ }
+}
\ No newline at end of file
diff --git a/Tithe-Vue/src/components/MultiSelectBox/composables/usePointerAction.js b/Tithe-Vue/src/components/MultiSelectBox/composables/usePointerAction.js
new file mode 100644
index 0000000..e324ae8
--- /dev/null
+++ b/Tithe-Vue/src/components/MultiSelectBox/composables/usePointerAction.js
@@ -0,0 +1,272 @@
+import { toRefs, watch, nextTick, computed } from 'vue'
+
+export default function usePointer (props, context, dep)
+{
+ const {
+ valueProp, showOptions, searchable, groupLabel,
+ groups: groupped, mode, groupSelect, disabledProp,
+ groupOptions,
+ } = toRefs(props)
+
+ // ============ DEPENDENCIES ============
+
+ const fo = dep.fo
+ const fg = dep.fg
+ const handleOptionClick = dep.handleOptionClick
+ const handleGroupClick = dep.handleGroupClick
+ const search = dep.search
+ const pointer = dep.pointer
+ const setPointer = dep.setPointer
+ const clearPointer = dep.clearPointer
+ const multiselect = dep.multiselect
+ const isOpen = dep.isOpen
+
+ // ============== COMPUTED ==============
+
+ // no export
+ const options = computed(() => {
+ return fo.value.filter(o => !o[disabledProp.value])
+ })
+
+ const groups = computed(() => {
+ return fg.value.filter(g => !g[disabledProp.value])
+ })
+
+ const canPointGroups = computed(() => {
+ return mode.value !== 'single' && groupSelect.value
+ })
+
+ const isPointerGroup = computed(() => {
+ return pointer.value && pointer.value.group
+ })
+
+ const currentGroup = computed(() => {
+ return getParentGroup(pointer.value)
+ })
+
+ const prevGroup = computed(() => {
+ const group = isPointerGroup.value ? pointer.value : /* istanbul ignore next */ getParentGroup(pointer.value)
+ const groupIndex = groups.value.map(g => g[groupLabel.value]).indexOf(group[groupLabel.value])
+ let prevGroup = groups.value[groupIndex - 1]
+
+ if (prevGroup === undefined) {
+ prevGroup = lastGroup.value
+ }
+
+ return prevGroup
+ })
+
+ const nextGroup = computed(() => {
+ let nextIndex = groups.value.map(g => g.label).indexOf(isPointerGroup.value
+ ? pointer.value[groupLabel.value]
+ : getParentGroup(pointer.value)[groupLabel.value]) + 1
+
+ if (groups.value.length <= nextIndex) {
+ nextIndex = 0
+ }
+
+ return groups.value[nextIndex]
+ })
+
+ const lastGroup = computed(() => {
+ return [...groups.value].slice(-1)[0]
+ })
+
+ const currentGroupFirstEnabledOption = computed(() => {
+ return pointer.value.__VISIBLE__.filter(o => !o[disabledProp.value])[0]
+ })
+
+ const currentGroupPrevEnabledOption = computed(() => {
+ const options = currentGroup.value.__VISIBLE__.filter(o => !o[disabledProp.value])
+ return options[options.map(o => o[valueProp.value]).indexOf(pointer.value[valueProp.value]) - 1]
+ })
+
+ const currentGroupNextEnabledOption = computed(() => {
+ const options = getParentGroup(pointer.value).__VISIBLE__.filter(o => !o[disabledProp.value])
+ return options[options.map(o => o[valueProp.value]).indexOf(pointer.value[valueProp.value]) + 1]
+ })
+
+ const prevGroupLastEnabledOption = computed(() => {
+ return [...prevGroup.value.__VISIBLE__.filter(o => !o[disabledProp.value])].slice(-1)[0]
+ })
+
+ const lastGroupLastEnabledOption = computed(() => {
+ return [...lastGroup.value.__VISIBLE__.filter(o => !o[disabledProp.value])].slice(-1)[0]
+ })
+
+ // =============== METHODS ==============
+
+ const isPointed = (option) => {
+ return (!!pointer.value && (
+ (!option.group && pointer.value[valueProp.value] === option[valueProp.value]) ||
+ (option.group !== undefined && pointer.value[groupLabel.value] === option[groupLabel.value])
+ )) ? true : undefined
+ }
+
+ const setPointerFirst = () => {
+ setPointer(options.value[0] || null)
+ }
+
+ const selectPointer = () => {
+ if (!pointer.value || pointer.value[disabledProp.value] === true) {
+ return
+ }
+
+ if (isPointerGroup.value) {
+ handleGroupClick(pointer.value)
+ } else {
+ handleOptionClick(pointer.value)
+ }
+ }
+
+ const forwardPointer = () => {
+ if (pointer.value === null) {
+ setPointer((groupped.value && canPointGroups.value ? (!groups.value[0].__CREATE__ ? groups.value[0] : options.value[0]) : options.value[0]) || null)
+ }
+ else if (groupped.value && canPointGroups.value) {
+ let nextPointer = isPointerGroup.value ? currentGroupFirstEnabledOption.value : currentGroupNextEnabledOption.value
+
+ if (nextPointer === undefined) {
+ nextPointer = nextGroup.value
+
+ if (nextPointer.__CREATE__) {
+ nextPointer = nextPointer[groupOptions.value][0]
+ }
+ }
+
+ setPointer(nextPointer || /* istanbul ignore next */ null)
+ } else {
+ let next = options.value.map(o => o[valueProp.value]).indexOf(pointer.value[valueProp.value]) + 1
+
+ if (options.value.length <= next) {
+ next = 0
+ }
+
+ setPointer(options.value[next] || null)
+ }
+
+ nextTick(() => {
+ adjustWrapperScrollToPointer()
+ })
+ }
+
+ const backwardPointer = () => {
+ if (pointer.value === null) {
+ let prevPointer = options.value[options.value.length - 1]
+
+ if (groupped.value && canPointGroups.value) {
+ prevPointer = lastGroupLastEnabledOption.value
+
+ if (prevPointer === undefined) {
+ prevPointer = lastGroup.value
+ }
+ }
+
+ setPointer(prevPointer || null)
+ }
+ else if (groupped.value && canPointGroups.value) {
+ let prevPointer = isPointerGroup.value ? prevGroupLastEnabledOption.value : currentGroupPrevEnabledOption.value
+
+ if (prevPointer === undefined) {
+ prevPointer = isPointerGroup.value ? prevGroup.value : currentGroup.value
+
+ if (prevPointer.__CREATE__) {
+ prevPointer = prevGroupLastEnabledOption.value
+
+ if (prevPointer === undefined) {
+ prevPointer = prevGroup.value
+ }
+ }
+ }
+
+ setPointer(prevPointer || /* istanbul ignore next */ null)
+ } else {
+ let prevIndex = options.value.map(o => o[valueProp.value]).indexOf(pointer.value[valueProp.value]) - 1
+
+ if (prevIndex < 0) {
+ prevIndex = options.value.length - 1
+ }
+
+ setPointer(options.value[prevIndex] || null)
+ }
+
+ nextTick(() => {
+ adjustWrapperScrollToPointer()
+ })
+ }
+
+ const getParentGroup = (option) => {
+ return groups.value.find((group) => {
+ return group.__VISIBLE__.map(o => o[valueProp.value]).indexOf(option[valueProp.value]) !== -1
+ })
+ }
+
+ // no export
+ /* istanbul ignore next */
+ const adjustWrapperScrollToPointer = () => {
+ let pointedOption = multiselect.value.querySelector(`[data-pointed]`)
+
+ if (!pointedOption) {
+ return
+ }
+
+ let wrapper = pointedOption.parentElement.parentElement
+
+ if (groupped.value) {
+ wrapper = isPointerGroup.value
+ ? pointedOption.parentElement.parentElement.parentElement
+ : pointedOption.parentElement.parentElement.parentElement.parentElement
+ }
+
+ if (pointedOption.offsetTop + pointedOption.offsetHeight > wrapper.clientHeight + wrapper.scrollTop) {
+ wrapper.scrollTop = pointedOption.offsetTop + pointedOption.offsetHeight - wrapper.clientHeight
+ }
+
+ if (pointedOption.offsetTop < wrapper.scrollTop) {
+ wrapper.scrollTop = pointedOption.offsetTop
+ }
+ }
+
+ // ============== WATCHERS ==============
+
+ watch(search, (val) => {
+ if (searchable.value) {
+ if (val.length && showOptions.value) {
+ setPointerFirst()
+ } else {
+ clearPointer()
+ }
+ }
+ })
+
+ watch(isOpen, (val) => {
+ if (val) {
+ let firstSelected = multiselect.value.querySelectorAll(`[data-selected]`)[0]
+
+ if (!firstSelected) {
+ return
+ }
+
+ let wrapper = firstSelected.parentElement.parentElement
+
+ nextTick(() => {
+ /* istanbul ignore next */
+ if (wrapper.scrollTop > 0) {
+ return
+ }
+
+ wrapper.scrollTop = firstSelected.offsetTop
+ })
+ }
+ })
+
+ return {
+ pointer,
+ canPointGroups,
+ isPointed,
+ setPointerFirst,
+ selectPointer,
+ forwardPointer,
+ backwardPointer,
+ }
+}
\ No newline at end of file
diff --git a/Tithe-Vue/src/components/MultiSelectBox/composables/useScroll.js b/Tithe-Vue/src/components/MultiSelectBox/composables/useScroll.js
new file mode 100644
index 0000000..e0a4815
--- /dev/null
+++ b/Tithe-Vue/src/components/MultiSelectBox/composables/useScroll.js
@@ -0,0 +1,99 @@
+import { toRefs, watch, nextTick, onMounted, ref, computed } from 'vue'
+
+export default function useScroll (props, context, dep)
+{
+ const {
+ limit, infinite,
+ } = toRefs(props)
+
+ // ============ DEPENDENCIES ============
+
+ const isOpen = dep.isOpen
+ const offset = dep.offset
+ const search = dep.search
+ const pfo = dep.pfo
+ const eo = dep.eo
+
+ // ================ DATA ================
+
+ // no export
+ const observer = ref(null)
+
+ const infiniteLoader = ref(null)
+
+ // ============== COMPUTED ==============
+
+ const hasMore = computed(() => {
+ return offset.value < pfo.value.length
+ })
+
+ // =============== METHODS ==============
+
+ // no export
+ /* istanbul ignore next */
+ const handleIntersectionObserver = (entries) => {
+ const { isIntersecting, target } = entries[0]
+
+ if (isIntersecting) {
+ const parent = target.offsetParent
+ const scrollTop = parent.scrollTop
+
+ offset.value += limit.value == -1 ? 10 : limit.value
+
+ nextTick(() => {
+ parent.scrollTop = scrollTop
+ })
+ }
+ }
+
+ const observe = () => {
+ /* istanbul ignore else */
+ if (isOpen.value && offset.value < pfo.value.length) {
+ observer.value.observe(infiniteLoader.value)
+ } else if (!isOpen.value && observer.value) {
+ observer.value.disconnect()
+ }
+ }
+
+ // ============== WATCHERS ==============
+
+ watch(isOpen, () => {
+ if (!infinite.value) {
+ return
+ }
+
+ observe()
+ })
+
+ watch(search, () => {
+ if (!infinite.value) {
+ return
+ }
+
+ offset.value = limit.value
+
+ observe()
+ }, { flush: 'post' })
+
+ watch(eo, () => {
+ if (!infinite.value) {
+ return
+ }
+
+ observe()
+ }, { immediate: false, flush: 'post' })
+
+ // ================ HOOKS ===============
+
+ onMounted(() => {
+ /* istanbul ignore else */
+ if (window && window.IntersectionObserver) {
+ observer.value = new IntersectionObserver(handleIntersectionObserver)
+ }
+ })
+
+ return {
+ hasMore,
+ infiniteLoader,
+ }
+}
\ No newline at end of file
diff --git a/Tithe-Vue/src/components/MultiSelectBox/composables/useSearch.js b/Tithe-Vue/src/components/MultiSelectBox/composables/useSearch.js
new file mode 100644
index 0000000..9887d29
--- /dev/null
+++ b/Tithe-Vue/src/components/MultiSelectBox/composables/useSearch.js
@@ -0,0 +1,81 @@
+import { ref, getCurrentInstance, watch, toRefs } from 'vue'
+
+export default function useSearch (props, context, dep)
+{
+ const { regex } = toRefs(props)
+
+ const $this = getCurrentInstance().proxy
+
+ // ============ DEPENDENCIES ============
+
+ const isOpen = dep.isOpen
+ const open = dep.open
+
+ // ================ DATA ================
+
+ const search = ref(null)
+
+ const input = ref(null)
+
+ // =============== METHODS ==============
+
+ const clearSearch = () => {
+ search.value = ''
+ }
+
+ const handleSearchInput = (e) => {
+ search.value = e.target.value
+ }
+
+ const handleKeypress = (e) => {
+ if (regex && regex.value) {
+ let regexp = regex.value
+
+ if (typeof regexp === 'string') {
+ regexp = new RegExp(regexp)
+ }
+
+ if (!e.key.match(regexp)) {
+ e.preventDefault()
+ }
+ }
+ }
+
+ const handlePaste = (e) => {
+ if (regex && regex.value) {
+ let clipboardData = e.clipboardData || /* istanbul ignore next */ window.clipboardData
+ let pastedData = clipboardData.getData('Text')
+
+ let regexp = regex.value
+
+ if (typeof regexp === 'string') {
+ regexp = new RegExp(regexp)
+ }
+
+ if (!pastedData.split('').every(c => !!c.match(regexp))) {
+ e.preventDefault()
+ }
+ }
+
+ context.emit('paste', e, $this)
+ }
+
+ // ============== WATCHERS ==============
+
+ watch(search, (val) => {
+ if (!isOpen.value && val) {
+ open()
+ }
+
+ context.emit('search-change', val, $this)
+ })
+
+ return {
+ search,
+ input,
+ clearSearch,
+ handleSearchInput,
+ handleKeypress,
+ handlePaste,
+ }
+}
diff --git a/Tithe-Vue/src/components/MultiSelectBox/composables/useValue.js b/Tithe-Vue/src/components/MultiSelectBox/composables/useValue.js
new file mode 100644
index 0000000..779ca54
--- /dev/null
+++ b/Tithe-Vue/src/components/MultiSelectBox/composables/useValue.js
@@ -0,0 +1,34 @@
+import { computed, toRefs, ref } from 'vue'
+
+export default function useValue (props, context)
+{
+ const { value, modelValue, mode, valueProp } = toRefs(props)
+
+ // ================ DATA ================
+
+ // internalValue
+ const iv = ref(mode.value !== 'single' ? [] : {})
+
+ // ============== COMPUTED ==============
+
+ /* istanbul ignore next */
+ // externalValue
+ const ev = modelValue && modelValue.value !== undefined ? modelValue : value
+
+ const plainValue = computed(() => {
+ return mode.value === 'single' ? iv.value[valueProp.value] : iv.value.map(v=>v[valueProp.value])
+ })
+
+ const textValue = computed(() => {
+ return mode.value !== 'single' ? iv.value.map(v=>v[valueProp.value]).join(',') : iv.value[valueProp.value]
+ })
+
+ return {
+ iv,
+ internalValue: iv,
+ ev,
+ externalValue: ev,
+ textValue,
+ plainValue,
+ }
+}
\ No newline at end of file
diff --git a/Tithe-Vue/src/components/MultiSelectBox/default.css b/Tithe-Vue/src/components/MultiSelectBox/default.css
new file mode 100644
index 0000000..7fdf03c
--- /dev/null
+++ b/Tithe-Vue/src/components/MultiSelectBox/default.css
@@ -0,0 +1 @@
+.multiselect{align-items:center;background:var(--ms-bg,#fff);border:var(--ms-border-width,1px) solid var(--ms-border-color,#d1d5db);border-radius:var(--ms-radius,4px);box-sizing:border-box;cursor:pointer;display:flex;font-size:var(--ms-font-size,1rem);justify-content:flex-end;margin:0 auto;min-height:calc(var(--ms-border-width, 1px)*2 + var(--ms-font-size, 1rem)*var(--ms-line-height, 1.375) + var(--ms-py, .5rem)*2);outline:none;position:relative;width:100%}.multiselect.is-open{border-radius:var(--ms-radius,4px) var(--ms-radius,4px) 0 0}.multiselect.is-open-top{border-radius:0 0 var(--ms-radius,4px) var(--ms-radius,4px)}.multiselect.is-disabled{background:var(--ms-bg-disabled,#f3f4f6);cursor:default}.multiselect.is-active{border:var(--ms-border-width-active,var(--ms-border-width,1px)) solid var(--ms-border-color-active,var(--ms-border-color,#d1d5db));box-shadow:0 0 0 var(--ms-ring-width,3px) var(--ms-ring-color,rgba(16,185,129,.188))}.multiselect-wrapper{align-items:center;box-sizing:border-box;cursor:pointer;display:flex;justify-content:flex-end;margin:0 auto;min-height:calc(var(--ms-border-width, 1px)*2 + var(--ms-font-size, 1rem)*var(--ms-line-height, 1.375) + var(--ms-py, .5rem)*2);outline:none;position:relative;width:100%}.multiselect-multiple-label,.multiselect-placeholder,.multiselect-single-label{align-items:center;background:transparent;box-sizing:border-box;display:flex;height:100%;left:0;line-height:var(--ms-line-height,1.375);max-width:100%;padding-left:var(--ms-px,.875rem);padding-right:calc(1.25rem + var(--ms-px, .875rem)*3);pointer-events:none;position:absolute;top:0}.multiselect-placeholder{color:var(--ms-placeholder-color,#9ca3af)}.multiselect-single-label-text{display:block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.multiselect-search{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:var(--ms-bg,#fff);border:0;border-radius:var(--ms-radius,4px);bottom:0;box-sizing:border-box;font-family:inherit;font-size:inherit;height:100%;left:0;outline:none;padding-left:var(--ms-px,.875rem);position:absolute;right:0;top:0;width:100%}.multiselect-search::-webkit-search-cancel-button,.multiselect-search::-webkit-search-decoration,.multiselect-search::-webkit-search-results-button,.multiselect-search::-webkit-search-results-decoration{-webkit-appearance:none}.multiselect-tags{align-items:center;display:flex;flex-grow:1;flex-shrink:1;flex-wrap:wrap;margin:var(--ms-tag-my,.25rem) 0 0;padding-left:var(--ms-py,.5rem)}.multiselect-tag{align-items:center;background:var(--ms-tag-bg,#10b981);border-radius:var(--ms-tag-radius,4px);color:var(--ms-tag-color,#fff);display:flex;font-size:var(--ms-tag-font-size,.875rem);font-weight:var(--ms-tag-font-weight,600);line-height:var(--ms-tag-line-height,1.25rem);margin-bottom:var(--ms-tag-my,.25rem);margin-right:var(--ms-tag-mx,.25rem);padding:var(--ms-tag-py,.125rem) 0 var(--ms-tag-py,.125rem) var(--ms-tag-px,.5rem);white-space:nowrap}.multiselect-tag.is-disabled{background:var(--ms-tag-bg-disabled,#9ca3af);color:var(--ms-tag-color-disabled,#fff);padding-right:var(--ms-tag-px,.5rem)}.multiselect-tag-remove{align-items:center;border-radius:var(--ms-tag-remove-radius,4px);display:flex;justify-content:center;margin:var(--ms-tag-remove-my,0) var(--ms-tag-remove-mx,.125rem);padding:var(--ms-tag-remove-py,.25rem) var(--ms-tag-remove-px,.25rem)}.multiselect-tag-remove:hover{background:rgba(0,0,0,.063)}.multiselect-tag-remove-icon{background-color:currentColor;display:inline-block;height:.75rem;-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 320 512' fill='currentColor' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m207.6 256 107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 320 512' fill='currentColor' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m207.6 256 107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z'/%3E%3C/svg%3E");-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;opacity:.8;width:.75rem}.multiselect-tags-search-wrapper{display:inline-block;flex-grow:1;flex-shrink:1;height:100%;margin:0 var(--ms-tag-mx,4px) var(--ms-tag-my,4px);position:relative}.multiselect-tags-search-copy{display:inline-block;height:1px;visibility:hidden;white-space:pre-wrap;width:100%}.multiselect-tags-search{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:0;bottom:0;box-sizing:border-box;font-family:inherit;font-size:inherit;left:0;outline:none;padding:0;position:absolute;right:0;top:0;width:100%}.multiselect-tags-search::-webkit-search-cancel-button,.multiselect-tags-search::-webkit-search-decoration,.multiselect-tags-search::-webkit-search-results-button,.multiselect-tags-search::-webkit-search-results-decoration{-webkit-appearance:none}.multiselect-inifite{align-items:center;display:flex;justify-content:center;min-height:calc(var(--ms-border-width, 1px)*2 + var(--ms-font-size, 1rem)*var(--ms-line-height, 1.375) + var(--ms-py, .5rem)*2);width:100%}.multiselect-inifite-spinner,.multiselect-spinner{animation:multiselect-spin 1s linear infinite;background-color:var(--ms-spinner-color,#10b981);flex-grow:0;flex-shrink:0;height:1rem;-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 512 512' fill='currentColor' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m456.433 371.72-27.79-16.045c-7.192-4.152-10.052-13.136-6.487-20.636 25.82-54.328 23.566-118.602-6.768-171.03-30.265-52.529-84.802-86.621-144.76-91.424C262.35 71.922 256 64.953 256 56.649V24.56c0-9.31 7.916-16.609 17.204-15.96 81.795 5.717 156.412 51.902 197.611 123.408 41.301 71.385 43.99 159.096 8.042 232.792-4.082 8.369-14.361 11.575-22.424 6.92z'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 512 512' fill='currentColor' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m456.433 371.72-27.79-16.045c-7.192-4.152-10.052-13.136-6.487-20.636 25.82-54.328 23.566-118.602-6.768-171.03-30.265-52.529-84.802-86.621-144.76-91.424C262.35 71.922 256 64.953 256 56.649V24.56c0-9.31 7.916-16.609 17.204-15.96 81.795 5.717 156.412 51.902 197.611 123.408 41.301 71.385 43.99 159.096 8.042 232.792-4.082 8.369-14.361 11.575-22.424 6.92z'/%3E%3C/svg%3E");-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:1rem;z-index:10}.multiselect-spinner{margin:0 var(--ms-px,.875rem) 0 0}.multiselect-clear{display:flex;flex-grow:0;flex-shrink:0;opacity:1;padding:0 var(--ms-px,.875rem) 0 0;position:relative;transition:.3s;z-index:10}.multiselect-clear:hover .multiselect-clear-icon{background-color:var(--ms-clear-color-hover,#000)}.multiselect-clear-icon{background-color:var(--ms-clear-color,#999);display:inline-block;-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 320 512' fill='currentColor' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m207.6 256 107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 320 512' fill='currentColor' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m207.6 256 107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z'/%3E%3C/svg%3E");transition:.3s}.multiselect-caret,.multiselect-clear-icon{height:1.125rem;-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:.625rem}.multiselect-caret{background-color:var(--ms-caret-color,#999);flex-grow:0;flex-shrink:0;margin:0 var(--ms-px,.875rem) 0 0;-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 320 512' fill='currentColor' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 320 512' fill='currentColor' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z'/%3E%3C/svg%3E");pointer-events:none;position:relative;transform:rotate(0deg);transition:transform .3s;z-index:10}.multiselect-caret.is-open{pointer-events:auto;transform:rotate(180deg)}.multiselect-dropdown{-webkit-overflow-scrolling:touch;background:var(--ms-dropdown-bg,#fff);border:var(--ms-dropdown-border-width,1px) solid var(--ms-dropdown-border-color,#d1d5db);border-radius:0 0 var(--ms-dropdown-radius,4px) var(--ms-dropdown-radius,4px);bottom:0;display:flex;flex-direction:column;left:calc(var(--ms-border-width, 1px)*-1);margin-top:calc(var(--ms-border-width, 1px)*-1);max-height:var(--ms-max-height,10rem);outline:none;overflow-y:scroll;position:absolute;right:calc(var(--ms-border-width, 1px)*-1);transform:translateY(100%);z-index:100}.multiselect-dropdown.is-top{border-radius:var(--ms-dropdown-radius,4px) var(--ms-dropdown-radius,4px) 0 0;bottom:auto;top:var(--ms-border-width,1px);transform:translateY(-100%)}.multiselect-dropdown.is-hidden{display:none}.multiselect-options{display:flex;flex-direction:column;list-style:none;margin:0;padding:0}.multiselect-group{margin:0;padding:0}.multiselect-group-label{align-items:center;background:var(--ms-group-label-bg,#e5e7eb);box-sizing:border-box;color:var(--ms-group-label-color,#374151);cursor:default;display:flex;font-size:.875rem;font-weight:600;justify-content:flex-start;line-height:var(--ms-group-label-line-height,1.375);padding:var(--ms-group-label-py,.3rem) var(--ms-group-label-px,.75rem);text-align:left;text-decoration:none}.multiselect-group-label.is-pointable{cursor:pointer}.multiselect-group-label.is-pointed{background:var(--ms-group-label-bg-pointed,#d1d5db);color:var(--ms-group-label-color-pointed,#374151)}.multiselect-group-label.is-selected{background:var(--ms-group-label-bg-selected,#059669);color:var(--ms-group-label-color-selected,#fff)}.multiselect-group-label.is-disabled{background:var(--ms-group-label-bg-disabled,#f3f4f6);color:var(--ms-group-label-color-disabled,#d1d5db);cursor:not-allowed}.multiselect-group-label.is-selected.is-pointed{background:var(--ms-group-label-bg-selected-pointed,#0c9e70);color:var(--ms-group-label-color-selected-pointed,#fff)}.multiselect-group-label.is-selected.is-disabled{background:var(--ms-group-label-bg-selected-disabled,#75cfb1);color:var(--ms-group-label-color-selected-disabled,#d1fae5)}.multiselect-group-options{margin:0;padding:0}.multiselect-option{align-items:center;box-sizing:border-box;cursor:pointer;display:flex;font-size:var(--ms-option-font-size,1rem);justify-content:flex-start;line-height:var(--ms-option-line-height,1.375);padding:var(--ms-option-py,.5rem) var(--ms-option-px,.75rem);text-align:left;text-decoration:none}.multiselect-option.is-pointed{background:var(--ms-option-bg-pointed,#f3f4f6);color:var(--ms-option-color-pointed,#1f2937)}.multiselect-option.is-selected{background:var(--ms-option-bg-selected,#10b981);color:var(--ms-option-color-selected,#fff)}.multiselect-option.is-disabled{background:var(--ms-option-bg-disabled,#fff);color:var(--ms-option-color-disabled,#d1d5db);cursor:not-allowed}.multiselect-option.is-selected.is-pointed{background:var(--ms-option-bg-selected-pointed,#26c08e);color:var(--ms-option-color-selected-pointed,#fff)}.multiselect-option.is-selected.is-disabled{background:var(--ms-option-bg-selected-disabled,#87dcc0);color:var(--ms-option-color-selected-disabled,#d1fae5)}.multiselect-no-options,.multiselect-no-results{color:var(--ms-empty-color,#4b5563);padding:var(--ms-option-py,.5rem) var(--ms-option-px,.75rem)}.multiselect-fake-input{background:transparent;border:0;bottom:-1px;font-size:0;height:1px;left:0;outline:none;padding:0;position:absolute;right:0;width:100%}.multiselect-fake-input:active,.multiselect-fake-input:focus{outline:none}.multiselect-assistive-text{clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.multiselect-spacer{display:none}[dir=rtl] .multiselect-multiple-label,[dir=rtl] .multiselect-placeholder,[dir=rtl] .multiselect-single-label{left:auto;padding-left:calc(1.25rem + var(--ms-px, .875rem)*3);padding-right:var(--ms-px,.875rem);right:0}[dir=rtl] .multiselect-search{padding-left:0;padding-right:var(--ms-px,.875rem)}[dir=rtl] .multiselect-tags{padding-left:0;padding-right:var(--ms-py,.5rem)}[dir=rtl] .multiselect-tag{margin-left:var(--ms-tag-mx,.25rem);margin-right:0;padding:var(--ms-tag-py,.125rem) var(--ms-tag-px,.5rem) var(--ms-tag-py,.125rem) 0}[dir=rtl] .multiselect-tag.is-disabled{padding-left:var(--ms-tag-px,.5rem)}[dir=rtl] .multiselect-caret,[dir=rtl] .multiselect-spinner{margin:0 0 0 var(--ms-px,.875rem)}[dir=rtl] .multiselect-clear{padding:0 0 0 var(--ms-px,.875rem)}@keyframes multiselect-spin{0%{transform:rotate(0)}to{transform:rotate(1turn)}}
\ No newline at end of file
diff --git a/Tithe-Vue/src/components/MultiSelectBox/utils/arraysEqual.js b/Tithe-Vue/src/components/MultiSelectBox/utils/arraysEqual.js
new file mode 100644
index 0000000..9db76e5
--- /dev/null
+++ b/Tithe-Vue/src/components/MultiSelectBox/utils/arraysEqual.js
@@ -0,0 +1,7 @@
+export default function arraysEqual (array1, array2) {
+ const array2Sorted = array2.slice().sort()
+
+ return array1.length === array2.length && array1.slice().sort().every(function(value, index) {
+ return value === array2Sorted[index];
+ })
+}
\ No newline at end of file
diff --git a/Tithe-Vue/src/components/MultiSelectBox/utils/isNullish.js b/Tithe-Vue/src/components/MultiSelectBox/utils/isNullish.js
new file mode 100644
index 0000000..5bb4900
--- /dev/null
+++ b/Tithe-Vue/src/components/MultiSelectBox/utils/isNullish.js
@@ -0,0 +1,3 @@
+export default function isNullish (val) {
+ return [null, undefined].indexOf(val) !== -1
+}
\ No newline at end of file
diff --git a/Tithe-Vue/src/components/MultiSelectBox/utils/isObject.js b/Tithe-Vue/src/components/MultiSelectBox/utils/isObject.js
new file mode 100644
index 0000000..b594b15
--- /dev/null
+++ b/Tithe-Vue/src/components/MultiSelectBox/utils/isObject.js
@@ -0,0 +1,3 @@
+export default function isObject (variable) {
+ return Object.prototype.toString.call(variable) === '[object Object]'
+}
\ No newline at end of file
diff --git a/Tithe-Vue/src/components/MultiSelectBox/utils/normalize.js b/Tithe-Vue/src/components/MultiSelectBox/utils/normalize.js
new file mode 100644
index 0000000..6024aeb
--- /dev/null
+++ b/Tithe-Vue/src/components/MultiSelectBox/utils/normalize.js
@@ -0,0 +1,11 @@
+export default function normalize (str, strict = true) {
+ return strict
+ ? String(str).toLowerCase().trim()
+ : String(str).toLowerCase()
+ .normalize('NFD')
+ .trim()
+ .replace(new RegExp(/æ/g), 'ae')
+ .replace(new RegExp(/œ/g), 'oe')
+ .replace(new RegExp(/ø/g), 'o')
+ .replace(/\p{Diacritic}/gu, '')
+}
\ No newline at end of file
diff --git a/Tithe-Vue/src/components/MultiSelectBox/utils/resolveDeps.js b/Tithe-Vue/src/components/MultiSelectBox/utils/resolveDeps.js
new file mode 100644
index 0000000..c0d8557
--- /dev/null
+++ b/Tithe-Vue/src/components/MultiSelectBox/utils/resolveDeps.js
@@ -0,0 +1,14 @@
+export default function (props, context, features, deps = {}) {
+ features.forEach((composable) => {
+ /* istanbul ignore else */
+ if (composable) {
+ deps = {
+ ...deps,
+ ...composable(props, context, deps)
+ }
+ }
+
+ })
+
+ return deps
+}
\ No newline at end of file
diff --git a/Tithe-Vue/src/components/SearchBoxes/ForaneSingleSelectBox.vue b/Tithe-Vue/src/components/SearchBoxes/ForaneSingleSelectBox.vue
new file mode 100644
index 0000000..841c70c
--- /dev/null
+++ b/Tithe-Vue/src/components/SearchBoxes/ForaneSingleSelectBox.vue
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
diff --git a/Tithe-Vue/src/components/SearchBoxes/KoottaymaByParishSingleSelectBox.vue b/Tithe-Vue/src/components/SearchBoxes/KoottaymaByParishSingleSelectBox.vue
new file mode 100644
index 0000000..686878a
--- /dev/null
+++ b/Tithe-Vue/src/components/SearchBoxes/KoottaymaByParishSingleSelectBox.vue
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
diff --git a/Tithe-Vue/src/components/SearchBoxes/ParishByForaneSingleSelectBox.vue b/Tithe-Vue/src/components/SearchBoxes/ParishByForaneSingleSelectBox.vue
new file mode 100644
index 0000000..8c23ac6
--- /dev/null
+++ b/Tithe-Vue/src/components/SearchBoxes/ParishByForaneSingleSelectBox.vue
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
diff --git a/Tithe-Vue/src/components/SearchBoxes/SingleSelectBox.vue b/Tithe-Vue/src/components/SearchBoxes/SingleSelectBox.vue
new file mode 100644
index 0000000..4def614
--- /dev/null
+++ b/Tithe-Vue/src/components/SearchBoxes/SingleSelectBox.vue
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
diff --git a/Tithe-Vue/src/externalized-data/graphqlQueries.js b/Tithe-Vue/src/externalized-data/graphqlQueries.js
index 5ae1aeb..bd43e51 100644
--- a/Tithe-Vue/src/externalized-data/graphqlQueries.js
+++ b/Tithe-Vue/src/externalized-data/graphqlQueries.js
@@ -87,6 +87,11 @@ export const foraneAllForaneListQuery = `query foranePageActiveForane{
getAllForanes{
foraneId
foraneName
+ address{
+ street{
+ streetName
+ }
+ }
}
}`;
@@ -162,6 +167,11 @@ export const parishAllParishListQuery = `query parishPageActiveParish ($foraneId
getAllParishesByForane (foraneId: $foraneId){
parishId
parishName
+ address{
+ street{
+ streetName
+ }
+ }
}
}`;
diff --git a/Tithe-Vue/src/main.js b/Tithe-Vue/src/main.js
index f8cbdc2..e75c9b4 100644
--- a/Tithe-Vue/src/main.js
+++ b/Tithe-Vue/src/main.js
@@ -14,6 +14,8 @@ import { useStyleStore } from "@/stores/style.js";
// import { darkModeKey, styleKey } from "@/config.js";
import { styleKey } from "@/config.js";
+import Multiselect from "@/components/MultiSelectBox/Multiselect.vue";
+
import "./css/main.css";
/* Init Pinia */
@@ -44,7 +46,7 @@ const app = createApp({
/* Create Vue app */
// createApp(App).use(router).use(pinia).mount("#app");
-app.use(router).use(pinia).mount("#app");
+app.use(router).use(pinia).component("Multiselect", Multiselect).mount("#app");
/* Init Pinia stores */
const mainStore = useMainStore(pinia);
diff --git a/Tithe-Vue/src/views/FamilyView.vue b/Tithe-Vue/src/views/FamilyView.vue
index 769be9d..5253c16 100644
--- a/Tithe-Vue/src/views/FamilyView.vue
+++ b/Tithe-Vue/src/views/FamilyView.vue
@@ -17,6 +17,8 @@ import {
} from "@mdi/js";
import SearchBox from "@/components/SearchBox.vue";
+import ForaneSingleSelectBox from "@/components/SearchBoxes/ForaneSingleSelectBox.vue";
+import ParishByForaneSingleSelectBox from "@/components/SearchBoxes/ParishByForaneSingleSelectBox.vue";
import LayoutAuthenticated from "@/layouts/LayoutAuthenticated.vue";
import SectionMain from "@/components/SectionMain.vue";
import FormField from "@/components/FormField.vue";
diff --git a/Tithe-Vue/src/views/ForaneView.vue b/Tithe-Vue/src/views/ForaneView.vue
index fc85861..a359f79 100644
--- a/Tithe-Vue/src/views/ForaneView.vue
+++ b/Tithe-Vue/src/views/ForaneView.vue
@@ -19,7 +19,7 @@ import {
mdiTableLarge,
} from "@mdi/js";
-import SearchBox from "@/components/SearchBox.vue";
+import ForaneSingleSelectBox from "@/components/SearchBoxes/ForaneSingleSelectBox.vue";
import LayoutAuthenticated from "@/layouts/LayoutAuthenticated.vue";
import SectionMain from "@/components/SectionMain.vue";
import FormField from "@/components/FormField.vue";
@@ -91,25 +91,8 @@ const tableTabTitle = foranePageTableTabTitle;
const forane = ref();
-const ACTIVE_FORANE_LIST_QUERY = gql`
- ${foraneAllForaneListQuery}
-`;
-
-const {
- result: activeForaneList,
- load: activeForaneListLoad,
- refetch: activeForaneListRefetch,
-} = useLazyQuery(ACTIVE_FORANE_LIST_QUERY);
-activeForaneListLoad();
-const loadForanes = (query, setOptions) => {
- setOptions(
- activeForaneList.value?.getAllForanes?.map((entity) => {
- return {
- id: entity.foraneId,
- label: entity.foraneName,
- };
- }) ?? []
- );
+const changeInForane = (entity) => {
+ forane.value = entity;
};
// Entity Count in Forane Page
@@ -346,7 +329,7 @@ const getActivePersonRows = computed(() => {
Forane
-
+
+
+
@@ -375,6 +360,7 @@ const getActivePersonRows = computed(() => {
v-model="createForaneForm.foraneName"
type="text"
:icon="mdiChurch"
+ :borderless="true"
placeholder="St. Peter's Church"
/>
@@ -387,6 +373,7 @@ const getActivePersonRows = computed(() => {
@@ -562,6 +549,8 @@ const getActivePersonRows = computed(() => {
+
+
diff --git a/Tithe-Vue/src/views/KoottaymaView.vue b/Tithe-Vue/src/views/KoottaymaView.vue
index 1f82c7f..8bb89aa 100644
--- a/Tithe-Vue/src/views/KoottaymaView.vue
+++ b/Tithe-Vue/src/views/KoottaymaView.vue
@@ -17,6 +17,9 @@ import {
mdiTableLarge,
} from "@mdi/js";
+import ForaneSingleSelectBox from "@/components/SearchBoxes/ForaneSingleSelectBox.vue";
+import ParishByForaneSingleSelectBox from "@/components/SearchBoxes/ParishByForaneSingleSelectBox.vue";
+import KoottaymaByParishSingleSelectBox from "@/components/SearchBoxes/KoottaymaByParishSingleSelectBox.vue";
import SearchBox from "@/components/SearchBox.vue";
import LayoutAuthenticated from "@/layouts/LayoutAuthenticated.vue";
import SectionMain from "@/components/SectionMain.vue";
@@ -80,69 +83,16 @@ const forane = ref();
const parish = ref();
const koottayma = ref();
-const ACTIVE_FORANE_LIST_QUERY = gql`
- ${koottaymaAllForaneListQuery}
-`;
-
-const {
- result: activeForaneList,
- load: activeForaneListLoad,
- refetch: activeForaneListRefetch,
-} = useLazyQuery(ACTIVE_FORANE_LIST_QUERY);
-activeForaneListLoad();
-const loadForanes = (query, setOptions) => {
- setOptions(
- activeForaneList.value?.getAllForanes?.map((entity) => {
- return {
- id: entity.foraneId,
- label: entity.foraneName,
- };
- }) ?? []
- );
+const changeInForane = (entity) => {
+ forane.value = entity;
};
-const ACTIVE_PARISH_BY_FORANE_LIST_QUERY = gql`
- ${koottaymaAllParishListQuery}
-`;
-
-const {
- result: activeParishList,
- load: activeParishListLoad,
- refetch: activeParishListRefetch,
-} = useLazyQuery(ACTIVE_PARISH_BY_FORANE_LIST_QUERY, () => ({
- foraneId: forane.value.id,
-}));
-const loadParishesByForane = (query, setOptions) => {
- setOptions(
- activeParishList.value?.getAllParishesByForane?.map((entity) => {
- return {
- id: entity.parishId,
- label: entity.parishName,
- };
- }) ?? []
- );
+const changeInParish = (entity) => {
+ parish.value = entity;
};
-const ACTIVE_KOOTTAYMA_BY_PARISH_LIST_QUERY = gql`
- ${koottaymaAllKoottaymaListQuery}
-`;
-
-const {
- result: activeKoottaymaList,
- load: activeKoottaymaListLoad,
- refetch: activeKoottaymaListRefetch,
-} = useLazyQuery(ACTIVE_KOOTTAYMA_BY_PARISH_LIST_QUERY, () => ({
- parishId: parish.value.id,
-}));
-const loadKoottaymasByParish = (query, setOptions) => {
- setOptions(
- activeKoottaymaList.value?.getAllKoottaymasByParish?.map((entity) => {
- return {
- id: entity.koottaymaId,
- label: entity.koottaymaName,
- };
- }) ?? []
- );
+const changeInKoottayma = (entity) => {
+ koottayma.value = entity;
};
// Entity Count in Koottayma Page
@@ -167,14 +117,6 @@ const activePersonCount = computed(
() => activeEntityByKoottaymaCount.value?.getPersonCountByKoottayma ?? 0
);
-watch(forane, () => {
- activeParishListLoad();
-});
-
-watch(parish, () => {
- activeKoottaymaListLoad();
-});
-
watch(koottayma, () => {
activeEntityByKoottaymaCountEnabled.value = true;
});
@@ -190,27 +132,13 @@ const formForane = ref();
// Form Parish Search Box
const formParish = ref();
-const {
- result: activeFormParishList,
- load: activeFormParishListLoad,
- refetch: activeFormParishListRefetch,
-} = useLazyQuery(ACTIVE_PARISH_BY_FORANE_LIST_QUERY, () => ({
- foraneId: formForane.value.id,
-}));
-const loadFormParishesByForane = (query, setOptions) => {
- setOptions(
- activeFormParishList.value?.getAllParishesByForane?.map((entity) => {
- return {
- id: entity.parishId,
- label: entity.parishName,
- };
- }) ?? []
- );
+const changeInFormForane = (entity) => {
+ formForane.value = entity;
};
-watch(formForane, () => {
- activeFormParishListLoad();
-});
+const changeInFormParish = (entity) => {
+ formParish.value = entity;
+};
watch(formParish, (value) => {
createKoottaymaForm.parishId = value.id;
@@ -372,32 +300,21 @@ const getActivePersonRows = computed(() => {
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
@@ -422,24 +339,19 @@ const getActivePersonRows = computed(() => {
placeholder="St. George Koottayma"
/>
-
-
-
-
-
-
+
+
+
+
{
.baseButtonStyle {
width: 100%;
}
+
+.multipleSelectAddressBox :deep(.multiselect-theme) {
+ --ms-bg: #1e293b;
+ --ms-dropdown-bg: #1e293b;
+ --ms-dropdown-border-color: #1e293b;
+
+ --ms-py: 0.757rem;
+}
diff --git a/Tithe-Vue/src/views/ParishView.vue b/Tithe-Vue/src/views/ParishView.vue
index 1a37455..fe5950c 100644
--- a/Tithe-Vue/src/views/ParishView.vue
+++ b/Tithe-Vue/src/views/ParishView.vue
@@ -18,7 +18,8 @@ import {
mdiTableLarge,
} from "@mdi/js";
-import SearchBox from "@/components/SearchBox.vue";
+import ForaneSingleSelectBox from "@/components/SearchBoxes/ForaneSingleSelectBox.vue";
+import ParishByForaneSingleSelectBox from "@/components/SearchBoxes/ParishByForaneSingleSelectBox.vue";
import LayoutAuthenticated from "@/layouts/LayoutAuthenticated.vue";
import SectionMain from "@/components/SectionMain.vue";
import FormField from "@/components/FormField.vue";
@@ -82,47 +83,12 @@ const tableTabTitle = parishPageTableTabTitle;
const forane = ref();
const parish = ref();
-const ACTIVE_FORANE_LIST_QUERY = gql`
- ${parishAllForaneListQuery}
-`;
-
-const {
- result: activeForaneList,
- load: activeForaneListLoad,
- refetch: activeForaneListRefetch,
-} = useLazyQuery(ACTIVE_FORANE_LIST_QUERY);
-activeForaneListLoad();
-const loadForanes = (query, setOptions) => {
- setOptions(
- activeForaneList.value?.getAllForanes?.map((entity) => {
- return {
- id: entity.foraneId,
- label: entity.foraneName,
- };
- }) ?? []
- );
+const changeInForane = (entity) => {
+ forane.value = entity;
};
-const ACTIVE_PARISH_BY_FORANE_LIST_QUERY = gql`
- ${parishAllParishListQuery}
-`;
-
-const {
- result: activeParishList,
- load: activeParishListLoad,
- refetch: activeParishListRefetch,
-} = useLazyQuery(ACTIVE_PARISH_BY_FORANE_LIST_QUERY, () => ({
- foraneId: forane.value.id,
-}));
-const loadParishesByForane = (query, setOptions) => {
- setOptions(
- activeParishList.value?.getAllParishesByForane?.map((entity) => {
- return {
- id: entity.parishId,
- label: entity.parishName,
- };
- }) ?? []
- );
+const changeInParish = (entity) => {
+ parish.value = entity;
};
// Entity Count in Parish Page
@@ -150,9 +116,9 @@ const activePersonCount = computed(
() => activeEntityByParishCount.value?.getPersonCountByParish ?? 0
);
-watch(forane, () => {
- activeParishListLoad();
-});
+// watch(forane, () => {
+// activeParishListLoad();
+// });
watch(parish, () => {
activeEntityByParishCountEnabled.value = true;
@@ -175,6 +141,10 @@ const createParishForm = reactive({
// Form Forane Search Box
const formForane = ref();
+const changeInFormForane = (entity) => {
+ formForane.value = entity;
+};
+
watch(formForane, (value) => {
createParishForm.foraneId = value.id;
});
@@ -302,6 +272,10 @@ const moveParishForm = reactive({
foraneId: "",
});
+const changeInNewForaneMove = (entity) => {
+ newForane.value = entity;
+};
+
watch(newForane, (value) => {
if (value.id === forane.value.id) {
newForane.value = "";
@@ -373,24 +347,13 @@ const getActivePersonRows = computed(() => {
@@ -411,6 +374,7 @@ const getActivePersonRows = computed(() => {
v-model="createParishForm.parishName"
type="text"
:icon="mdiChurch"
+ :borderless="true"
placeholder="St. Peter's Church"
/>
@@ -420,19 +384,16 @@ const getActivePersonRows = computed(() => {
v-model="createParishForm.address.buildingName"
/>
-->
-
-
-
+
@@ -459,15 +420,11 @@ const getActivePersonRows = computed(() => {
/>
-
-
-
+
{
.baseButtonStyle {
width: 100%;
}
+
+.multipleSelectAddressBox :deep(.multiselect-theme) {
+ --ms-bg: #1e293b;
+ --ms-dropdown-bg: #1e293b;
+ --ms-dropdown-border-color: #1e293b;
+
+ --ms-py: 0.757rem;
+}