Curseur - DsfrRange 
🌟 Introduction 
Bienvenue dans la documentation du DsfrRange, un composant Vue qui va slider dans votre coeur comme un croissant bien chaud glisse dans votre petit déjeuner. Ce composant est un véritable couteau suisse pour les curseurs, capable de tout faire, de l'affichage simple à la gestion de valeurs doubles. Mettez vos ceintures, on décolle !
Les curseurs sont des entrées numériques qui permettent de voir graphiquement une sélection par rapport a une valeur minimale et maximale. Ils servent à montrer en temps réel les options choisies et éclairer la prise de décision. ("Why so serious?" 🦇🃏)
🏅 La documentation sur le curseur importante sur le DSFR
La story sur le curseur importante sur le storybook de VueDsfr📐 Structure 
- Le composant est encapsulé dans une divavec la classefr-range-group, qui peut afficher un message d'erreur viamessage.
- Le labelest affiché en haut, suivi par un texte d'indice (hint) si fourni.
- Le curseur (input type="range") est stylisé avec des classes pour gérer la taille et l'état désactivé.
- Les valeurs minimales et maximales sont affichées si hideIndicatorsestfalse.
- Un second curseur est présent si la prop doubleesttrue.
- Les messages d'erreur ou autres sont affichés dans une divspécifique.
🛠️ Props 
| Nom | Type | Défaut | Description | 
|---|---|---|---|
| id | string | useRandomId('range') | Identifiant unique du curseur. Si non fourni, un id est généré aléatoirement. | 
| min | number | 0 | Valeur minimale du curseur. | 
| max | number | 100 | Valeur maximale du curseur. | 
| modelValue | number | 0 | Valeur actuelle du curseur. | 
| label | string | - | Texte de l'étiquette associée au curseur. | 
| hint | string | undefined | Texte d'indice optionnel. | 
| message | string | undefined | Message à afficher en cas d'erreur. | 
| prefix | string | undefined | Texte à afficher avant la valeur. | 
| suffix | string | undefined | Texte à afficher après la valeur. | 
| small | boolean | undefined | Si true, réduit la taille du curseur. | 
| hideIndicators | boolean | undefined | Cache les indicateurs de valeur min/max si true. | 
| step | number | undefined | Pas d'incrément du curseur. | 
| double | boolean | undefined | Active un second curseur si true. | 
| disabled | boolean | undefined | Désactive le curseur si true. | 
📡 Évenements 
- update:modelValue: Émis lors de la modification de la valeur du curseur. Renvoie la nouvelle valeur.
📝 Exemple 
<script lang="ts" setup>
import { ref } from 'vue'
import DsfrRange from '../DsfrRange.vue'
const value = ref<number>(100)
const value2 = ref<number>(100)
const lowerValue = ref<number>(0)
</script>
<template>
  <div class="fr-container fr-py-4w">
    <div>
      <DsfrRange
        v-model="value"
        label="Label du curseur"
      />
    </div>
    <p>
      {{ value }}
    </p>
    <div>
      <DsfrRange
        v-model="value2"
        v-model:lower-value="lowerValue"
        label="Label du curseur"
      />
    </div>
    <p>
      {{ lowerValue }} - {{ value2 }}
    </p>
  </div>
