Skip to content

Commit caf47c0

Browse files
feat(popover): fixed comments from the code review
1 parent 5d65ffd commit caf47c0

File tree

3 files changed

+79
-57
lines changed

3 files changed

+79
-57
lines changed

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

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,24 @@ import { PopoverContext } from './popover-context';
1818
import styles from './popover-trigger.css?inline';
1919

2020
export const PopoverTrigger = component$(
21-
(props: ExtendedPropsByAriaAttribute) => {
21+
(props: ExtendedPropsByAriaAttribute<'span'>) => {
2222
const ref = useSignal<HTMLElement>();
2323
const contextService = useContext(PopoverContext);
2424
useStylesScoped$(styles);
25-
const store = useStore<Partial<QwikUiAreaAttributesFunctionReturnType>>({
25+
const ariaAttributesStore = useStore<
26+
Partial<QwikUiAreaAttributesFunctionReturnType<'span'>>
27+
>({
2628
lastKey: undefined,
27-
ariaAttributes: {},
29+
ariaAttributes: undefined,
2830
});
2931
useTask$(({ track }) => {
3032
track(() => ({ ...props }));
31-
const { lastKey, ariaAttributes } = getAriaAttributes(
33+
const { lastKey, ariaAttributes } = getAriaAttributes<'span'>(
3234
props,
33-
store.lastKey
35+
ariaAttributesStore.lastKey
3436
);
35-
store.ariaAttributes = ariaAttributes;
36-
store.lastKey = lastKey;
37+
ariaAttributesStore.ariaAttributes = ariaAttributes;
38+
ariaAttributesStore.lastKey = lastKey;
3739
});
3840

3941
useVisibleTask$(() => {
@@ -46,7 +48,7 @@ export const PopoverTrigger = component$(
4648
return (
4749
<span
4850
ref={ref}
49-
{...store.ariaAttributes}
51+
{...ariaAttributesStore.ariaAttributes}
5052
role="button"
5153
class="popover-trigger"
5254
onMouseOver$={
Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,36 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
2-
import { AriaAttributes } from '@builder.io/qwik';
32
import {
43
ExtendedPropsByAriaAttribute,
4+
QwikAriaAttributeCamelCaseElement,
5+
QwikIntrinsicAriaAttributes,
6+
QwikUiAreaAttributesFunctionReturnType,
57
QwikUiAreaAttributesFunctionType,
6-
QwikUiAriaAttributesKebab,
7-
isKeyOfAriaAttributes,
8-
isKeyOfQwikUiAriaAttributes,
8+
isKeyOfQwikCamelAriaAttributes,
9+
isKeyOfQwikIntrinsicAriaAttributes,
910
} from './aria-attributes.type';
1011

11-
export function keyToKebabCase(str: string): keyof AriaAttributes {
12+
export function keyToKebabCase(
13+
str: string
14+
): keyof QwikIntrinsicAriaAttributes<string> {
1215
const newStr = str
1316
.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2')
1417
.toLowerCase();
15-
if (isKeyOfAriaAttributes(newStr)) {
18+
if (isKeyOfQwikIntrinsicAriaAttributes(newStr)) {
1619
return newStr;
1720
} else {
1821
throw new Error('The key you speficied was not an aria attribute.');
1922
}
2023
}
2124

22-
const cacheMap: Map<string, Partial<AriaAttributes>> = new Map();
25+
const cacheMap = new Map();
2326

24-
const memoize = (
25-
func: QwikUiAreaAttributesFunctionType
26-
): QwikUiAreaAttributesFunctionType => {
27+
const memoize = <K extends string>(
28+
func: QwikUiAreaAttributesFunctionType<K>
29+
): QwikUiAreaAttributesFunctionType<K> => {
2730
return (
28-
qwikUiAriaAttributes?: Partial<QwikUiAriaAttributesKebab>,
31+
qwikUiAriaAttributes?: Partial<QwikAriaAttributeCamelCaseElement<K>>,
2932
lastKey?: string
30-
): ReturnType<QwikUiAreaAttributesFunctionType> => {
33+
): ReturnType<QwikUiAreaAttributesFunctionType<K>> => {
3134
const key = JSON.stringify(qwikUiAriaAttributes);
3235
if (lastKey) {
3336
cacheMap.delete(lastKey);
@@ -42,38 +45,41 @@ const memoize = (
4245
};
4346
};
4447

45-
export const extractQwikUiAriaAttributes = <T = any>(
46-
props: ExtendedPropsByAriaAttribute<T>
48+
export const extractCamelAriaAttributes = <K extends string, T = any>(
49+
props: ExtendedPropsByAriaAttribute<K, T>
4750
) => {
51+
if (!props) {
52+
return {};
53+
}
4854
return Object.keys(props).reduce(
4955
(cur, propKey) =>
50-
isKeyOfQwikUiAriaAttributes(propKey)
56+
isKeyOfQwikCamelAriaAttributes(propKey)
5157
? { ...cur, [propKey]: props[propKey] }
5258
: cur,
5359
{}
5460
);
5561
};
5662

57-
export const getAriaAttributes = <T = any>(
58-
props: ExtendedPropsByAriaAttribute<T>,
63+
export const getAriaAttributes = <K extends string, T = any>(
64+
props: ExtendedPropsByAriaAttribute<K, T>,
5965
lastKey?: string
60-
): ReturnType<QwikUiAreaAttributesFunctionType> => {
66+
): QwikUiAreaAttributesFunctionReturnType<K> => {
6167
const process = (
62-
qwikUiAriaAttributes?: Partial<QwikUiAriaAttributesKebab>
63-
): ReturnType<QwikUiAreaAttributesFunctionType> => {
68+
qwikUiAriaAttributes?: Partial<QwikAriaAttributeCamelCaseElement<K>>
69+
): ReturnType<QwikUiAreaAttributesFunctionType<K>> => {
6470
return {
6571
lastKey: JSON.stringify(qwikUiAriaAttributes),
6672
ariaAttributes: qwikUiAriaAttributes
6773
? Object.keys(qwikUiAriaAttributes).reduce(
6874
(cur, key) =>
69-
isKeyOfQwikUiAriaAttributes(key)
75+
isKeyOfQwikCamelAriaAttributes(key)
7076
? { ...cur, [keyToKebabCase(key)]: qwikUiAriaAttributes[key] }
7177
: cur,
7278
{}
7379
)
7480
: {},
7581
};
7682
};
77-
const qwikUiAriaAttributes = extractQwikUiAriaAttributes<T>(props);
83+
const qwikUiAriaAttributes = extractCamelAriaAttributes<K, T>(props);
7884
return memoize(process)(qwikUiAriaAttributes, lastKey);
7985
};

packages/kit-headless/src/utils/aria-attributes.type.ts

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,68 @@
1-
/* eslint-disable @typescript-eslint/no-explicit-any */
2-
import { AriaAttributes } from '@builder.io/qwik';
1+
import { QwikIntrinsicElements } from '@builder.io/qwik';
32
// import { propertiesOf } from 'ts-reflection';
43

54
type ToCamelCase<S extends string> = S extends `${infer Head}-${infer Tail}`
65
? `${Head}${Capitalize<ToCamelCase<Tail>>}`
76
: S;
87

9-
type KebabToCamelKeys<T> = {
10-
[K in keyof T as ToCamelCase<string & K>]: T[K];
11-
};
8+
type StringValueKeys<T> = {
9+
[K in keyof T]: T[K] extends string ? K : never;
10+
}[keyof T];
11+
12+
export type AnyKeyOfQwikIntrinsicElement<K extends string> = StringValueKeys<
13+
QwikIntrinsicElements[K]
14+
>;
1215

13-
type FromCamelCase<S extends string> = string extends S
14-
? string
15-
: S extends `${infer T}${infer U}`
16-
? U extends Uncapitalize<U>
17-
? `${Lowercase<T>}${FromCamelCase<U>}`
18-
: `${Lowercase<T>}-${FromCamelCase<U>}`
19-
: Lowercase<S>;
16+
type AriaKeysOnlyCamel<T> = {
17+
[K in keyof T as K extends `aria-${string}` ? ToCamelCase<K> : never]: T[K];
18+
};
2019

21-
type CamelToKebabKeys<T> = {
22-
[K in keyof T as FromCamelCase<string & K>]: T[K];
20+
type AriaKeysOnlySnake<T> = {
21+
[K in keyof T as K extends `aria-${string}` ? K : never]: T[K];
2322
};
2423

25-
export type QwikUiAriaAttributesKebab = KebabToCamelKeys<AriaAttributes>;
24+
export type QwikAriaAttributeCamelCaseElement<K extends string> =
25+
K extends string ? AriaKeysOnlyCamel<QwikIntrinsicElements[K]> : undefined;
2626

27-
export type ExtendedPropsByAriaAttribute<T = undefined> = T extends object
28-
? T & QwikUiAriaAttributesKebab
27+
export type QwikIntrinsicAriaAttributes<K extends string> = K extends string
28+
? AriaKeysOnlySnake<QwikIntrinsicElements[K]>
29+
: undefined;
30+
31+
export type ExtendedPropsByAriaAttribute<
32+
K extends string,
33+
T = undefined
34+
> = T extends object
35+
? K extends undefined
36+
? T
37+
: T & QwikAriaAttributeCamelCaseElement<K extends string ? K : undefined>
2938
: T extends undefined
30-
? QwikUiAriaAttributesKebab
39+
? K extends undefined
40+
? object
41+
: QwikAriaAttributeCamelCaseElement<K extends string ? K : undefined>
3142
: never;
3243

33-
export type QwikUiAreaAttributesFunctionType = (
34-
ariaAttributes?: Partial<QwikUiAriaAttributesKebab>,
44+
export type QwikUiAreaAttributesFunctionType<K extends string> = (
45+
ariaAttributes?: Partial<QwikAriaAttributeCamelCaseElement<K>>,
3546
lastKey?: string
36-
) => { lastKey: string; ariaAttributes: Partial<AriaAttributes> };
47+
) => {
48+
lastKey: string;
49+
ariaAttributes: Partial<QwikIntrinsicAriaAttributes<K>>;
50+
};
3751

38-
export type QwikUiAreaAttributesFunctionReturnType =
39-
ReturnType<QwikUiAreaAttributesFunctionType>;
52+
export type QwikUiAreaAttributesFunctionReturnType<K extends string> =
53+
ReturnType<QwikUiAreaAttributesFunctionType<K>>;
4054

41-
export function isKeyOfQwikUiAriaAttributes(
55+
export function isKeyOfQwikCamelAriaAttributes(
4256
key: string
43-
): key is keyof QwikUiAriaAttributesKebab {
57+
): key is keyof QwikAriaAttributeCamelCaseElement<string> {
4458
// const ariaAttributeKeys = propertiesOf<QwikUiAriaAttributesKebab>();
4559
// return ariaAttributeKeys.includes(key as keyof QwikUiAriaAttributesKebab);
4660
return key.startsWith('aria') && key.indexOf('-') === -1;
4761
}
4862

49-
export function isKeyOfAriaAttributes(
63+
export function isKeyOfQwikIntrinsicAriaAttributes(
5064
key: string
51-
): key is keyof AriaAttributes {
65+
): key is keyof QwikIntrinsicAriaAttributes<string> {
5266
// const ariaAttributeKeys = propertiesOf<AriaAttributes>();
5367
// return ariaAttributeKeys.includes(key as keyof AriaAttributes);
5468
return key.startsWith('aria') && key.indexOf('-') > -1;

0 commit comments

Comments
 (0)