// Composables import { useProxiedModel } from "./proxiedModel.mjs"; // Utilities import { computed, inject, onBeforeUnmount, onMounted, provide, reactive, toRef, watch } from 'vue'; import { consoleWarn, deepEqual, findChildrenWithProvide, getCurrentInstance, getUid, propsFactory, wrapInArray } from "../util/index.mjs"; // Types export const makeGroupProps = propsFactory({ modelValue: { type: null, default: undefined }, multiple: Boolean, mandatory: [Boolean, String], max: Number, selectedClass: String, disabled: Boolean }, 'group'); export const makeGroupItemProps = propsFactory({ value: null, disabled: Boolean, selectedClass: String }, 'group-item'); export function useGroupItem(props, injectKey) { let required = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; const vm = getCurrentInstance('useGroupItem'); if (!vm) { throw new Error('[Vuetify] useGroupItem composable must be used inside a component setup function'); } const id = getUid(); provide(Symbol.for(`${injectKey.description}:id`), id); const group = inject(injectKey, null); if (!group) { if (!required) return group; throw new Error(`[Vuetify] Could not find useGroup injection with symbol ${injectKey.description}`); } const value = toRef(props, 'value'); const disabled = computed(() => !!(group.disabled.value || props.disabled)); group.register({ id, value, disabled }, vm); onBeforeUnmount(() => { group.unregister(id); }); const isSelected = computed(() => { return group.isSelected(id); }); const selectedClass = computed(() => isSelected.value && [group.selectedClass.value, props.selectedClass]); watch(isSelected, value => { vm.emit('group:selected', { value }); }); return { id, isSelected, toggle: () => group.select(id, !isSelected.value), select: value => group.select(id, value), selectedClass, value, disabled, group }; } export function useGroup(props, injectKey) { let isUnmounted = false; const items = reactive([]); const selected = useProxiedModel(props, 'modelValue', [], v => { if (v == null) return []; return getIds(items, wrapInArray(v)); }, v => { const arr = getValues(items, v); return props.multiple ? arr : arr[0]; }); const groupVm = getCurrentInstance('useGroup'); function register(item, vm) { // Is there a better way to fix this typing? const unwrapped = item; const key = Symbol.for(`${injectKey.description}:id`); const children = findChildrenWithProvide(key, groupVm?.vnode); const index = children.indexOf(vm); if (index > -1) { items.splice(index, 0, unwrapped); } else { items.push(unwrapped); } } function unregister(id) { if (isUnmounted) return; // TODO: re-evaluate this line's importance in the future // should we only modify the model if mandatory is set. // selected.value = selected.value.filter(v => v !== id) forceMandatoryValue(); const index = items.findIndex(item => item.id === id); items.splice(index, 1); } // If mandatory and nothing is selected, then select first non-disabled item function forceMandatoryValue() { const item = items.find(item => !item.disabled); if (item && props.mandatory === 'force' && !selected.value.length) { selected.value = [item.id]; } } onMounted(() => { forceMandatoryValue(); }); onBeforeUnmount(() => { isUnmounted = true; }); function select(id, value) { const item = items.find(item => item.id === id); if (value && item?.disabled) return; if (props.multiple) { const internalValue = selected.value.slice(); const index = internalValue.findIndex(v => v === id); const isSelected = ~index; value = value ?? !isSelected; // We can't remove value if group is // mandatory, value already exists, // and it is the only value if (isSelected && props.mandatory && internalValue.length <= 1) return; // We can't add value if it would // cause max limit to be exceeded if (!isSelected && props.max != null && internalValue.length + 1 > props.max) return; if (index < 0 && value) internalValue.push(id);else if (index >= 0 && !value) internalValue.splice(index, 1); selected.value = internalValue; } else { const isSelected = selected.value.includes(id); if (props.mandatory && isSelected) return; selected.value = value ?? !isSelected ? [id] : []; } } function step(offset) { // getting an offset from selected value obviously won't work with multiple values if (props.multiple) consoleWarn('This method is not supported when using "multiple" prop'); if (!selected.value.length) { const item = items.find(item => !item.disabled); item && (selected.value = [item.id]); } else { const currentId = selected.value[0]; const currentIndex = items.findIndex(i => i.id === currentId); let newIndex = (currentIndex + offset) % items.length; let newItem = items[newIndex]; while (newItem.disabled && newIndex !== currentIndex) { newIndex = (newIndex + offset) % items.length; newItem = items[newIndex]; } if (newItem.disabled) return; selected.value = [items[newIndex].id]; } } const state = { register, unregister, selected, select, disabled: toRef(props, 'disabled'), prev: () => step(items.length - 1), next: () => step(1), isSelected: id => selected.value.includes(id), selectedClass: computed(() => props.selectedClass), items: computed(() => items), getItemIndex: value => getItemIndex(items, value) }; provide(injectKey, state); return state; } function getItemIndex(items, value) { const ids = getIds(items, [value]); if (!ids.length) return -1; return items.findIndex(item => item.id === ids[0]); } function getIds(items, modelValue) { const ids = []; modelValue.forEach(value => { const item = items.find(item => deepEqual(value, item.value)); const itemByIndex = items[value]; if (item?.value != null) { ids.push(item.id); } else if (itemByIndex != null) { ids.push(itemByIndex.id); } }); return ids; } function getValues(items, ids) { const values = []; ids.forEach(id => { const itemIndex = items.findIndex(item => item.id === id); if (~itemIndex) { const item = items[itemIndex]; values.push(item.value != null ? item.value : itemIndex); } }); return values; } //# sourceMappingURL=group.mjs.map