mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
fix duplicate uuid + combo/date/select/editor
* update components to be sure to get unique uuid (before, same id when components were render at the same time) * update datepicker, combobox, select, editor to fit w3c and a11n
This commit is contained in:
parent
eb06a151e3
commit
ed301b6dd2
18 changed files with 258 additions and 186 deletions
|
|
@ -1,10 +1,9 @@
|
|||
<script setup>
|
||||
import { reactive, watch, onMounted, computed } from "vue";
|
||||
import { reactive } from "vue";
|
||||
import Alert from "@components/Widget/Alert.vue";
|
||||
import { feedbackIndex } from "@utils/tabindex.js";
|
||||
import { useBannerStore } from "@store/global.js";
|
||||
import { onBeforeMount } from "vue";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
/**
|
||||
@name Dashboard/Feedback.vue
|
||||
@description This component will display server feedbacks from the user.
|
||||
|
|
@ -14,7 +13,6 @@ import { v4 as uuidv4 } from "uuid";
|
|||
|
||||
const feedback = reactive({
|
||||
data: [],
|
||||
id: uuidv4(),
|
||||
});
|
||||
|
||||
// Handle feedback history panel
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import Combobox from "@components/Forms/Field/Combobox.vue";
|
|||
import Button from "@components/Widget/Button.vue";
|
||||
import Text from "@components/Widget/Text.vue";
|
||||
import Filter from "@components/Widget/Filter.vue";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { plugin_types } from "@utils/variables";
|
||||
import {
|
||||
useCheckPluginsValidity,
|
||||
|
|
@ -16,6 +15,7 @@ import {
|
|||
useListenTemp,
|
||||
useUnlistenTemp,
|
||||
} from "@utils/form.js";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
/**
|
||||
@name Form/Advanced.vue
|
||||
@description This component is used to create a complete advanced form with plugin selection.
|
||||
|
|
@ -64,7 +64,7 @@ const props = defineProps({
|
|||
columns: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: {},
|
||||
default: { pc: 12, tablet: 12, mobile: 12 },
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -96,10 +96,10 @@ const filters = [
|
|||
"setting_name",
|
||||
],
|
||||
field: {
|
||||
id: uuidv4(),
|
||||
id: `advanced-filter-keyword-${uuidv4()}`,
|
||||
value: "",
|
||||
type: "text",
|
||||
name: uuidv4(),
|
||||
name: `advanced-filter-keyword-${uuidv4()}`,
|
||||
containerClass: "setting",
|
||||
label: "inp_search_settings",
|
||||
placeholder: "inp_keyword",
|
||||
|
|
@ -121,11 +121,11 @@ const filters = [
|
|||
value: "all",
|
||||
keys: ["type"],
|
||||
field: {
|
||||
id: uuidv4(),
|
||||
id: `advanced-filter-type-${uuidv4()}`,
|
||||
value: "all",
|
||||
// add 'all' as first value
|
||||
values: ["all"].concat(plugin_types),
|
||||
name: uuidv4(),
|
||||
name: `advanced-filter-type-${uuidv4()}`,
|
||||
onlyDown: true,
|
||||
label: "inp_select_plugin_type",
|
||||
containerClass: "setting",
|
||||
|
|
@ -146,11 +146,11 @@ const filters = [
|
|||
value: "all",
|
||||
keys: ["context"],
|
||||
field: {
|
||||
id: uuidv4(),
|
||||
id: `advanced-filter-context-${uuidv4()}`,
|
||||
value: "all",
|
||||
// add 'all' as first value
|
||||
values: ["all", "multisite", "global"],
|
||||
name: uuidv4(),
|
||||
name: `advanced-filter-context-${uuidv4()}`,
|
||||
onlyDown: true,
|
||||
containerClass: "setting",
|
||||
label: "inp_select_plugin_context",
|
||||
|
|
@ -210,8 +210,8 @@ function updateTemplate(e) {
|
|||
}
|
||||
|
||||
const comboboxPlugin = {
|
||||
id: uuidv4(),
|
||||
name: uuidv4(),
|
||||
id: `advanced-combobox-${uuidv4()}`,
|
||||
name: `advanced-combobox-${uuidv4()}`,
|
||||
disabled: false,
|
||||
required: false,
|
||||
onlyDown: true,
|
||||
|
|
@ -228,11 +228,11 @@ const comboboxPlugin = {
|
|||
};
|
||||
|
||||
const buttonSave = {
|
||||
id: uuidv4(),
|
||||
id: `advanced-save-${uuidv4()}`,
|
||||
text: "action_save",
|
||||
color: "success",
|
||||
size: "normal",
|
||||
type: "bouton",
|
||||
type: "button",
|
||||
attrs: {
|
||||
"data-submit-form": JSON.stringify(data.base),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
<script setup>
|
||||
import { reactive, defineProps, onMounted, ref, readonly } from "vue";
|
||||
import { defineProps } from "vue";
|
||||
import Checkbox from "@components/Forms/Field/Checkbox.vue";
|
||||
import Input from "@components/Forms/Field/Input.vue";
|
||||
import Select from "@components/Forms/Field/Select.vue";
|
||||
import Datepicker from "@components/Forms/Field/Datepicker.vue";
|
||||
import Editor from "@components/Forms/Field/Editor.vue";
|
||||
|
||||
import { contentIndex } from "@utils/tabindex.js";
|
||||
/**
|
||||
@name Form/Fields.vue
|
||||
@description This component wraps all available fields for a form.
|
||||
|
|
@ -73,8 +73,8 @@ const props = defineProps({
|
|||
:label="props.setting.label"
|
||||
:name="props.setting.name"
|
||||
:popovers="props.setting.popovers || []"
|
||||
:onlyDownload="props.setting.onlyDownload || false"
|
||||
:overflowAttrEl="props.setting.overflowAttrEl || false"
|
||||
:onlyDown="props.setting.onlyDown || false"
|
||||
:overflowAttrEl="props.setting.overflowAttrEl || ''"
|
||||
:hideLabel="props.setting.hideLabel || false"
|
||||
:containerClass="props.setting.containerClass || ''"
|
||||
:headerClass="props.setting.headerClass || ''"
|
||||
|
|
|
|||
|
|
@ -113,15 +113,15 @@ const data = reactive({
|
|||
|
||||
const editorData = {
|
||||
value: data.inp || data.entry,
|
||||
name: uuidv4(),
|
||||
label: uuidv4(),
|
||||
name: `raw-editor-${uuidv4()}`,
|
||||
label: `raw-editor-${uuidv4()}`,
|
||||
hideLabel: true,
|
||||
columns: { pc: 12, tablet: 12, mobile: 12 },
|
||||
editorClass: "min-h-96",
|
||||
};
|
||||
|
||||
const buttonSave = {
|
||||
id: uuidv4(),
|
||||
id: `raw-save-${uuidv4()}`,
|
||||
text: "action_save",
|
||||
color: "success",
|
||||
size: "normal",
|
||||
|
|
|
|||
|
|
@ -46,16 +46,16 @@ const props = defineProps({
|
|||
});
|
||||
|
||||
const comboboxTemplate = {
|
||||
id: uuidv4(),
|
||||
name: uuidv4(),
|
||||
id: `combobox-template-${uuidv4()}`,
|
||||
name: `combobox-template-${uuidv4()}`,
|
||||
disabled: false,
|
||||
label: "dashboard_templates",
|
||||
columns: { pc: 4, tablet: 6, mobile: 12 },
|
||||
};
|
||||
|
||||
const comboboxModes = {
|
||||
id: uuidv4(),
|
||||
name: uuidv4(),
|
||||
id: `combobox-modes-${uuidv4()}`,
|
||||
name: `combobox-modes-${uuidv4()}`,
|
||||
disabled: false,
|
||||
required: false,
|
||||
onlyDown: true,
|
||||
|
|
@ -97,8 +97,6 @@ onBeforeMount(() => {
|
|||
<template>
|
||||
<Container
|
||||
v-if="data.currModeName && data.currTemplateName"
|
||||
:tag="'form'"
|
||||
method="POST"
|
||||
:containerClass="`col-span-12 w-full m-1 p-1`"
|
||||
:columns="props.columns"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<script setup>
|
||||
import { defineProps } from "vue";
|
||||
import { defineProps, onMounted, reactive } from "vue";
|
||||
import { contentIndex } from "@utils/tabindex.js";
|
||||
import { useClipboard } from "@vueuse/core";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { useUUID } from "@utils/global.js";
|
||||
|
||||
/**
|
||||
@name Forms/Feature/Clipboard.vue
|
||||
|
|
@ -29,7 +29,7 @@ const props = defineProps({
|
|||
id: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: uuidv4(),
|
||||
default: "",
|
||||
},
|
||||
isClipboard: {
|
||||
type: Boolean,
|
||||
|
|
@ -37,7 +37,7 @@ const props = defineProps({
|
|||
default: false,
|
||||
},
|
||||
valueToCopy: {
|
||||
type: String,
|
||||
type: [String, Number],
|
||||
required: false,
|
||||
default: "",
|
||||
},
|
||||
|
|
@ -52,6 +52,14 @@ const props = defineProps({
|
|||
default: "",
|
||||
},
|
||||
});
|
||||
|
||||
const clip = reactive({
|
||||
id: props.id,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
clip.id = useUUID();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -63,10 +71,10 @@ const props = defineProps({
|
|||
type="button"
|
||||
:class="['input-clipboard-button', copied ? 'copied' : 'not-copied']"
|
||||
:tabindex="contentIndex"
|
||||
@click.prevent="copy(valueToCopy)"
|
||||
:aria-labelledby="`${props.id}-clipboard-text`"
|
||||
@click.prevent="copy(`${valueToCopy}`)"
|
||||
:aria-labelledby="`${clip.id}-clipboard-text`"
|
||||
>
|
||||
<span :id="`${props.id}-clipboard-text`" class="sr-only">
|
||||
<span :id="`${clip.id}-clipboard-text`" class="sr-only">
|
||||
{{ $t("inp_input_clipboard_desc") }}
|
||||
</span>
|
||||
<svg
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { contentIndex } from "@utils/tabindex.js";
|
|||
import Container from "@components/Widget/Container.vue";
|
||||
import Header from "@components/Forms/Header/Field.vue";
|
||||
import ErrorField from "@components/Forms/Error/Field.vue";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { useUUID } from "@utils/global.js";
|
||||
|
||||
/**
|
||||
@name Forms/Field/Checkbox.vue
|
||||
|
|
@ -51,7 +51,7 @@ const props = defineProps({
|
|||
id: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: uuidv4(),
|
||||
default: "",
|
||||
},
|
||||
columns: {
|
||||
type: [Object, Boolean],
|
||||
|
|
@ -120,6 +120,7 @@ const props = defineProps({
|
|||
const checkboxEl = ref(null);
|
||||
|
||||
const checkbox = reactive({
|
||||
id: props.id,
|
||||
value: props.value,
|
||||
isValid: true,
|
||||
});
|
||||
|
|
@ -134,6 +135,7 @@ function updateValue() {
|
|||
|
||||
onMounted(() => {
|
||||
checkbox.isValid = checkboxEl.value.checkValidity();
|
||||
checkbox.id = useUUID(checkbox.id);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
@ -147,7 +149,7 @@ onMounted(() => {
|
|||
:required="props.required"
|
||||
:name="props.name"
|
||||
:label="props.label"
|
||||
:id="props.id"
|
||||
:id="checkbox.id"
|
||||
:hideLabel="props.hideLabel"
|
||||
:headerClass="props.headerClass"
|
||||
/>
|
||||
|
|
@ -158,7 +160,7 @@ onMounted(() => {
|
|||
:tabindex="props.tabId"
|
||||
@keyup.enter="$emit('inp', updateValue())"
|
||||
@click="$emit('inp', updateValue())"
|
||||
:id="props.id"
|
||||
:id="checkbox.id"
|
||||
:name="props.name"
|
||||
:disabled="props.disabled || false"
|
||||
:checked="checkbox.value === 'yes' ? true : false"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { contentIndex } from "@utils/tabindex.js";
|
|||
import Container from "@components/Widget/Container.vue";
|
||||
import Header from "@components/Forms/Header/Field.vue";
|
||||
import ErrorField from "@components/Forms/Error/Field.vue";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { useUUID } from "@utils/global.js";
|
||||
|
||||
/**
|
||||
@name Forms/Field/Combobox.vue
|
||||
|
|
@ -63,7 +63,7 @@ const props = defineProps({
|
|||
id: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: uuidv4(),
|
||||
default: "",
|
||||
},
|
||||
columns: {
|
||||
type: [Object, Boolean],
|
||||
|
|
@ -152,6 +152,7 @@ const props = defineProps({
|
|||
});
|
||||
|
||||
const inp = reactive({
|
||||
id: props.id,
|
||||
value: "",
|
||||
isValid: true,
|
||||
isMatching: computed(() => {
|
||||
|
|
@ -295,6 +296,7 @@ watch(select, () => {
|
|||
});
|
||||
|
||||
onMounted(() => {
|
||||
inp.id = useUUID(inp.id);
|
||||
inp.isValid = inputEl.value.checkValidity();
|
||||
selectWidth.value = `${selectBtn.value.clientWidth}px`;
|
||||
window.addEventListener("resize", () => {
|
||||
|
|
@ -318,13 +320,13 @@ const emits = defineEmits(["inp"]);
|
|||
:popovers="props.popovers"
|
||||
:required="props.required"
|
||||
:name="props.name"
|
||||
:id="props.id"
|
||||
:id="inp.id"
|
||||
:label="props.label"
|
||||
:hideLabel="props.hideLabel"
|
||||
:headerClass="props.headerClass"
|
||||
/>
|
||||
|
||||
<select aria-hidden="true" :name="props.name" class="hidden">
|
||||
<select :id="inp.id" aria-hidden="true" :name="props.name" class="hidden">
|
||||
<option
|
||||
v-for="(value, id) in props.values"
|
||||
:key="id"
|
||||
|
|
@ -348,7 +350,7 @@ const emits = defineEmits(["inp"]);
|
|||
:name="`${props.name}-custom`"
|
||||
:tabindex="props.tabId"
|
||||
ref="selectBtn"
|
||||
:aria-controls="`${props.id}-custom`"
|
||||
:aria-controls="`${inp.id}-custom`"
|
||||
:aria-expanded="select.isOpen ? 'true' : 'false'"
|
||||
:aria-description="$t('inp_select_dropdown_button_desc')"
|
||||
:disabled="props.disabled || false"
|
||||
|
|
@ -359,7 +361,7 @@ const emits = defineEmits(["inp"]);
|
|||
props.inpClass,
|
||||
]"
|
||||
>
|
||||
<span :id="`${props.id}-text`" class="select-btn-name">
|
||||
<span :id="`${inp.id}-text`" class="select-btn-name">
|
||||
{{
|
||||
props.maxBtnChars &&
|
||||
(select.value || props.value).length > +props.maxBtnChars
|
||||
|
|
@ -389,15 +391,16 @@ const emits = defineEmits(["inp"]);
|
|||
<div
|
||||
ref="selectDropdown"
|
||||
:style="{ width: selectWidth }"
|
||||
:id="`${props.id}-custom`"
|
||||
:id="`${inp.id}-custom`"
|
||||
:class="[select.isOpen ? 'open' : 'close']"
|
||||
class="select-dropdown-container"
|
||||
:aria-hidden="select.isOpen ? 'false' : 'true'"
|
||||
role="combobox"
|
||||
:aria-expanded="select.isOpen ? 'true' : 'false'"
|
||||
:aria-description="$t('inp_select_dropdown_desc')"
|
||||
>
|
||||
<div>
|
||||
<label :class="['sr-only']" :for="`${props.id}-combobox`">
|
||||
<label :class="['sr-only']" :for="`${inp.id}-combobox`">
|
||||
{{ $t("inp_combobox") }}
|
||||
</label>
|
||||
<input
|
||||
|
|
@ -406,15 +409,15 @@ const emits = defineEmits(["inp"]);
|
|||
v-model="inp.value"
|
||||
:placeholder="$t('inp_combobox_placeholder')"
|
||||
@input="inp.isValid = inputEl.checkValidity()"
|
||||
:aria-controls="`${props.id}-list`"
|
||||
:id="`${props.id}-combobox`"
|
||||
:aria-controls="`${inp.id}-list`"
|
||||
:id="`${inp.id}-combobox`"
|
||||
:class="[
|
||||
'input-combobox',
|
||||
inp.isValid ? 'valid' : 'invalid',
|
||||
props.inpClass,
|
||||
]"
|
||||
:pattern="props.pattern || '(?s).*'"
|
||||
:name="`${props.id}-combobox`"
|
||||
:name="`${inp.id}-combobox`"
|
||||
:value="inp.value"
|
||||
:type="'text'"
|
||||
/>
|
||||
|
|
@ -431,7 +434,7 @@ const emits = defineEmits(["inp"]);
|
|||
</div>
|
||||
<div
|
||||
data-select-dropdown
|
||||
:id="`${props.id}-list`"
|
||||
:id="`${inp.id}-list`"
|
||||
:aria-hidden="select.isOpen ? 'false' : 'true'"
|
||||
role="radiogroup"
|
||||
class="select-combobox-list"
|
||||
|
|
@ -461,9 +464,9 @@ const emits = defineEmits(["inp"]);
|
|||
'select-dropdown-btn',
|
||||
]"
|
||||
data-select-item
|
||||
:data-setting-id="props.id"
|
||||
:data-setting-id="inp.id"
|
||||
:data-setting-value="value"
|
||||
:aria-controls="`${props.id}-text`"
|
||||
:aria-controls="`${inp.id}-text`"
|
||||
:aria-checked="
|
||||
(select.value && select.value === value) ||
|
||||
(!select.value && value === props.value)
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
<script setup>
|
||||
import { reactive, defineEmits, defineProps, onMounted } from "vue";
|
||||
import { reactive, defineProps, onMounted, onUnmounted } from "vue";
|
||||
import { contentIndex } from "@utils/tabindex.js";
|
||||
import Container from "@components/Widget/Container.vue";
|
||||
import Header from "@components/Forms/Header/Field.vue";
|
||||
import ErrorField from "@components/Forms/Error/Field.vue";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import Clipboard from "@components/Forms/Feature/Clipboard.vue";
|
||||
import { useUUID } from "@utils/global";
|
||||
|
||||
import flatpickr from "flatpickr";
|
||||
|
||||
import "@assets/css/flatpickr.min.css";
|
||||
import "@assets/css/flatpickr.dark.min.css";
|
||||
|
||||
|
|
@ -60,7 +59,7 @@ const props = defineProps({
|
|||
id: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: uuidv4(),
|
||||
default: "",
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
|
|
@ -142,6 +141,7 @@ const props = defineProps({
|
|||
});
|
||||
|
||||
const date = reactive({
|
||||
id: props.id,
|
||||
isValid: true,
|
||||
format: "m/d/Y H:i:S",
|
||||
currStamp: "",
|
||||
|
|
@ -152,67 +152,6 @@ const picker = reactive({
|
|||
});
|
||||
|
||||
let datepicker;
|
||||
onMounted(() => {
|
||||
datepicker = flatpickr(`#${props.id}`, {
|
||||
locale: "en",
|
||||
dateFormat: date.format,
|
||||
defaultDate: +props.value,
|
||||
maxDate: +props.maxDate ? +props.maxDate : "",
|
||||
minDate: +props.minDate ? +props.minDate : "",
|
||||
enableTime: true,
|
||||
enableSeconds: true,
|
||||
time_24hr: true,
|
||||
minuteIncrement: 1,
|
||||
onChange(selectedDates, dateStr, instance) {
|
||||
if (!dateStr && props.required) return (date.isValid = false);
|
||||
//Check if date is in interval
|
||||
try {
|
||||
const currStamp = Date.parse(dateStr);
|
||||
|
||||
date.currStamp = currStamp;
|
||||
// Run whatever, if invalid this will override
|
||||
date.isValid = true;
|
||||
} catch (err) {}
|
||||
},
|
||||
onOpen(selectedDates, dateStr, instance) {
|
||||
picker.isOpen = true;
|
||||
// Focus on current date and update tabindex
|
||||
try {
|
||||
setIndex(instance.calendarContainer, contentIndex);
|
||||
const baseFocus =
|
||||
instance.calendarContainer.querySelector(".flatpickr-day.today") ||
|
||||
instance.calendarContainer.querySelector(".flatpickr-day");
|
||||
baseFocus.setAttribute("data-tabindex-active", true);
|
||||
setTimeout(() => {
|
||||
baseFocus.focus();
|
||||
}, 50);
|
||||
} catch (err) {}
|
||||
},
|
||||
onClose(selectedDates, dateStr, instance) {
|
||||
picker.isOpen = false;
|
||||
setIndex(instance.calendarContainer, "-1");
|
||||
},
|
||||
});
|
||||
// Check if multiple or not
|
||||
let datepickerEl = null;
|
||||
if (Array.isArray(datepicker)) {
|
||||
datepickerEl = datepicker[datepicker.length - 1];
|
||||
} else {
|
||||
datepickerEl = datepicker;
|
||||
}
|
||||
// Set valid date state
|
||||
if (!datepickerEl.selectedDates[0] && props.required) date.isValid = false;
|
||||
if (!datepickerEl.selectedDates[0] && !props.required) date.isValid = true;
|
||||
|
||||
const calendar = datepickerEl.calendarContainer;
|
||||
// Impossible to use default select month dropdown with keyboard
|
||||
// We need to create our own and link calendar to it
|
||||
setMonthSelect(calendar, props.id);
|
||||
// Override default behavior that go to input el instead of previous calendat element on tab + maj
|
||||
handleEvents(calendar, props.id, datepickerEl);
|
||||
|
||||
setPickerAtt(calendar, props.id);
|
||||
});
|
||||
|
||||
function setMonthSelect(calendar, id) {
|
||||
// Hide default select and optionss
|
||||
|
|
@ -287,10 +226,6 @@ function setMonthSelect(calendar, id) {
|
|||
|
||||
function setPickerAtt(calendarEl, id = false) {
|
||||
// change error non-standard attributes
|
||||
if (id) {
|
||||
calendarEl.setAttribute("id", id);
|
||||
}
|
||||
|
||||
const inps = calendarEl.querySelectorAll(
|
||||
'input.numInput[type="number"][maxlength]'
|
||||
);
|
||||
|
|
@ -312,6 +247,10 @@ function setPickerAtt(calendarEl, id = false) {
|
|||
calendarEl.querySelectorAll("svg").forEach((svg) => {
|
||||
svg.classList.add("pointer-events-none");
|
||||
});
|
||||
|
||||
if (id) {
|
||||
calendarEl.setAttribute("id", `${id}-calendar`);
|
||||
}
|
||||
}
|
||||
|
||||
function handleEvents(calendarEl, id, datepicker) {
|
||||
|
|
@ -634,6 +573,73 @@ function setIndex(calendarEl, tabindex) {
|
|||
second.setAttribute("tabindex", tabindex);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
date.id = useUUID(date.id);
|
||||
datepicker = flatpickr(`#${date.id}`, {
|
||||
locale: "en",
|
||||
dateFormat: date.format,
|
||||
defaultDate: +props.value,
|
||||
maxDate: +props.maxDate ? +props.maxDate : "",
|
||||
minDate: +props.minDate ? +props.minDate : "",
|
||||
enableTime: true,
|
||||
enableSeconds: true,
|
||||
time_24hr: true,
|
||||
minuteIncrement: 1,
|
||||
onChange(selectedDates, dateStr, instance) {
|
||||
if (!dateStr && props.required) return (date.isValid = false);
|
||||
//Check if date is in interval
|
||||
try {
|
||||
const currStamp = Date.parse(dateStr);
|
||||
|
||||
date.currStamp = currStamp;
|
||||
// Run whatever, if invalid this will override
|
||||
date.isValid = true;
|
||||
} catch (err) {}
|
||||
},
|
||||
onOpen(selectedDates, dateStr, instance) {
|
||||
picker.isOpen = true;
|
||||
// Focus on current date and update tabindex
|
||||
try {
|
||||
setIndex(instance.calendarContainer, contentIndex);
|
||||
const baseFocus =
|
||||
instance.calendarContainer.querySelector(".flatpickr-day.today") ||
|
||||
instance.calendarContainer.querySelector(".flatpickr-day");
|
||||
baseFocus.setAttribute("data-tabindex-active", true);
|
||||
setTimeout(() => {
|
||||
baseFocus.focus();
|
||||
}, 50);
|
||||
} catch (err) {}
|
||||
},
|
||||
onClose(selectedDates, dateStr, instance) {
|
||||
picker.isOpen = false;
|
||||
setIndex(instance.calendarContainer, "-1");
|
||||
},
|
||||
});
|
||||
// Check if multiple or not
|
||||
let datepickerEl = null;
|
||||
if (Array.isArray(datepicker)) {
|
||||
datepickerEl = datepicker[datepicker.length - 1];
|
||||
} else {
|
||||
datepickerEl = datepicker;
|
||||
}
|
||||
// Set valid date state
|
||||
if (!datepickerEl.selectedDates[0] && props.required) date.isValid = false;
|
||||
if (!datepickerEl.selectedDates[0] && !props.required) date.isValid = true;
|
||||
|
||||
const calendar = datepickerEl.calendarContainer;
|
||||
// Impossible to use default select month dropdown with keyboard
|
||||
// We need to create our own and link calendar to it
|
||||
setMonthSelect(calendar, date.id);
|
||||
// Override default behavior that go to input el instead of previous calendat element on tab + maj
|
||||
handleEvents(calendar, date.id, datepickerEl);
|
||||
|
||||
setPickerAtt(calendar, date.id);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
datepicker.destroy();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -648,7 +654,7 @@ function setIndex(calendarEl, tabindex) {
|
|||
:required="props.required"
|
||||
:name="props.name"
|
||||
:label="props.label"
|
||||
:id="props.id"
|
||||
:id="date.id"
|
||||
:hideLabel="props.hideLabel"
|
||||
:headerClass="props.headerClass"
|
||||
/>
|
||||
|
|
@ -657,8 +663,7 @@ function setIndex(calendarEl, tabindex) {
|
|||
<input
|
||||
:data-timestamp="date.currStamp"
|
||||
:tabindex="props.tabId"
|
||||
:aria-controls="props.id"
|
||||
:aria-selected="picker.isOpen ? 'true' : 'false'"
|
||||
:aria-controls="`${date.id}-calendar`"
|
||||
type="text"
|
||||
:class="[
|
||||
date.isValid ? 'valid' : 'invalid',
|
||||
|
|
@ -666,7 +671,7 @@ function setIndex(calendarEl, tabindex) {
|
|||
props.inpClass,
|
||||
props.disabled ? 'cursor-not-allowed' : 'cursor-pointer',
|
||||
]"
|
||||
:id="props.id"
|
||||
:id="date.id"
|
||||
:required="props.required || false"
|
||||
:disabled="props.disabled || false"
|
||||
:name="props.name"
|
||||
|
|
|
|||
|
|
@ -6,13 +6,14 @@ import {
|
|||
onMounted,
|
||||
defineProps,
|
||||
onUnmounted,
|
||||
onUpdated,
|
||||
} from "vue";
|
||||
import { contentIndex } from "@utils/tabindex.js";
|
||||
import Container from "@components/Widget/Container.vue";
|
||||
import Header from "@components/Forms/Header/Field.vue";
|
||||
import ErrorField from "@components/Forms/Error/Field.vue";
|
||||
import Clipboard from "@components/Forms/Feature/Clipboard.vue";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { useUUID } from "@utils/global.js";
|
||||
|
||||
import "@assets/script/editor/ace.js";
|
||||
import "@assets/script/editor/theme-dracula.js";
|
||||
|
|
@ -58,7 +59,7 @@ const props = defineProps({
|
|||
id: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: uuidv4(),
|
||||
default: useUUID(),
|
||||
},
|
||||
columns: {
|
||||
type: [Object, Boolean],
|
||||
|
|
@ -135,6 +136,7 @@ const props = defineProps({
|
|||
});
|
||||
|
||||
const editor = reactive({
|
||||
id: props.id,
|
||||
value: props.value,
|
||||
showInp: false,
|
||||
isValid: computed(() => {
|
||||
|
|
@ -154,7 +156,7 @@ let editorEl = null;
|
|||
// Ace editor vanilla logic
|
||||
class Editor {
|
||||
constructor() {
|
||||
this.editor = ace.edit(props.id);
|
||||
this.editor = ace.edit(editor.id);
|
||||
this.darkMode = document.querySelector("[data-dark-toggle]");
|
||||
this.initEditor();
|
||||
this.listenDarkToggle();
|
||||
|
|
@ -269,8 +271,52 @@ class Editor {
|
|||
}
|
||||
}
|
||||
|
||||
function removeErrCSS() {
|
||||
setTimeout(() => {
|
||||
try {
|
||||
const editorArea = document.querySelector("textarea.ace_text-input");
|
||||
|
||||
const dictStyle = JSON.parse(
|
||||
JSON.stringify(
|
||||
document.querySelector('[style*="font-optical-sizing"]').style
|
||||
)
|
||||
);
|
||||
// Loop and remove key if value is 'font-optical-sizing'
|
||||
for (const [key, value] of Object.entries(dictStyle)) {
|
||||
if (value === "font-optical-sizing") {
|
||||
delete dictStyle[key];
|
||||
}
|
||||
}
|
||||
document.querySelector('[style*="font-optical-sizing"]').style =
|
||||
dictStyle;
|
||||
} catch (e) {}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function setEditorAttrs() {
|
||||
// Add tabindex to editor
|
||||
try {
|
||||
const editorArea = document.querySelector("textarea.ace_text-input");
|
||||
// Set attributes
|
||||
editorArea.removeAttribute("wrap");
|
||||
editorArea.removeAttribute("autocorrect");
|
||||
|
||||
editorArea.tabIndex = contentIndex;
|
||||
editorArea.setAttribute("id", `${editor.id}-editor`);
|
||||
editorArea.setAttribute("name", props.name);
|
||||
// Focus on editor
|
||||
editorArea.addEventListener("focus", (e) => {
|
||||
const editorRange = editorEl.editor.getSelectionRange();
|
||||
editorEl.editor.gotoLine(editorRange.start.row, editorRange.start.column);
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Use ace editor
|
||||
onMounted(() => {
|
||||
editor.id = useUUID(editor.id);
|
||||
// Default value
|
||||
editorEl = new Editor();
|
||||
editorEl.setValue(editor.value);
|
||||
|
|
@ -280,16 +326,9 @@ onMounted(() => {
|
|||
// emit inp
|
||||
emits("inp", editor.value);
|
||||
});
|
||||
// Add tabindex to editor
|
||||
try {
|
||||
const editorArea = document.querySelector("textarea.ace_text-input");
|
||||
editorArea.tabIndex = contentIndex;
|
||||
editorArea.setAttribute("name", props.name);
|
||||
editorArea.addEventListener("focus", (e) => {
|
||||
const editorRange = editorEl.editor.getSelectionRange();
|
||||
editorEl.editor.gotoLine(editorRange.start.row, editorRange.start.column);
|
||||
});
|
||||
} catch (e) {}
|
||||
|
||||
setEditorAttrs();
|
||||
removeErrCSS();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
|
|
@ -301,7 +340,7 @@ onUnmounted(() => {
|
|||
|
||||
<template>
|
||||
<Container
|
||||
:containerClass="`field-container ${props.containerClass}`"
|
||||
:containerClass="`field-container setting ${props.containerClass}`"
|
||||
:columns="props.columns"
|
||||
>
|
||||
<Header
|
||||
|
|
@ -309,7 +348,7 @@ onUnmounted(() => {
|
|||
:required="props.required"
|
||||
:name="props.name"
|
||||
:label="props.label"
|
||||
:id="props.id"
|
||||
:id="`${editor.id}-editor`"
|
||||
:hideLabel="props.hideLabel"
|
||||
:headerClass="props.headerClass"
|
||||
/>
|
||||
|
|
@ -331,7 +370,7 @@ onUnmounted(() => {
|
|||
props.editorClass,
|
||||
]"
|
||||
:aria-description="$t('inp_editor_desc')"
|
||||
:id="props.id"
|
||||
:id="editor.id"
|
||||
></div>
|
||||
<Clipboard
|
||||
:isClipboard="props.isClipboard"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import Container from "@components/Widget/Container.vue";
|
|||
import Header from "@components/Forms/Header/Field.vue";
|
||||
import ErrorField from "@components/Forms/Error/Field.vue";
|
||||
import Clipboard from "@components/Forms/Feature/Clipboard.vue";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { useUUID } from "@utils/global.js";
|
||||
|
||||
/**
|
||||
@name Forms/Field/Input.vue
|
||||
|
|
@ -59,7 +59,7 @@ const props = defineProps({
|
|||
id: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: uuidv4(),
|
||||
default: "",
|
||||
},
|
||||
columns: {
|
||||
type: [Object, Boolean],
|
||||
|
|
@ -152,6 +152,7 @@ const props = defineProps({
|
|||
const inputEl = ref(null);
|
||||
|
||||
const inp = reactive({
|
||||
id: props.id,
|
||||
value: props.value,
|
||||
showInp: false,
|
||||
isValid: true,
|
||||
|
|
@ -160,6 +161,7 @@ const inp = reactive({
|
|||
const emits = defineEmits(["inp"]);
|
||||
|
||||
onMounted(() => {
|
||||
inp.id = useUUID(inp.id);
|
||||
inp.isValid = inputEl.value.checkValidity();
|
||||
|
||||
// Clipboard not allowed on http
|
||||
|
|
@ -177,7 +179,7 @@ onMounted(() => {
|
|||
:required="props.required"
|
||||
:name="props.name"
|
||||
:label="props.label"
|
||||
:id="props.id"
|
||||
:id="inp.id"
|
||||
:hideLabel="props.hideLabel"
|
||||
:headerClass="props.headerClass"
|
||||
/>
|
||||
|
|
@ -193,7 +195,7 @@ onMounted(() => {
|
|||
$emit('inp', inp.value);
|
||||
}
|
||||
"
|
||||
:id="props.id"
|
||||
:id="inp.id"
|
||||
:class="[
|
||||
'input-regular',
|
||||
inp.isValid ? 'valid' : 'invalid',
|
||||
|
|
@ -228,13 +230,13 @@ onMounted(() => {
|
|||
<div v-if="props.type === 'password'" class="input-pw-container">
|
||||
<button
|
||||
:tabindex="contentIndex"
|
||||
:aria-controls="props.id"
|
||||
:aria-controls="inp.id"
|
||||
@click.prevent="inp.showInp = inp.showInp ? false : true"
|
||||
:class="[props.disabled ? 'disabled' : 'enabled']"
|
||||
class="input-pw-button"
|
||||
:aria-labelledby="`${props.id}-password-text`"
|
||||
:aria-labelledby="`${inp.id}-password-text`"
|
||||
>
|
||||
<span :id="`${props.id}-password-text`" class="sr-only">{{
|
||||
<span :id="`${inp.id}-password-text`" class="sr-only">{{
|
||||
$t("inp_input_password_desc")
|
||||
}}</span>
|
||||
<svg
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { contentIndex } from "@utils/tabindex.js";
|
|||
import Container from "@components/Widget/Container.vue";
|
||||
import Header from "@components/Forms/Header/Field.vue";
|
||||
import ErrorField from "@components/Forms/Error/Field.vue";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { useUUID } from "@utils/global";
|
||||
|
||||
/**
|
||||
@name Forms/Field/Select.vue
|
||||
|
|
@ -56,7 +56,7 @@ const props = defineProps({
|
|||
id: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: uuidv4(),
|
||||
default: "",
|
||||
},
|
||||
columns: {
|
||||
type: [Object, Boolean],
|
||||
|
|
@ -147,6 +147,7 @@ const props = defineProps({
|
|||
});
|
||||
|
||||
const select = reactive({
|
||||
id: props.id,
|
||||
isOpen: false,
|
||||
// On mounted value is null to display props value
|
||||
// Then on new select we will switch to select.value
|
||||
|
|
@ -274,6 +275,7 @@ watch(select, () => {
|
|||
});
|
||||
|
||||
onMounted(() => {
|
||||
select.id = useUUID(select.id);
|
||||
selectWidth.value = `${selectBtn.value.clientWidth}px`;
|
||||
window.addEventListener("resize", () => {
|
||||
try {
|
||||
|
|
@ -297,12 +299,12 @@ const emits = defineEmits(["inp"]);
|
|||
:required="props.required"
|
||||
:name="props.name"
|
||||
:label="props.label"
|
||||
:id="props.id"
|
||||
:id="select.id"
|
||||
:hideLabel="props.hideLabel"
|
||||
:headerClass="props.headerClass"
|
||||
/>
|
||||
|
||||
<select aria-hidden="true" :name="props.name" class="hidden">
|
||||
<select :id="select.id" :name="props.name" class="hidden">
|
||||
<option
|
||||
v-for="(value, id) in props.values"
|
||||
:key="id"
|
||||
|
|
@ -326,7 +328,7 @@ const emits = defineEmits(["inp"]);
|
|||
:name="`${props.name}-custom`"
|
||||
:tabindex="props.tabId"
|
||||
ref="selectBtn"
|
||||
:aria-controls="`${props.id}-custom`"
|
||||
:aria-controls="`${select.id}-custom`"
|
||||
:aria-expanded="select.isOpen ? 'true' : 'false'"
|
||||
:aria-description="$t('inp_select_dropdown_button_desc')"
|
||||
data-select-dropdown
|
||||
|
|
@ -338,7 +340,7 @@ const emits = defineEmits(["inp"]);
|
|||
props.inpClass,
|
||||
]"
|
||||
>
|
||||
<span :id="`${props.id}-text`" class="select-btn-name">
|
||||
<span :id="`${select.id}-text`" class="select-btn-name">
|
||||
{{
|
||||
props.maxBtnChars &&
|
||||
(select.value || props.value).length > +props.maxBtnChars
|
||||
|
|
@ -372,7 +374,7 @@ const emits = defineEmits(["inp"]);
|
|||
ref="selectDropdown"
|
||||
role="radiogroup"
|
||||
:style="{ width: selectWidth }"
|
||||
:id="`${props.id}-custom`"
|
||||
:id="`${select.id}-custom`"
|
||||
:class="[select.isOpen ? 'open' : 'close']"
|
||||
class="select-dropdown-container"
|
||||
:aria-description="$t('inp_select_dropdown_desc')"
|
||||
|
|
@ -392,9 +394,9 @@ const emits = defineEmits(["inp"]);
|
|||
'select-dropdown-btn',
|
||||
]"
|
||||
data-select-item
|
||||
:data-setting-id="props.id"
|
||||
:data-setting-id="select.id"
|
||||
:data-setting-value="value"
|
||||
:aria-controls="`${props.id}-text`"
|
||||
:aria-controls="`${select.id}-text`"
|
||||
:aria-checked="
|
||||
(select.value && select.value === value) ||
|
||||
(!select.value && value === props.value)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script setup>
|
||||
import { onMounted } from "vue";
|
||||
import { defineProps, defineEmits, reactive } from "vue";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { useUUID } from "@utils/global.js";
|
||||
/**
|
||||
@name Forms/Error/Field.vue
|
||||
@description This component is an alert type to send feedback to the user.
|
||||
|
|
@ -62,7 +62,7 @@ const props = defineProps({
|
|||
|
||||
const alert = reactive({
|
||||
visible: true,
|
||||
id: uuidv4(),
|
||||
id: "",
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
|
|
@ -71,6 +71,8 @@ onMounted(() => {
|
|||
alert.visible = false;
|
||||
}, props.delayToClose);
|
||||
}
|
||||
|
||||
alert.id = useUUID(alert.id);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<script setup>
|
||||
import { computed, ref, watch, onBeforeMount, onMounted } from "vue";
|
||||
import { computed, ref, reactive, onMounted } from "vue";
|
||||
import { contentIndex } from "@utils/tabindex.js";
|
||||
import Container from "@components/Widget/Container.vue";
|
||||
import Icons from "@components/Widget/Icons.vue";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { useUUID } from "@utils/global.js";
|
||||
|
||||
/**
|
||||
@name Widget/Button.vue
|
||||
|
|
@ -38,7 +38,7 @@ const props = defineProps({
|
|||
id: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: uuidv4(),
|
||||
default: "",
|
||||
},
|
||||
// valid || delete || info
|
||||
text: {
|
||||
|
|
@ -103,6 +103,10 @@ const props = defineProps({
|
|||
},
|
||||
});
|
||||
|
||||
const btn = reactive({
|
||||
id: props.id,
|
||||
});
|
||||
|
||||
const btnEl = ref();
|
||||
|
||||
const buttonClass = computed(() => {
|
||||
|
|
@ -110,6 +114,7 @@ const buttonClass = computed(() => {
|
|||
});
|
||||
|
||||
onMounted(() => {
|
||||
btn.id = useUUID(btn.id);
|
||||
setAttrs();
|
||||
});
|
||||
|
||||
|
|
@ -134,7 +139,7 @@ function setAttrs() {
|
|||
<button
|
||||
:type="props.type"
|
||||
ref="btnEl"
|
||||
:id="props.id"
|
||||
:id="btn.id"
|
||||
@click="
|
||||
(e) => {
|
||||
if (e.target.getAttribute('type') !== 'submit') e.preventDefault();
|
||||
|
|
@ -143,7 +148,7 @@ function setAttrs() {
|
|||
:tabindex="props.tabId"
|
||||
:class="[buttonClass]"
|
||||
:disabled="props.disabled || false"
|
||||
:aria-labelledby="`text-${props.id}`"
|
||||
:aria-labelledby="`text-${btn.id}`"
|
||||
>
|
||||
<span
|
||||
:class="[
|
||||
|
|
@ -151,7 +156,7 @@ function setAttrs() {
|
|||
props.iconName ? 'mr-2' : '',
|
||||
'pointer-events-none',
|
||||
]"
|
||||
:id="`text-${props.id}`"
|
||||
:id="`text-${btn.id}`"
|
||||
>{{ $t(props.text, props.text) }}
|
||||
</span>
|
||||
<Icons
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<script setup>
|
||||
import Flex from "@components/Widget/Flex.vue";
|
||||
import Button from "@components/Widget/Button.vue";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
/**
|
||||
@name Widget/ButtonGroup.vue
|
||||
|
|
@ -37,17 +36,11 @@ import { v4 as uuidv4 } from "uuid";
|
|||
},
|
||||
],
|
||||
}
|
||||
@param {string} [id=uuidv4()] - Unique id of the button group
|
||||
@param {string} [groupClass="justify-center align-center"] - Additional class for the flex container
|
||||
@param {array} buttons - List of buttons to display. Button component is used.
|
||||
*/
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: uuidv4(),
|
||||
},
|
||||
groupClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import Title from "@components/Widget/Title.vue";
|
|||
import Status from "@components/Widget/Status.vue";
|
||||
import ContentDetailList from "@components/Content/DetailList.vue";
|
||||
import ButtonGroup from "@components/Widget/ButtonGroup.vue";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
/**
|
||||
@name Widget/Instance.vue
|
||||
@description This component is an instance widget.
|
||||
|
|
@ -33,7 +32,6 @@ import { v4 as uuidv4 } from "uuid";
|
|||
},
|
||||
]
|
||||
}
|
||||
@param {string} [id=uuid()] - Unique id of the instance
|
||||
@param {string} title
|
||||
@param {string} status
|
||||
@param {array} details - List of details to display
|
||||
|
|
@ -41,11 +39,6 @@ import { v4 as uuidv4 } from "uuid";
|
|||
*/
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: uuidv4(),
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
|
@ -74,7 +67,6 @@ const props = defineProps({
|
|||
<Title type="card" :title="props.title" />
|
||||
<ContentDetailList :details="props.details" />
|
||||
<ButtonGroup
|
||||
:id="props.id"
|
||||
:buttons="props.buttons"
|
||||
:groupClass="'justify-end align-center'"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script setup>
|
||||
import { defineProps, computed } from "vue";
|
||||
import { defineProps, computed, onMounted } from "vue";
|
||||
import { useUUID } from "@utils/global.js";
|
||||
|
||||
/**
|
||||
@name Icon/Status.vue
|
||||
|
|
@ -19,7 +20,7 @@ const props = defineProps({
|
|||
id: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "1",
|
||||
default: "",
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
|
|
@ -33,6 +34,10 @@ const props = defineProps({
|
|||
},
|
||||
});
|
||||
|
||||
const status = reactive({
|
||||
id: props.id,
|
||||
});
|
||||
|
||||
const statusDesc = computed(() => {
|
||||
if (props.status === "success")
|
||||
return ["dashboard_status_success", "status active or success."];
|
||||
|
|
@ -43,15 +48,19 @@ const statusDesc = computed(() => {
|
|||
if (props.status === "info")
|
||||
return ["dashboard_status_info", "status loading or waiting or unknown."];
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
status.id = useUUID(status.id);
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div :class="[props.statusClass, 'status-svg-container']">
|
||||
<div
|
||||
role="img"
|
||||
:aria-labelledby="`status-${props.id}`"
|
||||
:aria-labelledby="`status-${status.id}`"
|
||||
:class="[props.status, 'status-icon']"
|
||||
></div>
|
||||
<p :id="`status-${props.id}`" class="sr-only">
|
||||
<p :id="`status-${status.id}`" class="sr-only">
|
||||
{{ $t(statusDesc[0], statusDesc[1]) }}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
/**
|
||||
@name utils/global.js
|
||||
@description This file contains global utils that will be used in the application by default.
|
||||
|
|
@ -63,4 +65,16 @@ function isElHidden(el) {
|
|||
: false;
|
||||
}
|
||||
|
||||
export { useGlobal };
|
||||
/**
|
||||
@name useUUID
|
||||
@description This function return a unique identifier uuidv4 after waiting some random time.
|
||||
*/
|
||||
function useUUID(id = "") {
|
||||
if (id) return id;
|
||||
// Generate a random number between 0 and 10000 to avoid duplicate uuids when some components are rendered at the same time
|
||||
const random = Math.floor(Math.random() * 10000);
|
||||
|
||||
return uuidv4() + random;
|
||||
}
|
||||
|
||||
export { useGlobal, useUUID };
|
||||
|
|
|
|||
Loading…
Reference in a new issue