Skip to content

Commit b9fbc20

Browse files
Merge pull request #4 from thejackshelton/feat/select-dev
Feat/select dev
2 parents b0ca702 + a7d365a commit b9fbc20

File tree

10 files changed

+36
-55
lines changed

10 files changed

+36
-55
lines changed

apps/website/src/routes/docs/headless/select/examples/wrong-value.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export default component$(() => {
1414
<>
1515
<Select value="Jessi" class="relative min-w-40">
1616
<SelectTrigger class="w-full border-2 border-dashed border-red-400">
17-
<SelectValue placeholder="Select an option" />
17+
<SelectValue placeholder="wrong value placeholder" />
1818
</SelectTrigger>
1919
<SelectListbox class="absolute w-full border-2 border-dashed border-green-400 bg-slate-900 p-2">
2020
{usersSig.value.map((user) => (

apps/website/src/routes/docs/headless/select/select.driver.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@ export function createTestDriver<T extends DriverLocator>(locator: T) {
2323
// annoyingly, it seems we need to check if the listbox is hidden in playwright, or else the value does not update
2424
await expect(getListbox()).toBeHidden();
2525

26-
return await getTrigger()
27-
.locator('[data-value]', { includeHidden: true })
28-
.textContent();
26+
return await getTrigger().locator('[data-value]').textContent();
2927
};
3028

3129
const openListbox = async (key: OpenKeys | 'click') => {

apps/website/src/routes/docs/headless/select/select.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,7 @@ test.describe('Keyboard Behavior', () => {
383383
await getTrigger().press('Enter');
384384

385385
const value = await getValue();
386+
console.log(value);
386387
expect(optStr).toEqual(value);
387388
});
388389

packages/kit-headless/src/components/select/select-context.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ export type SelectContext = {
1212
triggerRef: Signal<HTMLButtonElement | undefined>;
1313
popoverRef: Signal<HTMLElement | undefined>;
1414
listboxRef: Signal<HTMLUListElement | undefined>;
15-
optionRefsArray: Signal<Array<Signal<HTMLLIElement | undefined>>>;
16-
selectedOptionRef: Signal<HTMLLIElement | null>;
1715

1816
// core state
1917
options: Opt[] | undefined;

packages/kit-headless/src/components/select/select-inline.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export type Opt = {
1414
*/
1515
export const Select: FunctionComponent<SelectProps> = (props) => {
1616
const { children: myChildren, ...rest } = props;
17-
let valuePropIndex = 0;
17+
let valuePropIndex = null;
1818
const childrenToProcess = (
1919
Array.isArray(myChildren) ? [...myChildren] : [myChildren]
2020
) as Array<JSXNode>;
@@ -74,7 +74,8 @@ export const Select: FunctionComponent<SelectProps> = (props) => {
7474
}
7575
}
7676
const isDisabledArr = opts.map((opt) => opt.isDisabled);
77-
if (isDisabledArr[valuePropIndex] === true) {
77+
78+
if (valuePropIndex !== null && isDisabledArr[valuePropIndex] === true) {
7879
valuePropIndex = isDisabledArr.findIndex((isDisabled) => isDisabled === false);
7980
if (valuePropIndex === -1) {
8081
throw new Error(
@@ -83,8 +84,15 @@ export const Select: FunctionComponent<SelectProps> = (props) => {
8384
}
8485
}
8586

87+
// const isMatch = opts[valuePropIndex].value === props.value;
88+
89+
// if (!isMatch && props.value) {
90+
// const obj = opts[valuePropIndex];
91+
// obj.value = '';
92+
// opts[valuePropIndex] = obj;
93+
// }
8694
return (
87-
<SelectImpl {...rest} _valuePropIndex={valuePropIndex}>
95+
<SelectImpl {...rest} _valuePropIndex={valuePropIndex} _options={opts}>
8896
{props.children}
8997
</SelectImpl>
9098
);

packages/kit-headless/src/components/select/select-option.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@ export const SelectOption = component$<SelectOptionProps>((props) => {
3535
throw Error('Qwik UI: Select component option cannot find its proper index.');
3636

3737
localIndexSig.value = index;
38-
39-
context.optionRefsArray.value[index] = optionRef;
4038
});
4139

4240
const handleClick$ = $(() => {

packages/kit-headless/src/components/select/select-trigger.tsx

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
11
import { component$, type PropsOf, useContext, sync$, $, Slot } from '@builder.io/qwik';
22
import SelectContextId from './select-context';
33
import { getNextEnabledOptionIndex, getPrevEnabledOptionIndex } from './utils';
4-
5-
export type OptionsType = {
6-
element: HTMLLIElement;
7-
isDisabled: boolean;
8-
}[];
94
export type OpenKeys = 'ArrowUp' | 'Enter' | 'Space' | 'ArrowDown';
105

116
type SelectTriggerProps = PropsOf<'button'>;
12-
export type DisabledArr = Array<{ disabled: boolean }>;
137
export const SelectTrigger = component$<SelectTriggerProps>((props) => {
148
const context = useContext(SelectContextId);
159
const openKeys = ['ArrowUp', 'ArrowDown'];
@@ -30,17 +24,7 @@ export const SelectTrigger = component$<SelectTriggerProps>((props) => {
3024
const handleKeyDown$ = $((e: KeyboardEvent) => {
3125
const shouldOpen = !context.isListboxOpenSig.value && openKeys.includes(e.key);
3226
const shouldClose = context.isListboxOpenSig.value && closedKeys.includes(e.key);
33-
34-
// TODO: refactor with data grabbed from inline component
35-
const options: OptionsType = context.optionRefsArray.value.map((option) => {
36-
if (option.value === undefined) {
37-
throw new Error('Qwik UI: internal select option is undefined');
38-
}
39-
40-
const isDisabled = option.value.ariaDisabled === 'true';
41-
42-
return { element: option.value, isDisabled };
43-
});
27+
if (!context.options) return;
4428

4529
if (shouldOpen) {
4630
context.isListboxOpenSig.value = true;
@@ -51,32 +35,35 @@ export const SelectTrigger = component$<SelectTriggerProps>((props) => {
5135
}
5236

5337
if (e.key === 'Home') {
54-
context.highlightedIndexSig.value = getNextEnabledOptionIndex(-1, options);
38+
context.highlightedIndexSig.value = getNextEnabledOptionIndex(-1, context.options);
5539
}
5640

5741
if (e.key === 'End') {
58-
const lastEnabledOptionIndex = getPrevEnabledOptionIndex(options.length, options);
42+
const lastEnabledOptionIndex = getPrevEnabledOptionIndex(
43+
context.options.length,
44+
context.options,
45+
);
5946
context.highlightedIndexSig.value = lastEnabledOptionIndex;
6047
}
6148

6249
/** When initially opening the listbox, we want to grab the first enabled option index */
6350
if (context.highlightedIndexSig.value === null) {
64-
context.highlightedIndexSig.value = getNextEnabledOptionIndex(-1, options);
51+
context.highlightedIndexSig.value = getNextEnabledOptionIndex(-1, context.options);
6552
return;
6653
}
6754

6855
if (context.isListboxOpenSig.value && !shouldOpen) {
6956
if (e.key === 'ArrowDown') {
7057
context.highlightedIndexSig.value = getNextEnabledOptionIndex(
7158
context.highlightedIndexSig.value,
72-
options,
59+
context.options,
7360
);
7461
}
7562

7663
if (e.key === 'ArrowUp') {
7764
context.highlightedIndexSig.value = getPrevEnabledOptionIndex(
7865
context.highlightedIndexSig.value,
79-
options,
66+
context.options,
8067
);
8168
}
8269

packages/kit-headless/src/components/select/select-value.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,21 @@ import { component$, useContext, type PropsOf } from '@builder.io/qwik';
33
import SelectContextId from './select-context';
44

55
type SelectValueProps = PropsOf<'span'> & {
6-
placeholder: string;
6+
placeholder?: string;
77
};
88

99
export const SelectValue = component$((props: SelectValueProps) => {
1010
const context = useContext(SelectContextId);
11-
const selectedOptStr = context.selectedOptionRef.value?.textContent;
11+
if (!context.options) return;
12+
13+
const selectedOptStr =
14+
context.selectedIndexSig.value !== null
15+
? context.options[context.selectedIndexSig.value].value
16+
: props.placeholder;
1217

1318
return (
1419
<span data-value {...props}>
15-
{selectedOptStr ?? context.value ?? props.placeholder}
20+
{selectedOptStr}
1621
</span>
1722
);
1823
});

packages/kit-headless/src/components/select/select.tsx

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import {
44
type PropsOf,
55
useSignal,
66
useContextProvider,
7-
type Signal,
8-
useTask$,
97
} from '@builder.io/qwik';
108
import { type SelectContext } from './select-context';
119
import SelectContextId from './select-context';
@@ -18,7 +16,7 @@ export type SelectProps = PropsOf<'div'> & {
1816
_options?: Opt[];
1917

2018
// when a value is passed, we check if it's an actual option value, and get its index at pre-render time.
21-
_valuePropIndex?: number;
19+
_valuePropIndex?: number | null;
2220
};
2321

2422
/* root component in select-inline.tsx */
@@ -28,30 +26,18 @@ export const SelectImpl = component$<SelectProps>((props) => {
2826
const triggerRef = useSignal<HTMLButtonElement>();
2927
const popoverRef = useSignal<HTMLElement>();
3028
const listboxRef = useSignal<HTMLUListElement>();
31-
const optionRefsArray = useSignal<Signal<HTMLLIElement>[]>([]);
3229
const value = props.value;
3330
const options = props._options;
3431

3532
// core state
36-
const selectedIndexSig = useSignal<number | null>(null);
37-
const selectedOptionRef = useSignal<HTMLLIElement | null>(null);
33+
const selectedIndexSig = useSignal<number | null>(props._valuePropIndex ?? null);
3834
const isListboxOpenSig = useSignal<boolean>(false);
3935
const highlightedIndexSig = useSignal<number | null>(props._valuePropIndex ?? null);
4036

41-
useTask$(function deriveSelectedRef({ track }) {
42-
track(() => selectedIndexSig.value);
43-
44-
if (selectedIndexSig.value !== null) {
45-
selectedOptionRef.value = optionRefsArray.value[selectedIndexSig.value].value;
46-
}
47-
});
48-
4937
const context: SelectContext = {
5038
triggerRef,
5139
popoverRef,
5240
listboxRef,
53-
selectedOptionRef,
54-
optionRefsArray,
5541
options,
5642
highlightedIndexSig,
5743
isListboxOpenSig,

packages/kit-headless/src/components/select/utils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { OptionsType } from './select-trigger';
1+
import { type Opt } from './select-inline';
22

3-
export const getNextEnabledOptionIndex = (index: number, options: OptionsType) => {
3+
export const getNextEnabledOptionIndex = (index: number, options: Opt[]) => {
44
let offset = 1;
55
let currentIndex = index;
66
const opts = options;
@@ -21,7 +21,7 @@ export const getNextEnabledOptionIndex = (index: number, options: OptionsType) =
2121
return (currentIndex + offset) % len;
2222
};
2323

24-
export const getPrevEnabledOptionIndex = (index: number, options: OptionsType) => {
24+
export const getPrevEnabledOptionIndex = (index: number, options: Opt[]) => {
2525
let offset = 1;
2626
let currentIndex = index;
2727
const opts = options;

0 commit comments

Comments
 (0)