Skip to content

Commit

Permalink
Merge pull request #26 from szhsin/feat/auto-height
Browse files Browse the repository at this point in the history
feat: auto height
  • Loading branch information
szhsin authored Apr 5, 2024
2 parents e20c9b6 + d03ef73 commit ed2835c
Show file tree
Hide file tree
Showing 13 changed files with 144 additions and 9 deletions.
34 changes: 34 additions & 0 deletions dist/cjs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,39 @@ const useAutocomplete = ({
};
};

const useLayoutEffect = typeof window !== 'undefined' && window.document && window.document.createElement ? react.useLayoutEffect : react.useEffect;
const findOverflowAncestor = element => {
while (element) {
element = element.parentElement;
if (!element || element === document.body) return;
const {
overflow,
overflowX,
overflowY
} = getComputedStyle(element);
if (/auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX)) return element;
}
};
const useAutoHeight = ({
anchorRef,
show,
margin = 0
}) => {
const [height, setHeight] = react.useState();
const computeHeight = react.useCallback(() => {
const anchor = anchorRef.current;
if (!anchor) return;
const overflowAncestor = findOverflowAncestor(anchor);
const bottomBoundary = overflowAncestor ? overflowAncestor.getBoundingClientRect().bottom : window.innerHeight;
const newHeight = bottomBoundary - anchor.getBoundingClientRect().bottom - margin;
setHeight(Math.max(newHeight, 0));
}, [anchorRef, margin]);
useLayoutEffect(() => {
show && computeHeight();
}, [show, computeHeight]);
return [height, computeHeight];
};

const scrollIntoView = element => element == null ? void 0 : element.scrollIntoView({
block: 'nearest'
});
Expand Down Expand Up @@ -290,4 +323,5 @@ exports.autocomplete = autocomplete;
exports.groupedTraversal = groupedTraversal;
exports.linearTraversal = linearTraversal;
exports.supercomplete = supercomplete;
exports.useAutoHeight = useAutoHeight;
exports.useAutocomplete = useAutocomplete;
36 changes: 36 additions & 0 deletions dist/esm/hooks/useAutoHeight.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useState, useCallback, useLayoutEffect as useLayoutEffect$1, useEffect } from 'react';

const useLayoutEffect = typeof window !== 'undefined' && window.document && window.document.createElement ? useLayoutEffect$1 : useEffect;
const findOverflowAncestor = element => {
while (element) {
element = element.parentElement;
if (!element || element === document.body) return;
const {
overflow,
overflowX,
overflowY
} = getComputedStyle(element);
if (/auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX)) return element;
}
};
const useAutoHeight = ({
anchorRef,
show,
margin = 0
}) => {
const [height, setHeight] = useState();
const computeHeight = useCallback(() => {
const anchor = anchorRef.current;
if (!anchor) return;
const overflowAncestor = findOverflowAncestor(anchor);
const bottomBoundary = overflowAncestor ? overflowAncestor.getBoundingClientRect().bottom : window.innerHeight;
const newHeight = bottomBoundary - anchor.getBoundingClientRect().bottom - margin;
setHeight(Math.max(newHeight, 0));
}, [anchorRef, margin]);
useLayoutEffect(() => {
show && computeHeight();
}, [show, computeHeight]);
return [height, computeHeight];
};

export { useAutoHeight };
1 change: 1 addition & 0 deletions dist/esm/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { useAutocomplete } from './hooks/useAutocomplete.js';
export { useAutoHeight } from './hooks/useAutoHeight.js';
export { autocomplete } from './features/autocomplete.js';
export { supercomplete } from './features/supercomplete.js';
export { linearTraversal } from './traversals/linearTraversal.js';
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@szhsin/react-autocomplete",
"version": "0.8.4",
"version": "0.8.5",
"description": "",
"author": "Zheng Song",
"license": "MIT",
Expand Down
4 changes: 4 additions & 0 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import type { HTMLAttributes, InputHTMLAttributes } from 'react';

/// types

export type PropsWithObjectRef<T> = T extends HTMLAttributes<infer E>
? T & { ref: React.RefObject<E> }
: never;

export interface GetProps<T> {
getInputProps: () => InputHTMLAttributes<HTMLInputElement>;
getListProps: () => HTMLAttributes<HTMLElement>;
Expand Down
47 changes: 47 additions & 0 deletions src/hooks/useAutoHeight.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { useState, useCallback, useEffect, useLayoutEffect as _useLayoutEffect } from 'react';

const useLayoutEffect =
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
typeof window !== 'undefined' && window.document && window.document.createElement!
? _useLayoutEffect
: useEffect;

const findOverflowAncestor = (element: Element | null) => {
while (element) {
element = element.parentElement;
if (!element || element === document.body) return;
const { overflow, overflowX, overflowY } = getComputedStyle(element);
if (/auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX)) return element;
}
};

