Skip to content

Commit

Permalink
Showing 2 changed files with 67 additions and 26 deletions.
46 changes: 33 additions & 13 deletions packages/core/src/checkbox/checkbox-input.tsx
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@ import {
OverrideComponentProps,
visuallyHiddenStyles,
} from "@kobalte/utils";
import { createEffect, JSX, on, splitProps } from "solid-js";
import { createEffect, createSignal, JSX, on, splitProps } from "solid-js";

import {
createFormControlField,
@@ -54,23 +54,28 @@ export function CheckboxInput(props: CheckboxInputProps) {

const { fieldProps } = createFormControlField(formControlFieldProps);

const [isInternalChangeEvent, setIsInternalChangeEvent] = createSignal(false);

const onChange: JSX.ChangeEventHandlerUnion<HTMLInputElement, Event> = e => {
callHandler(e, local.onChange);

e.stopPropagation();

const target = e.target as HTMLInputElement;

context.setIsChecked(target.checked);

// Unlike in React, inputs `checked` state can be out of sync with our toggle state.
// for example a readonly `<input type="checkbox" />` is always "checkable".
//
// Also, even if an input is controlled (ex: `<input type="checkbox" checked={isChecked} />`,
// clicking on the input will change its internal `checked` state.
//
// To prevent this, we need to force the input `checked` state to be in sync with the toggle state.
target.checked = context.checked();
if (!isInternalChangeEvent()) {
const target = e.target as HTMLInputElement;

context.setIsChecked(target.checked);

// Unlike in React, inputs `checked` state can be out of sync with our toggle state.
// for example a readonly `<input type="checkbox" />` is always "checkable".
//
// Also, even if an input is controlled (ex: `<input type="checkbox" checked={isChecked} />`,
// clicking on the input will change its internal `checked` state.
//
// To prevent this, we need to force the input `checked` state to be in sync with the toggle state.
target.checked = context.checked();
}
setIsInternalChangeEvent(false);
};

const onFocus: JSX.FocusEventHandlerUnion<any, FocusEvent> = e => {
@@ -83,6 +88,21 @@ export function CheckboxInput(props: CheckboxInputProps) {
context.setIsFocused(false);
};

createEffect(
on(
[() => context.checked(), () => context.value()],
() => {
setIsInternalChangeEvent(true);

ref?.dispatchEvent(new Event("input", { bubbles: true, cancelable: true }));
ref?.dispatchEvent(new Event("change", { bubbles: true, cancelable: true }));
},
{
defer: true,
},
),
);

// indeterminate is a property, but it can only be set via javascript
// https://css-tricks.com/indeterminate-checkboxes/
// Unlike in React, inputs `indeterminate` state can be out of sync with our.
47 changes: 34 additions & 13 deletions packages/core/src/radio-group/radio-group-item-input.tsx
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ import {
OverrideComponentProps,
visuallyHiddenStyles,
} from "@kobalte/utils";
import { createEffect, JSX, onCleanup, splitProps } from "solid-js";
import { createEffect, createSignal, JSX, on, onCleanup, splitProps } from "solid-js";

import { useFormControlContext } from "../form-control";
import { useRadioGroupContext } from "./radio-group-context";
@@ -73,23 +73,28 @@ export function RadioGroupItemInput(props: RadioGroupItemInputProps) {
);
};

const [isInternalChangeEvent, setIsInternalChangeEvent] = createSignal(false);

const onChange: JSX.ChangeEventHandlerUnion<HTMLInputElement, Event> = e => {
callHandler(e, local.onChange);

e.stopPropagation();

radioGroupContext.setSelectedValue(radioContext.value());

const target = e.target as HTMLInputElement;

// Unlike in React, inputs `checked` state can be out of sync with our state.
// for example a readonly `<input type="radio" />` is always "checkable".
//
// Also, even if an input is controlled (ex: `<input type="radio" checked={isChecked} />`,
// clicking on the input will change its internal `checked` state.
//
// To prevent this, we need to force the input `checked` state to be in sync with our state.
target.checked = radioContext.isSelected();
if (!isInternalChangeEvent()) {
radioGroupContext.setSelectedValue(radioContext.value());

const target = e.target as HTMLInputElement;

// Unlike in React, inputs `checked` state can be out of sync with our state.
// for example a readonly `<input type="radio" />` is always "checkable".
//
// Also, even if an input is controlled (ex: `<input type="radio" checked={isChecked} />`,
// clicking on the input will change its internal `checked` state.
//
// To prevent this, we need to force the input `checked` state to be in sync with our state.
target.checked = radioContext.isSelected();
}
setIsInternalChangeEvent(false);
};

const onFocus: JSX.FocusEventHandlerUnion<any, FocusEvent> = e => {
@@ -102,6 +107,22 @@ export function RadioGroupItemInput(props: RadioGroupItemInputProps) {
radioContext.setIsFocused(false);
};

createEffect(
on(
[() => radioContext.isSelected(), () => radioContext.value()],
c => {
if (!c[0] && c[1] === radioContext.value()) return;
setIsInternalChangeEvent(true);

const ref = radioContext.inputRef();
ref?.dispatchEvent(new Event("input", { bubbles: true, cancelable: true }));
ref?.dispatchEvent(new Event("change", { bubbles: true, cancelable: true }));
},
{
defer: true,
},
),
);
createEffect(() => onCleanup(radioContext.registerInput(others.id!)));

return (

0 comments on commit 2b37c0c

Please sign in to comment.