kopia lustrzana https://github.com/FacilMap/facilmap
164 wiersze
4.4 KiB
Vue
164 wiersze
4.4 KiB
Vue
![]() |
<script lang="ts">
|
||
|
import { computed, createApp, defineComponent, h, ref, VNode, VNodeArrayChildren, withDirectives } from 'vue';
|
||
|
import Alert from "./alert.vue";
|
||
|
import vValidity from '../../utils/validity';
|
||
|
import { useModal } from '../../utils/modal';
|
||
|
|
||
|
export type AlertProps = {
|
||
|
title: string;
|
||
|
message: string;
|
||
|
type?: "alert" | "confirm";
|
||
|
variant?: "success" | "danger" | "warning";
|
||
|
show?: boolean;
|
||
|
};
|
||
|
|
||
|
export interface AlertResult {
|
||
|
ok: boolean;
|
||
|
}
|
||
|
|
||
|
async function renderAlert({ getContent, onShown, ...props }: AlertProps & {
|
||
|
getContent?: () => string | VNode | VNodeArrayChildren;
|
||
|
onShown?: () => void;
|
||
|
}): Promise<AlertResult> {
|
||
|
return await new Promise<AlertResult>((resolve) => {
|
||
|
const el = document.createElement('div');
|
||
|
document.body.appendChild(el);
|
||
|
const app = createApp(defineComponent({
|
||
|
setup() {
|
||
|
return () => h(Alert, {
|
||
|
...props,
|
||
|
onShown: () => {
|
||
|
onShown?.();
|
||
|
},
|
||
|
onHide: (result) => {
|
||
|
resolve(result);
|
||
|
},
|
||
|
onHidden: () => {
|
||
|
app.unmount();
|
||
|
el.remove();
|
||
|
}
|
||
|
}, getContent);
|
||
|
}
|
||
|
}));
|
||
|
app.mount(el);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
export async function showAlert(props: Omit<AlertProps, 'type' | 'show'>): Promise<void> {
|
||
|
await renderAlert({ ...props, type: 'alert' });
|
||
|
}
|
||
|
|
||
|
export async function showConfirm(props: Omit<AlertProps, 'type' | 'show'>): Promise<boolean> {
|
||
|
const result = await renderAlert({ ...props, type: 'confirm' });
|
||
|
return result.ok;
|
||
|
}
|
||
|
|
||
|
export async function showPrompt({ initialValue = "", validate, ...props }: Omit<AlertProps, 'type' | 'show' | 'message'> & {
|
||
|
initialValue?: string;
|
||
|
/** Validate the value. Return an empty string or undefined to indicate validity. */
|
||
|
validate?: (value: string) => string | undefined;
|
||
|
}): Promise<string | undefined> {
|
||
|
const value = ref(initialValue);
|
||
|
const submitted = ref(false);
|
||
|
const validationError = computed(() => submitted.value ? undefined : validate?.(value.value));
|
||
|
const touched = ref(false);
|
||
|
const inputRef = ref<HTMLInputElement>();
|
||
|
|
||
|
const result = await renderAlert({
|
||
|
...props,
|
||
|
message: '',
|
||
|
type: 'confirm',
|
||
|
getContent: () => h('div', {
|
||
|
class: touched.value ? 'was-validated' : ''
|
||
|
}, [
|
||
|
withDirectives(h('input', {
|
||
|
type: "text",
|
||
|
class: `form-control${touched.value ? ' was-validated' : ''}`,
|
||
|
value: value.value,
|
||
|
onInput: (e: InputEvent) => {
|
||
|
value.value = (e.target as HTMLInputElement).value;
|
||
|
touched.value = true;
|
||
|
},
|
||
|
onBlur: () => {
|
||
|
touched.value = true;
|
||
|
},
|
||
|
autofocus: true,
|
||
|
ref: inputRef
|
||
|
}), [
|
||
|
[vValidity, validationError.value]
|
||
|
]),
|
||
|
...(validationError.value ? [h('div', {
|
||
|
class: "invalid-feedback"
|
||
|
}, validationError.value)] : [])
|
||
|
]),
|
||
|
onShown: () => {
|
||
|
inputRef.value!.focus();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
submitted.value = true;
|
||
|
|
||
|
return result.ok ? value.value : undefined;
|
||
|
}
|
||
|
</script>
|
||
|
|
||
|
<script setup lang="ts">
|
||
|
const props = withDefaults(defineProps<AlertProps>(), {
|
||
|
type: "alert"
|
||
|
});
|
||
|
|
||
|
const emit = defineEmits<{
|
||
|
(type: 'shown'): void;
|
||
|
(type: 'hide', result: AlertResult): void;
|
||
|
(type: 'hidden', result: AlertResult): void;
|
||
|
}>();
|
||
|
|
||
|
const result = ref<AlertResult>({
|
||
|
ok: false
|
||
|
});
|
||
|
|
||
|
const modal = useModal({
|
||
|
onShown: () => {
|
||
|
emit('shown');
|
||
|
},
|
||
|
onHide: () => {
|
||
|
emit('hide', result.value);
|
||
|
},
|
||
|
onHidden: () => {
|
||
|
emit('hidden', result.value);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
const formRef = ref<HTMLFormElement>();
|
||
|
const formTouched = ref(false);
|
||
|
const handleSubmit = () => {
|
||
|
if (formRef.value!.checkValidity()) {
|
||
|
result.value.ok = true;
|
||
|
modal.hide();
|
||
|
} else {
|
||
|
formTouched.value = true;
|
||
|
}
|
||
|
};
|
||
|
</script>
|
||
|
|
||
|
<template>
|
||
|
<Teleport to="body">
|
||
|
<div class="modal fade" tabindex="-1" aria-hidden="true" :ref="modal.ref">
|
||
|
<div class="modal-dialog">
|
||
|
<form class="modal-content" :class="{ 'was-validated': formTouched }" @submit.prevent="handleSubmit()" novalidate ref="formRef">
|
||
|
<div class="modal-header">
|
||
|
<h1 class="modal-title fs-5">{{props.title}}</h1>
|
||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
|
</div>
|
||
|
<div class="modal-body">
|
||
|
<slot>{{props.message}}</slot>
|
||
|
</div>
|
||
|
<div class="modal-footer">
|
||
|
<button v-if="type === 'confirm'" type="button" class="btn btn-light" data-bs-dismiss="modal">Cancel</button>
|
||
|
<button type="submit" class="btn" :class="`btn-${props.variant ?? 'primary'}`">OK</button>
|
||
|
</div>
|
||
|
</form>
|
||
|
</div>
|
||
|
</div>
|
||
|
</Teleport>
|
||
|
</template>
|