const useAutoHeight = ({
anchorRef,
show,
margin = 0
}: {
anchorRef: React.RefObject<Element>;
show?: boolean;
margin?: number;
}) => {
const [height, setHeight] = useState<React.CSSProperties['maxHeight']>();

const computeHeight = useCallback(() => {
const anchor = anchorRef.current;
if (!anchor) return;
const overflowAncestor = findOverflowAncestor(anchor);
const bottomBoundary = overflowAncestor
? overflowAncestor.getBoundingClientRect().bottom
: window.innerHeight;
const newHeight = bottomBoundary - anchor.getBoundingClientRect().bottom - margin;
setHeight(Math.max(newHeight, 0));
}, [anchorRef, margin]);

useLayoutEffect(() => {
show && computeHeight();
}, [show, computeHeight]);

return [height, computeHeight] as const;
};

export { useAutoHeight };
6 changes: 4 additions & 2 deletions src/hooks/useAutocomplete.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { useState, useRef, useCallback } from 'react';
import type { InputHTMLAttributes } from 'react';
import type {
GetProps,
AutocompleteProps,
AutocompleteState,
Instance,
Contextual
Contextual,
PropsWithObjectRef
} from '../common';
import { mergeEvents } from '../utils/mergeEvents';

Expand Down Expand Up @@ -53,7 +55,7 @@ const useAutocomplete = <T, FeatureActions>({
...restFeature
} = useFeature({ ...contextual, ...useTraversal(contextual) });

const getInputProps: GetProps<T>['getInputProps'] = () => {
const getInputProps: () => PropsWithObjectRef<InputHTMLAttributes<HTMLInputElement>> = () => {
const { onBlur, ...rest } = _getInputProps();
return {
...rest,
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { useAutocomplete } from './hooks/useAutocomplete';
export { useAutoHeight } from './hooks/useAutoHeight';
export { autocomplete } from './features/autocomplete';
export { supercomplete } from './features/supercomplete';
export { linearTraversal } from './traversals/linearTraversal';
Expand Down
3 changes: 3 additions & 0 deletions types/common.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import type { HTMLAttributes, InputHTMLAttributes } from 'react';
export type PropsWithObjectRef<T> = T extends HTMLAttributes<infer E> ? T & {
ref: React.RefObject<E>;
} : never;
export interface GetProps<T> {
getInputProps: () => InputHTMLAttributes<HTMLInputElement>;
getListProps: () => HTMLAttributes<HTMLElement>;
Expand Down
6 changes: 6 additions & 0 deletions types/hooks/useAutoHeight.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
declare const useAutoHeight: ({ anchorRef, show, margin }: {
anchorRef: React.RefObject<Element>;
show?: boolean | undefined;
margin?: number | undefined;
}) => readonly [import("csstype").Property.MaxHeight<string | number> | undefined, () => void];
export { useAutoHeight };
8 changes: 4 additions & 4 deletions types/hooks/useAutocomplete.d.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/// <reference types="react" />
import type { GetProps, AutocompleteProps } from '../common';
import type { InputHTMLAttributes } from 'react';
import type { GetProps, AutocompleteProps, PropsWithObjectRef } from '../common';
declare const useAutocomplete: <T, FeatureActions>({ onChange, isItemDisabled, feature: useFeature, traversal: useTraversal, getItemValue: _getItemValue }: AutocompleteProps<T, FeatureActions>) => {
setInputValue: (value: string) => void;
focusItem: T | null | undefined;
setFocusItem: (item?: T | null | undefined) => void;
open: boolean;
setOpen: (value: boolean) => void;
getInputProps: () => import("react").InputHTMLAttributes<HTMLInputElement>;
getInputProps: () => PropsWithObjectRef<InputHTMLAttributes<HTMLInputElement>>;
getListProps: () => import("react").HTMLAttributes<HTMLElement>;
} & Omit<GetProps<T> & FeatureActions, "getInputProps" | "getListProps">;
} & Omit<GetProps<T> & FeatureActions, "getListProps" | "getInputProps">;
export { useAutocomplete };
1 change: 1 addition & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { useAutocomplete } from './hooks/useAutocomplete';
export { useAutoHeight } from './hooks/useAutoHeight';
export { autocomplete } from './features/autocomplete';
export { supercomplete } from './features/supercomplete';
export { linearTraversal } from './traversals/linearTraversal';
Expand Down

0 comments on commit ed2835c

Please sign in to comment.