Skip to content

Commit

Permalink
finish radio group
Browse files Browse the repository at this point in the history
  • Loading branch information
huntabyte committed Apr 18, 2024
1 parent ab7ef3b commit 5038242
Show file tree
Hide file tree
Showing 12 changed files with 168 additions and 112 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"prettier": "^3.2.5",
"prettier-plugin-svelte": "^3.2.2",
"prettier-plugin-tailwindcss": "0.5.13",
"svelte": "5.0.0-next.104",
"svelte": "5.0.0-next.107",
"svelte-eslint-parser": "^0.34.1",
"wrangler": "^3.44.0"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/bits-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"jsdom": "^24.0.0",
"publint": "^0.2.7",
"resize-observer-polyfill": "^1.5.1",
"svelte": "5.0.0-next.104",
"svelte": "5.0.0-next.107",
"svelte-check": "^3.6.9",
"tslib": "^2.6.2",
"typescript": "^5.3.3",
Expand Down
7 changes: 3 additions & 4 deletions packages/bits-ui/src/lib/bits/accordion/accordion.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
getDataDisabled,
getDataOpenClosed,
kbd,
readonlyBox,
styleToString,
verifyContextDeps,
} from "$lib/internal/index.js";
Expand Down Expand Up @@ -59,7 +58,7 @@ class AccordionBaseState {
* SINGLE
*/

type AccordionSingleStateProps = AccordionBaseStateProps & BoxedValues<{ value: string; }>;
type AccordionSingleStateProps = AccordionBaseStateProps & BoxedValues<{ value: string }>;

export class AccordionSingleState extends AccordionBaseState {
#value: Box<string>;
Expand All @@ -83,7 +82,7 @@ export class AccordionSingleState extends AccordionBaseState {
* MULTIPLE
*/

type AccordionMultiStateProps = AccordionBaseStateProps & BoxedValues<{ value: string[]; }>;
type AccordionMultiStateProps = AccordionBaseStateProps & BoxedValues<{ value: string[] }>;

export class AccordionMultiState extends AccordionBaseState {
#value: Box<string[]>;
Expand Down Expand Up @@ -261,7 +260,7 @@ class AccordionContentState {
item: AccordionItemState;
node: Box<HTMLElement | null>;
#id: ReadonlyBox<string>;
#originalStyles: { transitionDuration: string; animationName: string; } | undefined = undefined;
#originalStyles: { transitionDuration: string; animationName: string } | undefined = undefined;
#isMountAnimationPrevented = false;
#width = $state(0);
#height = $state(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ type CollapsibleContentStateProps = ReadonlyBoxedValues<{

class CollapsibleContentState {
root: CollapsibleRootState;
#originalStyles: { transitionDuration: string; animationName: string; } | undefined;
#originalStyles: { transitionDuration: string; animationName: string } | undefined;
#styleProp: ReadonlyBox<StyleProperties>;
node: Box<HTMLElement | null>;
node = boxedState<HTMLElement | null>(null);
#isMountAnimationPrevented = $state(false);
#width = $state(0);
#height = $state(0);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
<script lang="ts">
import { getRadioGroupInputState } from "../radio-group.svelte.js";
const inputState = getRadioGroupInputState();
</script>

{#if inputState.shouldRender}
<input {...inputState.props} />
{/if}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
id: idProp = generateId(),
asChild,
children,
indicator,
child,
value: valueProp,
disabled: disabledProp = false,
Expand Down Expand Up @@ -38,6 +39,10 @@
{@render child?.({ props: mergedProps })}
{:else}
<button bind:this={el} {...mergedProps}>
{@render children?.()}
{#if indicator}
{@render indicator({ checked: item.checked })}
{:else}
{@render children?.()}
{/if}
</button>
{/if}
53 changes: 50 additions & 3 deletions packages/bits-ui/src/lib/bits/radio-group/radio-group.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { getDirectionalKeys, kbd } from "$lib/internal/kbd.js";
import { getElemDirection } from "$lib/internal/locale.js";
import type { Orientation } from "$lib/shared/index.js";
import { verifyContextDeps } from "$lib/internal/context.js";
import { srOnlyStyles, styleToString } from "$lib/internal/style.js";

type RadioGroupRootStateProps = ReadonlyBoxedValues<{
id: string;
Expand All @@ -21,7 +22,7 @@ type RadioGroupRootStateProps = ReadonlyBoxedValues<{
orientation: Orientation;
name: string | undefined;
}> &
BoxedValues<{ value: string; }>;
BoxedValues<{ value: string }>;

class RadioGroupRootState {
id: RadioGroupRootStateProps["id"];
Expand All @@ -32,6 +33,7 @@ class RadioGroupRootState {
orientation: RadioGroupRootStateProps["orientation"];
name: RadioGroupRootStateProps["name"];
value: RadioGroupRootStateProps["value"];
activeTabIndexNode = boxedState<HTMLElement | null>(null);

constructor(props: RadioGroupRootStateProps) {
this.id = props.id;
Expand All @@ -54,6 +56,7 @@ class RadioGroupRootState {

getRadioItemNodes() {
if (!this.node.value) return [];

return Array.from(this.node.value.querySelectorAll("[data-bits-radio-group-item]")).filter(
(el): el is HTMLElement => el instanceof HTMLElement && !el.dataset.disabled
);
Expand All @@ -63,8 +66,13 @@ class RadioGroupRootState {
return new RadioGroupItemState(props, this);
}

createInput() {
return new RadioGroupInputState(this);
}

get props() {
return {
id: this.id.value,
role: "radiogroup",
"aria-required": getAriaRequired(this.required.value),
"data-disabled": getDataDisabled(this.disabled.value),
Expand All @@ -89,11 +97,12 @@ type RadioGroupItemStateProps = ReadonlyBoxedValues<{
class RadioGroupItemState {
#id: RadioGroupItemStateProps["id"];
#node: Box<HTMLElement | null>;
#root: RadioGroupRootState;
#root = undefined as unknown as RadioGroupRootState;
#disabled: RadioGroupItemStateProps["disabled"];
#value: RadioGroupItemStateProps["value"];
#value = undefined as unknown as RadioGroupItemStateProps["value"];
#composedClick: EventCallback<MouseEvent>;
#composedKeydown: EventCallback<KeyboardEvent>;
checked = $derived(this.#root.value.value === this.#value.value);

constructor(props: RadioGroupItemStateProps, root: RadioGroupRootState) {
this.#disabled = props.disabled;
Expand All @@ -104,6 +113,12 @@ class RadioGroupItemState {
this.#composedKeydown = composeHandlers(props.onkeydown, this.#onkeydown);

this.#node = useNodeById(this.#id);

$effect(() => {
if (!this.#node.value) return;
if (!this.#root.isChecked(this.#value.value)) return;
this.#root.activeTabIndexNode.value = this.#node.value;
});
}

#onclick = () => {
Expand Down Expand Up @@ -152,6 +167,7 @@ class RadioGroupItemState {

get props() {
return {
id: this.#id.value,
disabled: this.#isDisabled ? true : undefined,
"data-value": this.#value.value,
"data-orientation": this.#root.orientation.value,
Expand All @@ -161,13 +177,40 @@ class RadioGroupItemState {
"data-bits-radio-group-item": "",
type: "button",
role: "radio",
tabIndex: this.#root.activeTabIndexNode.value === this.#node.value ? 0 : -1,
//
onclick: this.#composedClick,
onkeydown: this.#composedKeydown,
} as const;
}
}

//
// INPUT
//

class RadioGroupInputState {
#root = undefined as unknown as RadioGroupRootState;
shouldRender = $derived(this.#root.name.value !== undefined);

constructor(root: RadioGroupRootState) {
this.#root = root;
}

get props() {
return {
name: this.#root.name.value,
value: this.#root.value.value,
required: this.#root.required.value,
disabled: this.#root.disabled.value,
"aria-hidden": "true",
hidden: true,
style: styleToString(srOnlyStyles),
tabIndex: -1,
} as const;
}
}

//
// CONTEXT METHODS
//
Expand All @@ -186,3 +229,7 @@ export function getRadioGroupRootState() {
export function setRadioGroupItemState(props: RadioGroupItemStateProps) {
return getRadioGroupRootState().createItem(props);
}

export function getRadioGroupInputState() {
return getRadioGroupRootState().createInput();
}
4 changes: 2 additions & 2 deletions packages/bits-ui/src/lib/bits/radio-group/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ export type RadioGroupItemPropsWithoutHTML = WithAsChild<{
*/
disabled?: boolean;

onclick: EventCallback<MouseEvent>;
onclick?: EventCallback<MouseEvent>;

onkeydown: EventCallback<KeyboardEvent>;
onkeydown?: EventCallback<KeyboardEvent>;

/**
* A snippet to render the radio item's indicator.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script lang="ts">
import { getCtx } from "../ctx.js";
import type { InputProps } from "../index.js";
import { getSrOnlyStyles } from "$lib/internal/style.js";
import { srOnlyStyles, styleToString } from "$lib/internal/style.js";
type $$Props = InputProps;
export let el: $$Props["el"] = undefined;
Expand All @@ -22,7 +22,7 @@
const attrs = {
...getAttrs("input"),
style: getSrOnlyStyles(),
style: styleToString(srOnlyStyles),
};
$: inputValue = getValue($value);
Expand Down
24 changes: 11 additions & 13 deletions packages/bits-ui/src/lib/internal/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,14 @@ export function styleToString(style: StyleProperties = {}): string {
return styleString;
}

export function getSrOnlyStyles() {
return styleToString({
position: "absolute",
width: "1px",
height: "1px",
padding: "0",
margin: "-1px",
overflow: "hidden",
clip: "rect(0, 0, 0, 0)",
"white-space": "nowrap",
"border-width": "0",
});
}
export const srOnlyStyles: StyleProperties = {
position: "absolute",
width: "1px",
height: "1px",
padding: "0",
margin: "-1px",
overflow: "hidden",
clip: "rect(0, 0, 0, 0)",
"white-space": "nowrap",
"border-width": "0",
};
Loading

0 comments on commit 5038242

Please sign in to comment.