-
-
Notifications
You must be signed in to change notification settings - Fork 93
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
266 additions
and
158 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 4 additions & 21 deletions
25
packages/bits-ui/src/lib/bits/switch/components/switch-input.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,8 @@ | ||
<script lang="ts"> | ||
import { melt } from "@melt-ui/svelte"; | ||
import { getCtx } from "../ctx.js"; | ||
import type { InputProps } from "../index.js"; | ||
import { getSwitchInputState } from "../switch.svelte.js"; | ||
import { srOnlyStyles, styleToString } from "$lib/internal/style.js"; | ||
type $$Props = InputProps; | ||
export let el: $$Props["el"] = undefined; | ||
const { | ||
elements: { input }, | ||
options: { value, name, disabled, required }, | ||
} = getCtx(); | ||
$: inputValue = $value === undefined || $value === "" ? "on" : $value; | ||
const inputState = getSwitchInputState(); | ||
</script> | ||
|
||
<input | ||
bind:this={el} | ||
use:melt={$input} | ||
name={$name} | ||
disabled={$disabled} | ||
required={$required} | ||
value={inputValue} | ||
{...$$restProps} | ||
/> | ||
<input {...inputState.props} style={styleToString(srOnlyStyles)} /> |
27 changes: 11 additions & 16 deletions
27
packages/bits-ui/src/lib/bits/switch/components/switch-thumb.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,21 @@ | ||
<script lang="ts"> | ||
import { getCtx } from "../ctx.js"; | ||
import type { ThumbProps } from "../index.js"; | ||
import { getSwitchThumbState } from "../switch.svelte.js"; | ||
import { styleToString } from "$lib/internal/style.js"; | ||
type $$Props = ThumbProps; | ||
let { asChild, child, el = $bindable(), style = {}, ...restProps }: ThumbProps = $props(); | ||
export let asChild: $$Props["asChild"] = false; | ||
export let el: $$Props["el"] = undefined; | ||
const thumbState = getSwitchThumbState(); | ||
const { | ||
states: { checked }, | ||
getAttrs, | ||
} = getCtx(); | ||
$: attrs = { | ||
...getAttrs("thumb"), | ||
"data-state": $checked ? "checked" : "unchecked", | ||
"data-checked": $checked ? "" : undefined, | ||
}; | ||
const mergedProps = $derived({ | ||
...restProps, | ||
...thumbState.props, | ||
style: styleToString(style), | ||
}); | ||
</script> | ||
|
||
{#if asChild} | ||
<slot {attrs} checked={$checked} /> | ||
{@render child?.({ props: mergedProps, checked: thumbState.root.checked.value })} | ||
{:else} | ||
<span bind:this={el} {...$$restProps} {...attrs} /> | ||
<span bind:this={el} {...mergedProps} /> | ||
{/if} |
106 changes: 49 additions & 57 deletions
106
packages/bits-ui/src/lib/bits/switch/components/switch.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,70 +1,62 @@ | ||
<script lang="ts"> | ||
import { melt } from "@melt-ui/svelte"; | ||
import { setCtx } from "../ctx.js"; | ||
import type { Events, Props } from "../index.js"; | ||
import SwitchInput from "./switch-input.svelte"; | ||
import { createDispatcher } from "$lib/internal/events.js"; | ||
import type { RootProps } from "../index.js"; | ||
import { setSwitchRootState } from "../switch.svelte.js"; | ||
import { box, readonlyBox } from "$lib/internal/box.svelte.js"; | ||
import { styleToString } from "$lib/internal/style.js"; | ||
type $$Props = Props; | ||
type $$Events = Events; | ||
export let checked: $$Props["checked"] = undefined; | ||
export let onCheckedChange: $$Props["onCheckedChange"] = undefined; | ||
export let disabled: $$Props["disabled"] = undefined; | ||
export let name: $$Props["name"] = undefined; | ||
export let value: $$Props["value"] = undefined; | ||
export let includeInput: $$Props["includeInput"] = true; | ||
export let required: $$Props["required"] = undefined; | ||
export let asChild: $$Props["asChild"] = false; | ||
export let inputAttrs: $$Props["inputAttrs"] = undefined; | ||
export let el: $$Props["el"] = undefined; | ||
let { | ||
child, | ||
asChild, | ||
children, | ||
el = $bindable(), | ||
disabled: disabledProp = false, | ||
required: requiredProp = false, | ||
checked: checkedProp = false, | ||
value: valueProp = "", | ||
name: nameProp = undefined, | ||
onclick: onclickProp = () => {}, | ||
onkeydown: onkeydownProp = () => {}, | ||
onCheckedChange, | ||
style = {}, | ||
...restProps | ||
}: RootProps = $props(); | ||
const { | ||
elements: { root }, | ||
states: { checked: localChecked }, | ||
updateOption, | ||
getAttrs, | ||
} = setCtx({ | ||
const checked = box( | ||
() => checkedProp, | ||
(v) => { | ||
checkedProp = v; | ||
onCheckedChange?.(v); | ||
} | ||
); | ||
const disabled = readonlyBox(() => disabledProp); | ||
const required = readonlyBox(() => requiredProp); | ||
const value = readonlyBox(() => valueProp); | ||
const name = readonlyBox(() => nameProp); | ||
const onclick = readonlyBox(() => onclickProp); | ||
const onkeydown = readonlyBox(() => onkeydownProp); | ||
const rootState = setSwitchRootState({ | ||
checked, | ||
disabled, | ||
name, | ||
value, | ||
required, | ||
defaultChecked: checked, | ||
onCheckedChange: ({ next }) => { | ||
if (checked !== next) { | ||
onCheckedChange?.(next); | ||
checked = next; | ||
} | ||
return next; | ||
}, | ||
value, | ||
name, | ||
onclick, | ||
onkeydown, | ||
}); | ||
const dispatch = createDispatcher(); | ||
$: checked !== undefined && localChecked.set(checked); | ||
$: updateOption("disabled", disabled); | ||
$: updateOption("name", name); | ||
$: updateOption("value", value); | ||
$: updateOption("required", required); | ||
$: builder = $root; | ||
$: attrs = { ...getAttrs("root"), "data-checked": checked ? "" : undefined }; | ||
$: Object.assign(builder, attrs); | ||
const mergedProps = $derived({ | ||
...restProps, | ||
...rootState.props, | ||
style: styleToString(style), | ||
}); | ||
</script> | ||
|
||
{#if asChild} | ||
<slot {builder} /> | ||
{@render child?.({ props: mergedProps, checked: rootState.checked.value })} | ||
{:else} | ||
<button | ||
bind:this={el} | ||
use:melt={builder} | ||
type="button" | ||
{...$$restProps} | ||
on:m-click={dispatch} | ||
on:m-keydown={dispatch} | ||
> | ||
<slot {builder} /> | ||
<button bind:this={el} {...mergedProps}> | ||
{@render children?.()} | ||
</button> | ||
{/if} | ||
{#if includeInput} | ||
<SwitchInput {...inputAttrs} /> | ||
{/if} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,4 @@ | ||
export { default as Root } from "./components/switch.svelte"; | ||
export { default as Thumb } from "./components/switch-thumb.svelte"; | ||
export { default as Input } from "./components/switch-input.svelte"; | ||
|
||
export type { | ||
SwitchProps as Props, | ||
SwitchThumbProps as ThumbProps, | ||
SwitchInputProps as InputProps, | ||
SwitchEvents as Events, | ||
} from "./types.js"; | ||
export type { SwitchRootProps as RootProps, SwitchThumbProps as ThumbProps } from "./types.js"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
import { getContext, setContext } from "svelte"; | ||
import { | ||
getAriaChecked, | ||
getAriaRequired, | ||
getDataChecked, | ||
getDataDisabled, | ||
getDataRequired, | ||
} from "$lib/internal/attrs.js"; | ||
import type { BoxedValues, ReadonlyBoxedValues } from "$lib/internal/box.svelte.js"; | ||
import { type EventCallback, composeHandlers } from "$lib/internal/events.js"; | ||
import { kbd } from "$lib/internal/kbd.js"; | ||
|
||
type SwitchRootStateProps = ReadonlyBoxedValues<{ | ||
disabled: boolean; | ||
required: boolean; | ||
name: string | undefined; | ||
value: string; | ||
onclick: EventCallback<MouseEvent>; | ||
onkeydown: EventCallback<KeyboardEvent>; | ||
}> & | ||
BoxedValues<{ | ||
checked: boolean; | ||
}>; | ||
|
||
class SwitchRootState { | ||
checked: SwitchRootStateProps["checked"]; | ||
disabled: SwitchRootStateProps["disabled"]; | ||
required: SwitchRootStateProps["required"]; | ||
name: SwitchRootStateProps["name"]; | ||
value: SwitchRootStateProps["value"]; | ||
#composedClick: EventCallback<MouseEvent>; | ||
#composedKeydown: EventCallback<KeyboardEvent>; | ||
|
||
constructor(props: SwitchRootStateProps) { | ||
this.checked = props.checked; | ||
this.disabled = props.disabled; | ||
this.required = props.required; | ||
this.name = props.name; | ||
this.value = props.value; | ||
this.#composedClick = composeHandlers(props.onclick, this.#onclick); | ||
this.#composedKeydown = composeHandlers(props.onkeydown, this.#onkeydown); | ||
} | ||
|
||
#toggle() { | ||
this.checked.value = !this.checked.value; | ||
} | ||
|
||
#onkeydown = (e: KeyboardEvent) => { | ||
if (!(e.key === kbd.ENTER || e.key === kbd.SPACE) || this.disabled.value) return; | ||
e.preventDefault(); | ||
this.#toggle(); | ||
}; | ||
|
||
#onclick = () => { | ||
if (this.disabled.value) return; | ||
this.#toggle(); | ||
}; | ||
|
||
get props() { | ||
return { | ||
"data-disabled": getDataDisabled(this.disabled.value), | ||
"data-state": getDataChecked(this.checked.value), | ||
"data-required": getDataRequired(this.required.value), | ||
type: "button", | ||
role: "switch", | ||
"aria-checked": getAriaChecked(this.checked.value), | ||
"aria-required": getAriaRequired(this.required.value), | ||
"data-switch-root": "", | ||
// | ||
onclick: this.#composedClick, | ||
onkeydown: this.#composedKeydown, | ||
} as const; | ||
} | ||
|
||
createInput() { | ||
return new SwitchInputState(this); | ||
} | ||
|
||
createThumb() { | ||
return new SwitchThumbState(this); | ||
} | ||
} | ||
|
||
class SwitchInputState { | ||
#root: SwitchRootState; | ||
|
||
constructor(root: SwitchRootState) { | ||
this.#root = root; | ||
} | ||
|
||
get shouldRender() { | ||
return this.#root.name.value !== undefined; | ||
} | ||
|
||
get props() { | ||
return { | ||
type: "checkbox", | ||
name: this.#root.name.value, | ||
value: this.#root.value.value, | ||
checked: this.#root.checked.value, | ||
disabled: this.#root.disabled.value, | ||
required: this.#root.required.value, | ||
} as const; | ||
} | ||
} | ||
|
||
class SwitchThumbState { | ||
root: SwitchRootState; | ||
|
||
constructor(root: SwitchRootState) { | ||
this.root = root; | ||
} | ||
|
||
get props() { | ||
return { | ||
"data-disabled": getDataDisabled(this.root.disabled.value), | ||
"data-state": getDataChecked(this.root.checked.value), | ||
"data-required": getDataRequired(this.root.required.value), | ||
"data-switch-thumb": "", | ||
} as const; | ||
} | ||
} | ||
|
||
// | ||
// CONTEXT METHODS | ||
// | ||
|
||
const SWITCH_ROOT_KEY = Symbol("Switch.Root"); | ||
|
||
export function setSwitchRootState(props: SwitchRootStateProps) { | ||
return setContext(SWITCH_ROOT_KEY, new SwitchRootState(props)); | ||
} | ||
|
||
export function getSwitchRootState(): SwitchRootState { | ||
return getContext(SWITCH_ROOT_KEY); | ||
} | ||
|
||
export function getSwitchInputState(): SwitchInputState { | ||
return getSwitchRootState().createInput(); | ||
} | ||
|
||
export function getSwitchThumbState(): SwitchThumbState { | ||
return getSwitchRootState().createThumb(); | ||
} |
Oops, something went wrong.