ldc-boutique-cnc-sable/resources/ts/fo/components/WordCounterComponent.vue
2026-10-27 09:58:12 +01:00

140 lines
3.3 KiB
Vue

<template>
<div class="word-counter-varchar d-flex flex-row justify-content-around align-items-center w-100">
<span>
{{ __("vue.WordCounterComponent.words") }}&nbsp;:&nbsp;<b>{{ inTwords }}</b>
</span>
<span>
{{ __("vue.WordCounterComponent.characters") }}&nbsp;:&nbsp;<b>{{ inTchars }}</b>
</span>
</div>
</template>
<script setup lang="ts">
import {
nextTick,
onBeforeUnmount,
onMounted,
ref,
useAttrs,
watch
} from "vue";
import { __ } from "@modules/trans";
import z from "@modules/zod";
defineOptions({ name: "WordCounterComponent" });
const props = defineProps({
for: {
type: String,
default: null,
},
});
const attrs = useAttrs();
const inTid = ref<string>("");
const inTwords = ref<number>(0);
const inTchars = ref<number>(0);
const inTinput = ref<HTMLInputElement | HTMLTextAreaElement | null>(null);
const calculateValues = (string: string): [number, number] => {
const arr = string.split(" ");
return [arr.filter((word) => word !== "").length, string.length];
};
const updateValues = (string: string) => {
const [words, chars] = calculateValues(string);
inTwords.value = words;
inTchars.value = chars;
};
const handleInput = (e: Event) => {
const el = e.target as HTMLInputElement | HTMLTextAreaElement | null;
if (!el) {
throw new Error(`${inTid.value} is not an input or textarea`);
}
updateValues(el.value);
};
onMounted(() => {
const json = String(attrs.json ?? "{}");
let data;
try {
data = z.object({
for: z.string()
.nonempty()
.refine((val) => globalThis.document
.getElementById(val), {message: "The 'for' field must not be empty and exist in the DOM" })
}).parse(JSON.parse(json));
} catch (e) {
console.error(`Invalid JSON data for WordCounterComponent: ${e}`);
data = {};
}
inTid.value = String(props.for ?? "");
if (data.for) {
inTid.value = data.for;
}
});
watch(
() => props.for,
(newVal) => {
if (newVal) {
inTid.value = newVal;
}
}
);
watch(inTid, async (newVal) => {
if (!newVal) return;
await nextTick();
const input = document.getElementById(newVal);
if (!input || !(input instanceof HTMLInputElement || input instanceof HTMLTextAreaElement)) {
throw new Error(`${newVal} is not an input or textarea`);
}
inTinput.value = input;
});
watch(inTinput, (newInput, oldInput) => {
if (oldInput) {
oldInput.removeEventListener("input", handleInput);
}
if (!newInput) return;
newInput.addEventListener("input", handleInput);
updateValues(newInput.value);
});
// Éviter les fuites de mémoire lors de la destruction du composant
onBeforeUnmount(() => {
if (inTinput.value) {
inTinput.value.removeEventListener("input", handleInput);
}
});
</script>
<style lang="scss">
#word-counter+input[type="text"],
.word-counter+input[type="text"],
#word-counter+textarea,
.word-counter+textarea,
#word-counter-varchar+textarea,
.word-counter-varchar+textarea {
border-radius: 0 0 var(--bs-border-radius-xl) var(--bs-border-radius-xl);
}
.word-counter-varchar {
color: var(--bs-body-color);
border: var(--bs-border-width) solid var(--bs-border-color);
border-bottom: 0;
background-color: rgb(var(--bs-secondary-bg-rgb));
border-top-left-radius: var(--bs-border-radius-xl);
border-top-right-radius: var(--bs-border-radius-xl);
}
</style>