Skip to content

Commit

Permalink
fix: rename exposed options and items and improve the option API
Browse files Browse the repository at this point in the history
  • Loading branch information
logaretm committed Jan 15, 2025
1 parent c60c421 commit 2c0cc5f
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 21 deletions.
16 changes: 8 additions & 8 deletions packages/core/src/collections/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,26 @@ export interface CollectionInit<TItem> {
/**
* The property to track by, it can be a function that extracts the value from the item. Should be the same as the "value" prop of the option.
*/
trackBy?: string | ((item: TItem) => unknown);
key?: string | ((item: TItem) => unknown);
}

export interface CollectionManager<TItem> {
items: ComputedRef<TItem[]>;
trackBy: (item: TItem) => unknown;
key: (item: TItem) => unknown;
}

// TODO: Implement fetching, loading, pagination, adding a new item, etc...
export function defineCollection<TItem>(init: CollectionInit<TItem>): CollectionManager<TItem> {
const { items, trackBy } = init;
const { items, key } = init;

return {
items: computed(() => toValue(items)),
trackBy:
typeof trackBy === 'function'
? trackBy
key:
typeof key === 'function'
? key
: item => {
if (trackBy && isObject(item)) {
return getFromPath(item, trackBy, item);
if (key && isObject(item)) {
return getFromPath(item, key, item);
}

return item;
Expand Down
31 changes: 27 additions & 4 deletions packages/core/src/useComboBox/useComboBox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,12 @@ export function useComboBox<TOption, TValue = TOption>(
isPopupOpen,
listBoxEl,
selectedOption,
selectedOptions,
focusNext,
focusPrev,
findFocusedOption,
items,
renderedOptions,
} = useListBox<TOption, TValue>({
labeledBy: () => labelledByProps.value['aria-labelledby'],
focusStrategy: 'VIRTUAL_WITH_SELECTED',
Expand All @@ -163,7 +165,20 @@ export function useComboBox<TOption, TValue = TOption>(
onChange(evt) {
inputValue.value = (evt.target as HTMLInputElement).value;
},
onBlur() {
onBlur(evt) {
let relatedTarget = (evt as any).relatedTarget as HTMLElement | null;

Check warning on line 169 in packages/core/src/useComboBox/useComboBox.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
if (relatedTarget) {
relatedTarget = relatedTarget.closest('[role="option"]') as HTMLElement | null;
const opt = renderedOptions.value.find(opt => opt.id === relatedTarget?.id);
if (opt) {
setValue(opt.getValue());
inputValue.value = opt.getLabel() ?? '';
isPopupOpen.value = false;
}

return;
}

setTouched(true);
if (toValue(props.allowCustomValue)) {
return;
Expand All @@ -175,7 +190,7 @@ export function useComboBox<TOption, TValue = TOption>(
}

const item = items.value?.find(item =>
isEqual(collectionOptions?.collection?.trackBy(item.option), fieldValue.value),
isEqual(collectionOptions?.collection?.key(item.option), fieldValue.value),
);

inputValue.value = item?.registration?.getLabel() ?? '';
Expand Down Expand Up @@ -347,13 +362,21 @@ export function useComboBox<TOption, TValue = TOption>(
*/
buttonProps,
/**
* The items in the collection.
* The options in the collection.
*/
items: filteredItems,
options: filteredItems,
/**
* The value of the text field, will contain the label of the selected option or the user input if they are currently typing.
*/
inputValue,
/**
* The selected options if multiple is true.
*/
selectedOptions,
/**
* The selected option if multiple is false.
*/
selectedOption,
},
field,
);
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/useListBox/useListBox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export function useListBox<TOption, TValue = TOption>(
);

function associateOption(registration: OptionRegistration<TValue>) {
const item = collection?.items.value.find(item => isEqual(collection?.trackBy(item), registration.getValue()));
const item = collection?.items.value.find(item => isEqual(collection?.key(item), registration.getValue()));
if (!item) {
return;
}
Expand Down
25 changes: 17 additions & 8 deletions packages/playground/src/components/ComboBox.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<script setup lang="ts" generic="TOption extends { label: string }, TValue">
<script setup lang="ts" generic="TOption extends { label: string; value: string }, TValue">
import { useComboBox, ComboBoxProps, defineCollection, useDefaultFilter } from '@formwerk/core';
import Option from './OptionItem.vue';
import { useFuzzyList } from '../composables/useFuzzyFilter';
Expand All @@ -9,8 +9,13 @@ interface Props extends ComboBoxProps<TOption, TValue> {
const props = defineProps<Props>();
const { contains } = useDefaultFilter({
caseSensitive: false,
});
const collection = defineCollection({
items: props.options,
items: () => props.options,
key: 'value',
});
const {
Expand All @@ -21,20 +26,24 @@ const {
errorMessageProps,
errorMessage,
descriptionProps,
inputValue,
options,
selectedOptions,
} = useComboBox(props, {
collection,
filter: contains,
});
const results = useFuzzyList(props.options, ['label'], inputValue);
</script>

<template>
<div class="select-field">
<p v-bind="labelProps">{{ label }}</p>

<div class="flex items-center gap-2">
<input v-bind="inputProps" type="text" />
<div>
<!-- <span v-for="item in selectedOptions" :key="item.label">{{ item.label }}</span> -->

<input v-bind="inputProps" type="text" />
</div>

<button v-bind="buttonProps">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
Expand All @@ -48,10 +57,10 @@ const results = useFuzzyList(props.options, ['label'], inputValue);
<div v-bind="listBoxProps" popover>
<slot>
<Option
v-for="(option, idx) in results"
v-for="(option, idx) in options"
:key="option.label"
:label="option.label"
:value="option"
:value="option.value"
:index="idx"
/>
</slot>
Expand Down

0 comments on commit 2c0cc5f

Please sign in to comment.