157 lines
3.0 KiB
Vue
157 lines
3.0 KiB
Vue
<template>
|
|
<div class="choice-checklist" :id="`${id}-wrapper`">
|
|
<div class="choice-checklist-controls">
|
|
<button type="button" class="choice-checklist-control" @click="selectAll">
|
|
Select all
|
|
</button>
|
|
<button type="button" class="choice-checklist-control" @click="selectNone">
|
|
Select none
|
|
</button>
|
|
</div>
|
|
<fieldset class="choice-checklist-fieldset">
|
|
<legend class="visually-hidden">{{ label || name }}</legend>
|
|
<label
|
|
v-for="(choice, index) in choices"
|
|
:key="choice.value"
|
|
class="choice-checklist-item"
|
|
:for="`${id}-${index}`"
|
|
>
|
|
<input
|
|
:id="`${id}-${index}`"
|
|
type="checkbox"
|
|
:checked="isSelected(choice.value)"
|
|
@change="handleToggle(choice.value)"
|
|
/>
|
|
<span>{{ choiceLabel(choice) }}</span>
|
|
</label>
|
|
</fieldset>
|
|
<input
|
|
:id="`${id}-value`"
|
|
:name="name"
|
|
type="text"
|
|
class="visually-hidden choice-checklist-value"
|
|
:value="modelValue"
|
|
:required="required"
|
|
tabindex="-1"
|
|
aria-hidden="true"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed } from 'vue'
|
|
import {
|
|
allChoiceValues,
|
|
choiceLabel,
|
|
formatChecklistValue,
|
|
parseChecklistValue,
|
|
toggleChoice
|
|
} from '../utils/choiceChecklistHelpers.js'
|
|
|
|
const props = defineProps({
|
|
id: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
name: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
label: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
choices: {
|
|
type: Array,
|
|
required: true
|
|
},
|
|
modelValue: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
required: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
})
|
|
|
|
const emit = defineEmits(['update:modelValue'])
|
|
|
|
const selectedValues = computed(() => parseChecklistValue(props.modelValue))
|
|
|
|
function isSelected(value) {
|
|
return selectedValues.value.includes(value)
|
|
}
|
|
|
|
function emitSelection(selected) {
|
|
emit('update:modelValue', formatChecklistValue(selected))
|
|
}
|
|
|
|
function handleToggle(value) {
|
|
emitSelection(toggleChoice(selectedValues.value, value))
|
|
}
|
|
|
|
function selectAll() {
|
|
emitSelection(allChoiceValues(props.choices))
|
|
}
|
|
|
|
function selectNone() {
|
|
emitSelection([])
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.choice-checklist {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5em;
|
|
}
|
|
|
|
.choice-checklist-controls {
|
|
display: flex;
|
|
gap: 0.75em;
|
|
}
|
|
|
|
.choice-checklist-control {
|
|
background: none;
|
|
border: none;
|
|
color: inherit;
|
|
cursor: pointer;
|
|
font: inherit;
|
|
padding: 0;
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.choice-checklist-fieldset {
|
|
border: none;
|
|
display: grid;
|
|
gap: 0.5em 1em;
|
|
grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.choice-checklist-item {
|
|
align-items: center;
|
|
display: flex;
|
|
gap: 0.4em;
|
|
margin: 0;
|
|
}
|
|
|
|
.choice-checklist-item input[type="checkbox"] {
|
|
margin: 0;
|
|
}
|
|
|
|
.visually-hidden {
|
|
border: 0;
|
|
clip: rect(0 0 0 0);
|
|
height: 1px;
|
|
margin: -1px;
|
|
overflow: hidden;
|
|
padding: 0;
|
|
position: absolute;
|
|
white-space: nowrap;
|
|
width: 1px;
|
|
}
|
|
</style>
|