Skip to content

Commit

Permalink
fix: various bug fixes (#456)
Browse files Browse the repository at this point in the history
* fix(number-field): only format when enabled

* fix(number-field): don't trigger `onRawValueChange` on mount when NaN

* fix(select): correct type definition & empty value for multiselect

* fix(text-field): clear input when controlled value set to undefined

* fix(combobox): correct type definition & empty value for multiselect

* fix(skeleton): correct data-animate & data-visible attribute value

* fix(combobox): close list on outside click

* fix(navigation-menu): incorrect animation after closed
  • Loading branch information
jer3m01 authored Aug 26, 2024
1 parent 1872bcb commit 24c5ef8
Show file tree
Hide file tree
Showing 16 changed files with 58 additions and 29 deletions.
4 changes: 2 additions & 2 deletions apps/docs/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ export default defineConfig({
},
prerender: {
routes: ["/docs/core/overview/introduction"],
crawlLinks: true
}
crawlLinks: true,
},
},

extensions: ["mdx", "md"],
Expand Down
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
"files": {
"ignore": ["./tsconfig.json", "*/netlify/*"]
"ignore": ["./tsconfig.json", "*/netlify/*", "**/package.json"]
},
"vcs": {
"enabled": true,
Expand Down
3 changes: 1 addition & 2 deletions packages/core/src/combobox/combobox-base.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -596,9 +596,8 @@ export function ComboboxBase<
focusStrategy: FocusStrategy | boolean,
triggerMode?: ComboboxTriggerMode,
) => {

// If set to only open manually, ignore other triggers
if (local.triggerMode === 'manual' && triggerMode !== 'manual') {
if (local.triggerMode === "manual" && triggerMode !== "manual") {
return;
}

Expand Down
7 changes: 5 additions & 2 deletions packages/core/src/combobox/combobox-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,14 @@ export function ComboboxContent<T extends ValidComponent = "div">(
"onFocusOutside",
]);

const close = () => {
const dismiss = () => {
context.resetInputValue(
context.listState().selectionManager().selectedKeys(),
);
context.close();
setTimeout(() => {
context.close();
});
};

const onFocusOutside = (e: FocusOutsideEvent) => {
Expand Down Expand Up @@ -165,7 +168,7 @@ export function ComboboxContent<T extends ValidComponent = "div">(
local.style,
)}
onFocusOutside={onFocusOutside}
onDismiss={close}
onDismiss={dismiss}
{...context.dataset()}
{...others}
/>
Expand Down
11 changes: 11 additions & 0 deletions packages/core/src/combobox/combobox-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export interface ComboboxInputCommonProps<
ref: T | ((el: T) => void);
onInput: JSX.EventHandlerUnion<T, InputEvent>;
onKeyDown: JSX.EventHandlerUnion<T, KeyboardEvent>;
onClick: JSX.EventHandlerUnion<T, MouseEvent>;
onFocus: JSX.EventHandlerUnion<T, FocusEvent>;
onBlur: JSX.EventHandlerUnion<T, FocusEvent>;
onTouchEnd: JSX.EventHandlerUnion<T, TouchEvent>;
Expand Down Expand Up @@ -93,6 +94,7 @@ export function ComboboxInput<T extends ValidComponent = "input">(
[
"ref",
"disabled",
"onClick",
"onInput",
"onKeyDown",
"onFocus",
Expand All @@ -113,6 +115,14 @@ export function ComboboxInput<T extends ValidComponent = "input">(

const { fieldProps } = createFormControlField(formControlFieldProps);

const onClick: JSX.EventHandlerUnion<HTMLInputElement, MouseEvent> = (e) => {
callHandler(e, local.onClick);

if (context.triggerMode() === "focus" && !context.isOpen()) {
context.open(false, "focus");
}
};

const onInput: JSX.EventHandlerUnion<HTMLInputElement, InputEvent> = (e) => {
callHandler(e, local.onInput);

Expand Down Expand Up @@ -315,6 +325,7 @@ export function ComboboxInput<T extends ValidComponent = "input">(
aria-required={formControlContext.isRequired() || undefined}
aria-disabled={formControlContext.isDisabled() || undefined}
aria-readonly={formControlContext.isReadOnly() || undefined}
onClick={onClick}
onInput={onInput}
onKeyDown={onKeyDown}
onFocus={onFocus}
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/combobox/combobox-root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {

export interface ComboboxSingleSelectionOptions<T> {
/** The controlled value of the combobox. */
value?: T;
value?: T | null;

/**
* The value of the combobox when initially rendered.
Expand All @@ -24,7 +24,7 @@ export interface ComboboxSingleSelectionOptions<T> {
defaultValue?: T;

/** Event handler called when the value changes. */
onChange?: (value: T) => void;
onChange?: (value: T | null) => void;

/** Whether the combobox allow multiple selection. */
multiple?: false;
Expand Down Expand Up @@ -100,10 +100,10 @@ export function ComboboxRoot<

const onChange = (value: Option[]) => {
if (local.multiple) {
local.onChange?.(value as any);
local.onChange?.((value ?? []) as any);
} else {
// use `null` as "no value" because `undefined` mean the component is "uncontrolled".
local.onChange?.((value[0] ?? null) as any);
local.onChange?.((value[0] ?? null) as any); // TODO: maybe return undefined? breaking change!
}
};

Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/menu/menu-content-base.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,8 @@ export function MenuContentBase<T extends ValidComponent = "div">(

createEffect(() => onCleanup(context.registerContentId(local.id!)));

onCleanup(() => context.setContentRef(undefined));

const commonAttributes: Omit<MenuContentBaseRenderProps, keyof MenuDataSet> =
{
ref: mergeRefs((el) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/menu/menu-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export interface MenuContextValue {
triggerId: Accessor<string | undefined>;
contentId: Accessor<string | undefined>;
setTriggerRef: (el: HTMLElement) => void;
setContentRef: (el: HTMLElement) => void;
setContentRef: (el: HTMLElement | undefined) => void;
open: (focusStrategy: FocusStrategy | boolean) => void;
close: (recursively?: boolean) => void;
toggle: (focusStrategy: FocusStrategy | boolean) => void;
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/navigation-menu/navigation-menu-root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,12 @@ export function NavigationMenuRoot<T extends ValidComponent = "ul">(
element: () => viewportRef() ?? null,
});

createEffect(() => {
if (!viewportPresent()) {
context.setPreviousMenu(undefined);
}
});

const context: NavigationMenuContextValue = {
dataset,
delayDuration: () => local.delayDuration!,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,10 @@ export function NavigationMenuTrigger<T extends ValidComponent = "button">(
if (context.dataset()["data-expanded"] === "") return;

timeoutId = window.setTimeout(() => {
context.setAutoFocusMenu(true);
menuContext?.triggerRef()?.focus();
setTimeout(() => {
context.setAutoFocusMenu(true);
});
}, context.delayDuration());
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,12 @@ export function NavigationMenuViewport<T extends ValidComponent = "li">(
);

const height = createMemo((prev) => {
if (ref() === undefined || !context.viewportPresent()) return undefined;
if (size.height() === 0) return prev;
return size.height();
});
const width = createMemo((prev) => {
if (ref() === undefined || !context.viewportPresent()) return undefined;
if (size.width() === 0) return prev;
return size.width();
});
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/number-field/number-field-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ export function NumberFieldInput<T extends ValidComponent = "input">(
>
as={local.as || "input"}
value={
Number.isNaN(context.rawValue())
Number.isNaN(context.rawValue()) || context.value() === undefined
? ""
: context.formatNumber(context.rawValue())
}
Expand Down
11 changes: 6 additions & 5 deletions packages/core/src/number-field/number-field-root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ export function NumberFieldRoot<T extends ValidComponent = "div">(
return new NumberFormatter(locale(), local.formatOptions);
});

const formatNumber = (number: number) =>
local.format ? numberFormatter().format(number) : number.toString();

const parseRawValue = (value: string | number | undefined) =>
local.format && typeof value !== "number"
? numberParser().parse(value ?? "")
Expand All @@ -203,14 +206,12 @@ export function NumberFieldRoot<T extends ValidComponent = "div">(
value: () => local.value,
defaultValue: () => local.defaultValue ?? local.rawValue,
onChange: (value) => {
local.onChange?.(
typeof value === "number" ? numberFormatter().format(value) : value,
);
local.onChange?.(typeof value === "number" ? formatNumber(value) : value);
local.onRawValueChange?.(parseRawValue(value));
},
});

local.onRawValueChange?.(parseRawValue(value()));
if (value() !== undefined) local.onRawValueChange?.(parseRawValue(value()));

function isAllowedInput(char: string): boolean {
if (local.allowedInput !== undefined) return local.allowedInput.test(char);
Expand Down Expand Up @@ -267,7 +268,7 @@ export function NumberFieldRoot<T extends ValidComponent = "div">(
setValue,
rawValue: () => parseRawValue(value()),
generateId: createGenerateId(() => access(formControlProps.id)!),
formatNumber: (number: number) => numberFormatter().format(number),
formatNumber,
format: () => {
if (!local.format) return;
let rawValue = context.rawValue();
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/select/select-root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {

export interface SelectSingleSelectionOptions<T> {
/** The controlled value of the select. */
value?: T;
value?: T | null;

/**
* The value of the select when initially rendered.
Expand All @@ -24,7 +24,7 @@ export interface SelectSingleSelectionOptions<T> {
defaultValue?: T;

/** Event handler called when the value changes. */
onChange?: (value: T) => void;
onChange?: (value: T | null) => void;

/** Whether the select allow multiple selection. */
multiple?: false;
Expand Down Expand Up @@ -101,10 +101,10 @@ export function SelectRoot<

const onChange = (value: Option[]) => {
if (local.multiple) {
local.onChange?.(value as any);
local.onChange?.((value ?? []) as any);
} else {
// use `null` as "no value" because `undefined` mean the component is "uncontrolled".
local.onChange?.((value[0] ?? null) as any);
local.onChange?.((value[0] ?? null) as any); // TODO: maybe return undefined? breaking change!
}
};

Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/skeleton/skeleton-root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ export interface SkeletonRootCommonProps<T extends HTMLElement = HTMLElement> {

export interface SkeletonRootRenderProps extends SkeletonRootCommonProps {
role: "group";
"data-animate": boolean;
"data-visible": boolean;
"data-animate": boolean | undefined;
"data-visible": boolean | undefined;
}

export type SkeletonRootProps<
Expand Down Expand Up @@ -82,8 +82,8 @@ export function Skeleton<T extends ValidComponent = "div">(
<Polymorphic<SkeletonRootRenderProps>
as="div"
role="group"
data-animate={local.animate}
data-visible={local.visible}
data-animate={local.animate || undefined}
data-visible={local.visible || undefined}
style={combineStyle(
{
"border-radius": local.circle
Expand Down
7 changes: 5 additions & 2 deletions packages/core/src/text-field/text-field-root.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
OverrideComponentProps,
type ValidationState,
access,
createGenerateId,
Expand Down Expand Up @@ -108,8 +107,12 @@ export function TextFieldRoot<T extends ValidComponent = "div">(
FORM_CONTROL_PROP_NAMES,
);

// Disable reactivity to only track controllability on first run
// Our current implementation breaks with undefined (stops tracking controlled value)
const initialValue = local.value;

const [value, setValue] = createControllableSignal({
value: () => local.value,
value: () => (initialValue === undefined ? undefined : local.value ?? ""),
defaultValue: () => local.defaultValue,
onChange: (value) => local.onChange?.(value),
});
Expand Down

0 comments on commit 24c5ef8

Please sign in to comment.