overhaul modals from ground up
This commit is contained in:
parent
75df808dad
commit
97e733d58e
|
@ -0,0 +1,113 @@
|
||||||
|
<template>
|
||||||
|
<ModalPopup
|
||||||
|
:show="show ?? show_"
|
||||||
|
:fullWidth="opts_.fullWidth ?? fullWidth"
|
||||||
|
@close="if (opts_.clickAwayCancels ?? clickAwayCancels) respond(false);"
|
||||||
|
@after-leave="reset"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<slot name="header">
|
||||||
|
{{ headerText_ }}
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
<slot>
|
||||||
|
{{ bodyText_ }}
|
||||||
|
</slot>
|
||||||
|
<template #footer>
|
||||||
|
<slot name="footer">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-secondary"
|
||||||
|
@click="respond(false)"
|
||||||
|
>
|
||||||
|
{{ opts_.cancelText ?? cancelText }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
:class="['btn', (opts_.confirmDangerous ?? confirmDangerous) ? 'btn-danger' : 'btn-primary']"
|
||||||
|
:disabled="disabled"
|
||||||
|
@click="respond(true)"
|
||||||
|
>
|
||||||
|
{{ opts_.confirmText ?? confirmText }}
|
||||||
|
</button>
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
</ModalPopup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import ModalPopup from './ModalPopup.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
clickAwayCancels: Boolean,
|
||||||
|
confirmDangerous: Boolean,
|
||||||
|
cancelText: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: 'Cancel',
|
||||||
|
},
|
||||||
|
confirmText: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: 'OK',
|
||||||
|
},
|
||||||
|
fullWidth: Boolean,
|
||||||
|
disabled: Boolean,
|
||||||
|
},
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const show_ = ref(false);
|
||||||
|
const headerText_ = ref("");
|
||||||
|
const bodyText_ = ref("");
|
||||||
|
const opts_ = ref({});
|
||||||
|
const onReponse_ = ref(null);
|
||||||
|
const ask = (headerText, bodyText, opts = {}) => {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
opts_.value = opts;
|
||||||
|
headerText_.value = headerText;
|
||||||
|
bodyText_.value = bodyText;
|
||||||
|
onReponse_.value = resolve;
|
||||||
|
show_.value = true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const respond = (response) => {
|
||||||
|
onReponse_.value?.(response);
|
||||||
|
show_.value = false;
|
||||||
|
if (response)
|
||||||
|
emit('confirm');
|
||||||
|
else
|
||||||
|
emit('cancel');
|
||||||
|
}
|
||||||
|
const reset = () => {
|
||||||
|
headerText_.value = "";
|
||||||
|
bodyText_.value = "";
|
||||||
|
opts_.value = {};
|
||||||
|
onReponse_.value = null;
|
||||||
|
emit('after-leave');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
show_,
|
||||||
|
headerText_,
|
||||||
|
bodyText_,
|
||||||
|
opts_,
|
||||||
|
ask,
|
||||||
|
respond,
|
||||||
|
reset,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
ModalPopup,
|
||||||
|
},
|
||||||
|
emits: [
|
||||||
|
'cancel',
|
||||||
|
'confirm',
|
||||||
|
'after-leave',
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -16,114 +16,118 @@ If not, see <https://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<TransitionRoot
|
<transition
|
||||||
as="div"
|
enter-active-class="ease-out duration-500"
|
||||||
class="fixed inset-0 z-10 overflow-visible"
|
enter-from-class="opacity-0"
|
||||||
:show="showModal"
|
enter-to-class="opacity-100"
|
||||||
|
leave-active-class="ease-in duration-500"
|
||||||
|
leave-from-class="opacity-100"
|
||||||
|
leave-to-class="opacity-0"
|
||||||
>
|
>
|
||||||
<TransitionChild
|
|
||||||
as="template"
|
|
||||||
enter="ease-out duration-500"
|
|
||||||
enter-from="opacity-0"
|
|
||||||
enter-to="opacity-100"
|
|
||||||
leave="ease-in duration-500"
|
|
||||||
leave-from="opacity-100"
|
|
||||||
leave-to="opacity-0"
|
|
||||||
>
|
|
||||||
<div class="fixed z-10 inset-0 bg-neutral-500/75 dark:bg-black/50 transition-opacity pointer" />
|
|
||||||
</TransitionChild>
|
|
||||||
<div
|
<div
|
||||||
|
v-if="() => show ?? show_"
|
||||||
|
class="fixed z-10 inset-0 bg-neutral-500/75 dark:bg-black/50 transition-opacity pointer"
|
||||||
|
/>
|
||||||
|
</transition>
|
||||||
|
<transition
|
||||||
|
enter-active-class="ease-out duration-300"
|
||||||
|
enter-from-class="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-90"
|
||||||
|
enter-to-class="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
leave-active-class="ease-in duration-100"
|
||||||
|
leave-from-class="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
leave-to-class="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-75"
|
||||||
|
@after-leave="reset"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="() => show ?? show_"
|
||||||
class="fixed z-10 inset-0 overflow-hidden flex items-end sm:items-center justify-center px-4 pb-20 sm:p-0"
|
class="fixed z-10 inset-0 overflow-hidden flex items-end sm:items-center justify-center px-4 pb-20 sm:p-0"
|
||||||
@click.self="$emit('close')"
|
@click.self="close(false)"
|
||||||
>
|
>
|
||||||
<TransitionChild
|
<div
|
||||||
as="template"
|
:class="[fullWidth ? 'sm:max-w-full' : 'sm:max-w-lg', 'inline-flex flex-col items-stretch overflow-hidden transform transition-all text-left z-10']">
|
||||||
enter="ease-out duration-300"
|
<div class="block w-[512px]" /> <!-- set min width of div -->
|
||||||
enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-90"
|
<div class="card flex flex-col items-stretch overflow-hidden">
|
||||||
enter-to="opacity-100 translate-y-0 sm:scale-100"
|
<div class="card-header">
|
||||||
leave="ease-in duration-100"
|
<h3 class="text-header">
|
||||||
leave-from="opacity-100 translate-y-0 sm:scale-100"
|
|
||||||
leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-75"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
:class="[autoWidth ? 'sm:max-w-full' : 'sm:max-w-lg', 'inline-flex flex-col items-stretch overflow-hidden transform transition-all text-left z-10']">
|
|
||||||
<div class="block w-[512px]" /> <!-- set min width of div -->
|
|
||||||
<div class="card flex flex-col items-stretch overflow-hidden">
|
|
||||||
<div class="card-header">
|
|
||||||
<slot name="header">
|
<slot name="header">
|
||||||
<h3
|
{{ headerText_ }}
|
||||||
class="text-header"
|
</slot>
|
||||||
v-html="headerText"
|
</h3>
|
||||||
/>
|
</div>
|
||||||
|
<div class="card-body flex flex-row items-center gap-2">
|
||||||
|
<slot name="icon" />
|
||||||
|
<div class="grow overflow-x-auto">
|
||||||
|
<slot>
|
||||||
|
{{ bodyText_ }}
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body flex flex-row items-center gap-2">
|
</div>
|
||||||
<slot name="icon" />
|
<div class="card-footer w-full">
|
||||||
<div class="grow overflow-x-auto">
|
<div class="button-group-row justify-end overflow-x-auto">
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-footer button-group-row justify-end">
|
|
||||||
<slot name="footer">
|
<slot name="footer">
|
||||||
<button
|
<button
|
||||||
v-if="!noCancel"
|
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-secondary"
|
class="btn btn-primary"
|
||||||
@click="$emit('cancel'); $emit('close')"
|
@click="close(true)"
|
||||||
>
|
>
|
||||||
{{ cancelText }}
|
OK
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
:class="['btn', applyDangerous ? 'btn-danger' : 'btn-primary']"
|
|
||||||
:disabled="disableContinue"
|
|
||||||
@click="$emit('apply'); $emit('close')"
|
|
||||||
>
|
|
||||||
{{ applyText }}
|
|
||||||
</button>
|
</button>
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</TransitionChild>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</TransitionRoot>
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { TransitionChild, TransitionRoot } from '@headlessui/vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
showModal: Boolean,
|
show: {
|
||||||
noCancel: {
|
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
default: false,
|
default: null,
|
||||||
},
|
},
|
||||||
autoWidth: Boolean,
|
fullWidth: Boolean,
|
||||||
headerText: String,
|
|
||||||
cancelText: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
default: "Cancel",
|
|
||||||
},
|
|
||||||
applyText: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
default: "Apply",
|
|
||||||
},
|
|
||||||
applyDangerous: Boolean,
|
|
||||||
disableContinue: Boolean,
|
|
||||||
},
|
},
|
||||||
components: {
|
setup(props, { emit }) {
|
||||||
TransitionChild,
|
const show_ = ref(false);
|
||||||
TransitionRoot,
|
const headerText_ = ref("");
|
||||||
|
const bodyText_ = ref("");
|
||||||
|
const onClose_ = ref(null);
|
||||||
|
const open = (headerText, bodyText) => {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
headerText_.value = headerText;
|
||||||
|
bodyText_.value = bodyText;
|
||||||
|
onClose_.value = resolve;
|
||||||
|
show_.value = true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const close = (clickedOk) => {
|
||||||
|
onClose_.value?.(clickedOk);
|
||||||
|
show_.value = false;
|
||||||
|
emit('close');
|
||||||
|
};
|
||||||
|
const reset = () => {
|
||||||
|
headerText_.value = '';
|
||||||
|
bodyText_.value = '';
|
||||||
|
onClose_.value = null;
|
||||||
|
emit('after-leave');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
show_,
|
||||||
|
headerText_,
|
||||||
|
bodyText_,
|
||||||
|
open,
|
||||||
|
close,
|
||||||
|
reset,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
emits: [
|
emits: [
|
||||||
'apply',
|
|
||||||
'cancel',
|
|
||||||
'close',
|
'close',
|
||||||
|
'after-leave',
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,27 +1,55 @@
|
||||||
<template>
|
<template>
|
||||||
<ModalPopup
|
<ModalConfirm
|
||||||
:showModal="show"
|
:show="show ?? show_"
|
||||||
:headerText="headerText"
|
:clickAwayCancels="clickAwayCancels"
|
||||||
autoWidth
|
:confirmDangerous="opts_.confirmDangerous ?? confirmDangerous"
|
||||||
:disableContinue="!valid"
|
:cancelText="cancelText"
|
||||||
@cancel="cancel()"
|
:confirmText="confirmText"
|
||||||
@apply="apply()"
|
:fullWidth="fullWidth"
|
||||||
|
:disabled="!valid"
|
||||||
|
@cancel="cancel"
|
||||||
|
@confirm="confirm"
|
||||||
|
@after-leave="reset"
|
||||||
>
|
>
|
||||||
|
<template #header>
|
||||||
|
<slot name="header">
|
||||||
|
{{ headerText_ }}
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
<div v-if="inputs_.length === 0">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
v-model="defaultInputValue_"
|
||||||
|
class="w-full input-textlike"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-for="input in inputs"
|
v-else
|
||||||
|
v-for="input in inputs_"
|
||||||
:key="input.key"
|
:key="input.key"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
v-if="input.label"
|
v-if="input.label"
|
||||||
class="block text-label"
|
class="block text-label"
|
||||||
>{{ input.label }}</label>
|
>{{ input.label }}</label>
|
||||||
|
<template v-if="input.type === 'radio'">
|
||||||
|
<input
|
||||||
|
v-for="option in input.options"
|
||||||
|
v-bind="{ ...input.props }"
|
||||||
|
type="radio"
|
||||||
|
:value="option"
|
||||||
|
v-model="input.value"
|
||||||
|
ref="inputRefs_"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
<input
|
<input
|
||||||
:class="[!['button', 'checkbox', 'color', 'file', 'radio', 'range'].includes(input.type) ? 'input-textlike w-full' : input.type == 'checkbox' ? 'input-checkbox' : '']"
|
v-else
|
||||||
v-bind="{...input.props}"
|
v-bind="{ ...input.props }"
|
||||||
:type="input.type"
|
:type="input.type"
|
||||||
:value="output[input.key] ?? input.default"
|
|
||||||
:placeholder="input.placeholder"
|
:placeholder="input.placeholder"
|
||||||
@input="(event) => { output[input.key] = event.target.value; validate(); }"
|
v-model="input.value"
|
||||||
|
@change="input.feedback = input.validate(input.value)"
|
||||||
|
ref="inputRefs_"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="input.feedback"
|
v-if="input.feedback"
|
||||||
|
@ -31,100 +59,194 @@
|
||||||
<span class="text-feedback text-error">{{ input.feedback }}</span>
|
<span class="text-feedback text-error">{{ input.feedback }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ModalPopup>
|
</ModalConfirm>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { ref, computed, nextTick } from 'vue';
|
import { ref, nextTick } from 'vue';
|
||||||
import { ExclamationCircleIcon } from '@heroicons/vue/solid';
|
import { ExclamationCircleIcon } from '@heroicons/vue/solid';
|
||||||
import ModalPopup from './ModalPopup.vue';
|
import ModalConfirm from './ModalConfirm.vue';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @typedef {Object} ModalPromptInputObj
|
||||||
|
* @property {String} key - Key to index output object
|
||||||
|
* @property {String} label - Text to go in label
|
||||||
|
* @property {String} type - input tag type property value
|
||||||
|
* @property {T} value - current value of input to be v-modelled
|
||||||
|
* @property {String} placeholder - Placeholder text
|
||||||
|
* @property {ModalPromptInputValidationCallback<T>} validate - Validation function, return string if invalid or null if valid
|
||||||
|
* @property {String} feedback - Feedback if invalid
|
||||||
|
* @property {Object} props - Extra properties to v-bind to the input element
|
||||||
|
* @property {*[]|undefined} options - Array of option values for radio button input
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @callback ModalPromptInputValidationCallback
|
||||||
|
* @param {T} value - Current value of input to validate
|
||||||
|
* @returns {String|null} - Return null if valid or string explaining why it's invalid
|
||||||
|
*/
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
props: {
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
clickAwayCancels: Boolean,
|
||||||
|
confirmDangerous: Boolean,
|
||||||
|
cancelText: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: 'Cancel',
|
||||||
|
},
|
||||||
|
confirmText: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: 'OK',
|
||||||
|
},
|
||||||
|
fullWidth: Boolean,
|
||||||
|
},
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const show = ref(false);
|
const show_ = ref(false);
|
||||||
const headerText = ref("");
|
const headerText_ = ref("");
|
||||||
const inputs = ref([]);
|
const opts_ = ref({});
|
||||||
const output = ref({});
|
const inputs_ = ref([]);
|
||||||
const valid = ref(false);
|
const valid_ = ref(false);
|
||||||
const apply = ref(() => null);
|
const resolver_ = ref(null);
|
||||||
const cancel = ref(() => null);
|
const defaultInputRef_ = ref();
|
||||||
|
const defaultInputValue_ = ref("");
|
||||||
|
const inputRefs_ = ref([]);
|
||||||
|
|
||||||
/**
|
const ask = (headerText, inputs = [], opts = {}) => {
|
||||||
* Promise with method to add input to prompt
|
const prom = new Promise(resolve => {
|
||||||
* @typedef {Object} ModalPromptPromise
|
opts_.value = opts;
|
||||||
* @property {function} addInput
|
headerText_.value = headerText;
|
||||||
*/
|
inputs_.value = inputs;
|
||||||
|
resolver_.value = resolve;
|
||||||
/**
|
show_.value = true;
|
||||||
* Prompt user for input
|
nextTick(() => {
|
||||||
* @param {string} header - Text to display in popup header
|
if (inputs.length) {
|
||||||
* @returns {ModalPromptPromise} - Resolves object with responses or null if cancelled
|
inputRefs_.value[0].focus();
|
||||||
*/
|
} else {
|
||||||
const prompt = (header) => {
|
defaultInputRef_.value.focus();
|
||||||
headerText.value = header;
|
}
|
||||||
output.value = {};
|
});
|
||||||
inputs.value = [];
|
});
|
||||||
valid.value = false;
|
|
||||||
show.value = true;
|
|
||||||
const prom = new Promise((resolve, reject) => {
|
|
||||||
apply.value = () => resolve(output.value);
|
|
||||||
cancel.value = () => resolve(null);
|
|
||||||
}).finally(() => show.value = false);
|
|
||||||
/**
|
/**
|
||||||
* Callback to validate input, cannot be arrow function
|
* Add a text input
|
||||||
* @callback ModalPromptInputValidationCallback
|
* @param {String} key - Key to index output object
|
||||||
* @param {any} - value of input
|
* @param {String} label - Text to go in label
|
||||||
* @returns {boolean} - true if valid, false otherwise
|
* @param {String} defaultValue - Default value
|
||||||
|
* @param {String} placeholder - Placeholder text
|
||||||
|
* @param {ModalPromptInputValidationCallback<String>} validate - Input validation callback
|
||||||
|
* @param {Object} props - Extra properties to v-bind to the input element
|
||||||
*/
|
*/
|
||||||
/**
|
prom.addTextInput = (key, label, defaultValue, placeholder, validate = () => null, props = {}) => {
|
||||||
* Add an input to the prompt
|
/**
|
||||||
* @param {string} key - object key for result
|
* @type {ModalPromptInputObj<String>}
|
||||||
* @param {string} type - input tag type prop value
|
*/
|
||||||
* @param {string} label - label for input
|
|
||||||
* @param {string} placeholder - input placeholder if applicable
|
|
||||||
* @param {any} defaultValue - Default value of input
|
|
||||||
* @param {ModalPromptInputValidationCallback} validate - Validation callback for input
|
|
||||||
* @param {object} props - optional extra properties for input tag
|
|
||||||
* @returns {ModalPromptPromise} - Resolves object with responses or null if cancelled
|
|
||||||
*/
|
|
||||||
const addInput = (key, type, label, placeholder, defaultValue, validate, props) => {
|
|
||||||
const input = {
|
const input = {
|
||||||
key,
|
key,
|
||||||
type,
|
|
||||||
label,
|
label,
|
||||||
|
type: 'text',
|
||||||
|
value: defaultValue,
|
||||||
placeholder,
|
placeholder,
|
||||||
default: defaultValue,
|
validate,
|
||||||
props,
|
props: {...props, class: 'input-textlike ' + (props.class ?? '')},
|
||||||
feedback: '',
|
feedback: '',
|
||||||
}
|
}
|
||||||
output.value[key] = defaultValue;
|
inputs_.value.push(input);
|
||||||
input.validate = validate?.bind(input),
|
return prom;
|
||||||
inputs.value.push(input);
|
}
|
||||||
|
/**
|
||||||
|
* Add a checkbox input
|
||||||
|
* @param {String} key - Key to index output object
|
||||||
|
* @param {String} label - Text to go in label
|
||||||
|
* @param {*} defaultValue - Default value
|
||||||
|
* @param {*} trueValue - Value of result when checked
|
||||||
|
* @param {*} falseValue - Value of result when unchecked
|
||||||
|
* @param {Object} props - Extra properties to v-bind to the input element
|
||||||
|
*/
|
||||||
|
prom.addCheckboxInput = (key, label, defaultValue = false, trueValue = true, falseValue = false, props = {}) => {
|
||||||
|
/**
|
||||||
|
* @type {ModalPromptInputObj<*>}
|
||||||
|
*/
|
||||||
|
const input = {
|
||||||
|
key,
|
||||||
|
label,
|
||||||
|
type: 'checkbox',
|
||||||
|
value: defaultValue,
|
||||||
|
placeholder,
|
||||||
|
validate: () => null,
|
||||||
|
props: { ...props, 'true-value': trueValue, 'false-value': falseValue, class: 'input-checkbox ' + (props.class ?? '') },
|
||||||
|
feedback: '',
|
||||||
|
}
|
||||||
|
inputs_.value.push(input);
|
||||||
return prom;
|
return prom;
|
||||||
}
|
}
|
||||||
prom.addInput = addInput;
|
|
||||||
return prom;
|
return prom;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const confirm = () => {
|
||||||
|
const response = inputs_.value.length
|
||||||
|
? inputs_.value.reduce((response, input) => {
|
||||||
|
response[input.key] = input.value;
|
||||||
|
return response;
|
||||||
|
}, {})
|
||||||
|
: defaultInputValue_.value;
|
||||||
|
resolver_.value?.(response);
|
||||||
|
show_.value = false;
|
||||||
|
emit('confirm');
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
|
resolver_.value?.(null);
|
||||||
|
show_.value = false;
|
||||||
|
emit('cancel');
|
||||||
|
};
|
||||||
|
|
||||||
|
const reset = () => {
|
||||||
|
headerText_ = "";
|
||||||
|
opts_.value = {};
|
||||||
|
inputs_.value = [];
|
||||||
|
valid_.value = false;
|
||||||
|
resolver_.value = null;
|
||||||
|
defaultInputValue_.value = "";
|
||||||
|
emit('after-leave');
|
||||||
|
};
|
||||||
|
|
||||||
const validate = () => {
|
const validate = () => {
|
||||||
valid.value = inputs.value.every(input => input.validate?.(output.value[input.key]) ?? true);
|
valid_.value = inputs_.value.every(input => input.feedback === null);
|
||||||
}
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
show,
|
show_,
|
||||||
headerText,
|
opts_,
|
||||||
inputs,
|
headerText_,
|
||||||
output,
|
inputs_,
|
||||||
valid,
|
valid_,
|
||||||
apply,
|
resolver_,
|
||||||
|
defaultInputRef_,
|
||||||
|
defaultInputValue_,
|
||||||
|
inputRefs_,
|
||||||
|
ask,
|
||||||
|
confirm,
|
||||||
cancel,
|
cancel,
|
||||||
prompt,
|
reset,
|
||||||
validate,
|
validate,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
ModalPopup,
|
ModalConfirm,
|
||||||
ExclamationCircleIcon,
|
ExclamationCircleIcon,
|
||||||
}
|
},
|
||||||
|
emits: [
|
||||||
|
'confirm',
|
||||||
|
'cancel',
|
||||||
|
'after-leave',
|
||||||
|
]
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Reference in New Issue