</template><script lang="ts" setup>
import type { DsfrRangeProps } from './DsfrRange.types'
import { computed, onMounted, ref, watch } from 'vue'
import { useRandomId } from '../../utils/random-utils'
const props = withDefaults(defineProps<DsfrRangeProps>(), {
  id: () => useRandomId('range'),
  min: 0,
  max: 100,
  modelValue: 0,
  lowerValue: undefined,
  hint: undefined,
  message: undefined,
  prefix: undefined,
  suffix: undefined,
  step: undefined,
})
const emit = defineEmits<{
  (e: 'update:modelValue', payload: string | number): void
  (e: 'update:lowerValue', payload: string | number): void
}>()
const input = ref<HTMLInputElement>()
const output = ref<HTMLSpanElement>()
const inputWidth = ref()
const double = computed(() => props.lowerValue !== undefined)
const stepped = computed(() => props.step !== undefined)
const outputStyle = computed(() => {
  if (props.lowerValue === undefined) {
    const translateXValue = (props.modelValue - props.min) / (props.max - props.min) * inputWidth.value
    return `transform: translateX(${translateXValue}px) translateX(-${translateXValue / inputWidth.value * 100}%);`
  }
  const translateXValue = (props.modelValue + props.lowerValue - props.min) / 2 / (props.max - props.min) * inputWidth.value
  return `transform: translateX(${translateXValue}px) translateX(-${props.lowerValue + ((props.modelValue - props.lowerValue) / 2)}%);`
})
const rangeStyle = computed(() => {
  const range = props.max - props.min
  const ratioRight = (props.modelValue - props.min) / range
  const ratioLeft = ((props.lowerValue ?? 0) - props.min) / range
  const innerPadding = props.small ? 12 : 24
  const stepWidth = (inputWidth.value - innerPadding) / (range / (props.step ?? 1 + 1))
  const paddingRight = double.value ? 32 * (1 - ratioRight) : 0
  return {
    '--progress-right': `${(ratioRight * inputWidth.value + paddingRight).toFixed(2)}px`,
    ...(double.value ? { '--progress-left': `${(ratioLeft * inputWidth.value).toFixed(2)}px` } : {}),
    ...(stepped.value ? { '--step-width': `${Math.floor(stepWidth)}px` } : {}),
  }
})
watch([() => props.modelValue, () => props.lowerValue], ([upper, lower]) => {
  if (lower === undefined) {
    return
  }
  if (double.value && upper < lower) {
    emit('update:lowerValue', upper)
  }
  if (double.value && lower > upper) {
    emit('update:modelValue', lower)
  }
})
const outputValue = computed(() => {
  return (props.prefix ?? '')
    .concat(double.value ? `${props.lowerValue} - ` : '')
    .concat(`${props.modelValue}`)
    .concat(props.suffix ?? '')
})
onMounted(() => {
  inputWidth.value = input.value?.offsetWidth
})
</script>
<template>
  <div
    :id="`${id}-group`"
    class="fr-range-group"
    :class="{ 'fr-range-group--error': message }"
  >
    <label
      :id="`${id}-label`"
      class="fr-label"
    >
      <slot name="label">
        {{ label }}
      </slot>
      <span class="fr-hint-text">
        <slot name="hint">
          {{ hint }}
        </slot>
      </span>
    </label>
    <div
      class="fr-range"
      data-fr-js-range="true"
      :class="{
        'fr-range--sm': small,
        'fr-range--step': stepped,
        'fr-range--double': double,
        'fr-range-group--disabled': disabled,
      }"
      :data-fr-prefix="prefix ?? undefined"
      :data-fr-suffix="suffix ?? undefined"
      :style="rangeStyle"
    >
      <span
        ref="output"
        class="fr-range__output"
        data-fr-js-range-output="true"
        :style="outputStyle"
      >{{ outputValue }}</span>
      <input
        v-if="double"
        :id="`${id}-2`"
        type="range"
        :min="min"
        :max="max"
        :step="step"
        :value="lowerValue"
        :disabled="disabled"
        :aria-disabled="disabled"
        :aria-labelledby="`${id}-label`"
        :aria-describedby="`${id}-messages`"
        @input="emit('update:lowerValue', +($event.target as HTMLInputElement)?.value)"
      >
      <input
        :id="id"
        ref="input"
        type="range"
        :min="min"
        :max="max"
        :step="step"
        :value="modelValue"
        :disabled="disabled"
        :aria-disabled="disabled"
        :aria-labelledby="`${id}-label`"
        :aria-describedby="`${id}-messages`"
        @input="emit('update:modelValue', +($event.target as HTMLInputElement)?.value)"
      >
      <span
        v-if="!hideIndicators"
        class="fr-range__min"
        aria-hidden="true"
        data-fr-js-range-limit="true"
      >{{ min }}</span>
      <span
        v-if="!hideIndicators"
        class="fr-range__max"
        aria-hidden="true"
        data-fr-js-range-limit="true"
      >{{ max }}</span>
    </div>
    <div
      v-if="message || $slots.messages"
      :id="`${id}-messages`"
      class="fr-messages-group"
      aria-live="polite"
      role="alert"
    >
      <slot name="messages">
        <p
          v-if="message"
          :id="`${id}-message-error`"
          class="fr-message fr-message--error"
        >
          {{ message }}
        </p>
      </slot>
    </div>
  </div>
</template>export type DsfrRangeProps = {
  id?: string
  min?: number
  max?: number
  modelValue?: number
  lowerValue?: number
  label: string
  hint?: string
  message?: string
  prefix?: string
  suffix?: string
  small?: boolean
  hideIndicators?: boolean
  step?: number
  disabled?: boolean
}Et voilà ! Notre DsfrRange est prêt à être croqué dans vos interfaces comme une baguette bien croustillante. N'oubliez pas de l'assaisonner avec vos styles et logiques pour qu'il s'intègre parfaitement dans le festin visuel de votre application. Bon codage ! 🥖💻
