@tamagui/switch 1.0.1-beta.136 → 1.0.1-beta.139

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,265 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod));
21
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
+ var Switch_draggable_tmp_exports = {};
23
+ __export(Switch_draggable_tmp_exports, {
24
+ Switch: () => Switch,
25
+ SwitchFrame: () => SwitchFrame,
26
+ SwitchThumb: () => SwitchThumb,
27
+ SwitchThumbFrame: () => SwitchThumbFrame,
28
+ createSwitchScope: () => createSwitchScope
29
+ });
30
+ module.exports = __toCommonJS(Switch_draggable_tmp_exports);
31
+ var import_react_use_previous = require("@radix-ui/react-use-previous");
32
+ var import_compose_refs = require("@tamagui/compose-refs");
33
+ var import_core = require("@tamagui/core");
34
+ var import_create_context = require("@tamagui/create-context");
35
+ var import_focusable = require("@tamagui/focusable");
36
+ var import_label = require("@tamagui/label");
37
+ var import_stacks = require("@tamagui/stacks");
38
+ var import_use_controllable_state = require("@tamagui/use-controllable-state");
39
+ var React = __toESM(require("react"));
40
+ var import_react_native = require("react-native");
41
+ const SWITCH_NAME = "Switch";
42
+ const getSwitchHeight = (val) => Math.round((0, import_core.getVariableValue)((0, import_core.getSize)(val)) * 0.65);
43
+ const getSwitchWidth = (val) => getSwitchHeight(val) * 2;
44
+ const scopeContexts = (0, import_create_context.createContextScope)(SWITCH_NAME);
45
+ const [createSwitchContext] = scopeContexts;
46
+ const createSwitchScope = scopeContexts[1];
47
+ const [SwitchProvider, useSwitchContext] = createSwitchContext(SWITCH_NAME);
48
+ const THUMB_NAME = "SwitchThumb";
49
+ const SwitchThumbFrame = (0, import_core.styled)(import_stacks.ThemeableStack, {
50
+ name: "SwitchThumb",
51
+ backgroundColor: "$background",
52
+ borderRadius: 1e3,
53
+ variants: {
54
+ size: {
55
+ "...size": (val) => ({
56
+ height: getSwitchHeight(val),
57
+ width: getSwitchHeight(val)
58
+ })
59
+ }
60
+ },
61
+ defaultVariants: {
62
+ size: "$4"
63
+ }
64
+ });
65
+ const SwitchThumb = SwitchThumbFrame.extractable(React.forwardRef((props, forwardedRef) => {
66
+ const { __scopeSwitch, ...thumbProps } = props;
67
+ const { size, disabled, checked, frameLayout } = useSwitchContext(THUMB_NAME, __scopeSwitch);
68
+ const [position] = React.useState(new import_react_native.Animated.ValueXY(0));
69
+ const [thumbWidth, setThumbWidth] = React.useState(0);
70
+ const responder = React.useMemo(() => {
71
+ return import_react_native.PanResponder.create({
72
+ onStartShouldSetPanResponder: () => true,
73
+ onPanResponderMove: import_react_native.Animated.event([
74
+ null,
75
+ {
76
+ dx: position.x
77
+ }
78
+ ], {
79
+ useNativeDriver: false
80
+ })
81
+ });
82
+ }, []);
83
+ React.useEffect(() => {
84
+ position.addListener((val) => {
85
+ console.log("x", val);
86
+ });
87
+ }, []);
88
+ const maxMove = (0, import_core.getVariableValue)(getSwitchWidth(size)) - (0, import_core.getVariableValue)(getSwitchHeight(size));
89
+ const maxDrag = ((frameLayout == null ? void 0 : frameLayout.width) || 0) - thumbWidth;
90
+ return /* @__PURE__ */ React.createElement(import_react_native.Animated.View, {
91
+ style: [
92
+ {
93
+ transform: [{ translateX: position.x }]
94
+ }
95
+ ]
96
+ }, /* @__PURE__ */ React.createElement(SwitchThumbFrame, {
97
+ size,
98
+ "data-state": getState(checked),
99
+ "data-disabled": disabled ? "" : void 0,
100
+ ...responder.panHandlers,
101
+ ...thumbProps,
102
+ onLayout: ({ nativeEvent }) => {
103
+ console.log("thumb", nativeEvent.layout);
104
+ setThumbWidth(nativeEvent.layout.width);
105
+ },
106
+ x: checked ? maxMove : 0,
107
+ ref: forwardedRef
108
+ }));
109
+ }));
110
+ SwitchThumb.displayName = THUMB_NAME;
111
+ const SwitchFrame = (0, import_core.styled)(import_stacks.XStack, {
112
+ name: "Switch",
113
+ tag: "button",
114
+ borderRadius: 1e3,
115
+ borderWidth: 2,
116
+ borderColor: "transparent",
117
+ backgroundColor: "$background",
118
+ focusStyle: {
119
+ borderColor: "$borderColorFocus"
120
+ },
121
+ variants: {
122
+ size: {
123
+ "...size": (val) => {
124
+ const height = (0, import_core.calc)(getSwitchHeight(val), "+", 4);
125
+ const width = (0, import_core.calc)(getSwitchWidth(val), "+", 4);
126
+ return {
127
+ height,
128
+ minHeight: height,
129
+ width
130
+ };
131
+ }
132
+ }
133
+ },
134
+ defaultVariants: {
135
+ size: "$4"
136
+ }
137
+ });
138
+ const Switch = (0, import_core.withStaticProperties)(SwitchFrame.extractable(React.forwardRef((props, forwardedRef) => {
139
+ const {
140
+ __scopeSwitch,
141
+ labeledBy: ariaLabelledby,
142
+ name,
143
+ checked: checkedProp,
144
+ defaultChecked,
145
+ required,
146
+ disabled,
147
+ value = "on",
148
+ onCheckedChange,
149
+ size = "$4",
150
+ ...switchProps
151
+ } = props;
152
+ const [button, setButton] = React.useState(null);
153
+ const composedRefs = (0, import_compose_refs.useComposedRefs)(forwardedRef, (node) => setButton(node));
154
+ const labelId = (0, import_label.useLabelContext)(button);
155
+ const labelledBy = ariaLabelledby || labelId;
156
+ const hasConsumerStoppedPropagationRef = React.useRef(false);
157
+ const [frameLayout, setFrameLayout] = React.useState();
158
+ const isFormControl = import_core.isWeb ? button ? Boolean(button.closest("form")) : true : false;
159
+ const [checked = false, setChecked] = (0, import_use_controllable_state.useControllableState)({
160
+ prop: checkedProp,
161
+ defaultProp: defaultChecked || false,
162
+ onChange: onCheckedChange
163
+ });
164
+ if (!import_core.isWeb) {
165
+ React.useEffect(() => {
166
+ if (!props.id)
167
+ return;
168
+ return (0, import_focusable.registerFocusable)(props.id, {
169
+ focus: () => {
170
+ setChecked((x) => !x);
171
+ }
172
+ });
173
+ }, [props.id, setChecked]);
174
+ }
175
+ return /* @__PURE__ */ React.createElement(SwitchProvider, {
176
+ scope: __scopeSwitch,
177
+ checked,
178
+ disabled,
179
+ size,
180
+ frameLayout
181
+ }, /* @__PURE__ */ React.createElement(SwitchFrame, {
182
+ size,
183
+ onLayout: ({ nativeEvent }) => {
184
+ console.log("?", nativeEvent.layout);
185
+ setFrameLayout(nativeEvent.layout);
186
+ },
187
+ role: "switch",
188
+ "aria-checked": checked,
189
+ "aria-labelledby": labelledBy,
190
+ "aria-required": required,
191
+ "data-state": getState(checked),
192
+ "data-disabled": disabled ? "" : void 0,
193
+ disabled,
194
+ theme: checked ? "active" : null,
195
+ themeShallow: true,
196
+ tabIndex: disabled ? void 0 : 0,
197
+ value,
198
+ ...switchProps,
199
+ ref: composedRefs,
200
+ onPress: (event) => {
201
+ var _a;
202
+ (_a = props.onPress) == null ? void 0 : _a.call(props, event);
203
+ setChecked((prevChecked) => !prevChecked);
204
+ if (import_core.isWeb && isFormControl) {
205
+ hasConsumerStoppedPropagationRef.current = event.isPropagationStopped();
206
+ if (!hasConsumerStoppedPropagationRef.current)
207
+ event.stopPropagation();
208
+ }
209
+ }
210
+ }), import_core.isWeb && isFormControl && /* @__PURE__ */ React.createElement(BubbleInput, {
211
+ control: button,
212
+ bubbles: !hasConsumerStoppedPropagationRef.current,
213
+ name,
214
+ value,
215
+ checked,
216
+ required,
217
+ disabled,
218
+ style: { transform: "translateX(-100%)" }
219
+ }));
220
+ })), {
221
+ Thumb: SwitchThumb
222
+ });
223
+ const BubbleInput = (props) => {
224
+ const { control, checked, bubbles = true, ...inputProps } = props;
225
+ const ref = React.useRef(null);
226
+ const prevChecked = (0, import_react_use_previous.usePrevious)(checked);
227
+ React.useEffect(() => {
228
+ const input = ref.current;
229
+ const inputProto = window.HTMLInputElement.prototype;
230
+ const descriptor = Object.getOwnPropertyDescriptor(inputProto, "checked");
231
+ const setChecked = descriptor.set;
232
+ if (prevChecked !== checked && setChecked) {
233
+ const event = new Event("click", { bubbles });
234
+ setChecked.call(input, checked);
235
+ input.dispatchEvent(event);
236
+ }
237
+ }, [prevChecked, checked, bubbles]);
238
+ return /* @__PURE__ */ React.createElement("input", {
239
+ type: "checkbox",
240
+ "aria-hidden": true,
241
+ defaultChecked: checked,
242
+ ...inputProps,
243
+ tabIndex: -1,
244
+ ref,
245
+ style: {
246
+ ...props.style,
247
+ position: "absolute",
248
+ pointerEvents: "none",
249
+ opacity: 0,
250
+ margin: 0
251
+ }
252
+ });
253
+ };
254
+ function getState(checked) {
255
+ return checked ? "checked" : "unchecked";
256
+ }
257
+ // Annotate the CommonJS export names for ESM import in node:
258
+ 0 && (module.exports = {
259
+ Switch,
260
+ SwitchFrame,
261
+ SwitchThumb,
262
+ SwitchThumbFrame,
263
+ createSwitchScope
264
+ });
265
+ //# sourceMappingURL=Switch.draggable-tmp.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/Switch.draggable-tmp.tsx"],
4
+ "sourcesContent": ["// via radix\n// https://github.com/radix-ui/primitives/blob/main/packages/react/switch/src/Switch.tsx\n\nimport { usePrevious } from '@radix-ui/react-use-previous'\nimport { useComposedRefs } from '@tamagui/compose-refs'\nimport {\n GetProps,\n SizeTokens,\n calc,\n getSize,\n getVariableValue,\n isWeb,\n styled,\n withStaticProperties,\n} from '@tamagui/core'\nimport { ScopedProps, createContextScope } from '@tamagui/create-context'\nimport { registerFocusable } from '@tamagui/focusable'\nimport { useLabelContext } from '@tamagui/label'\nimport { ThemeableStack, XStack } from '@tamagui/stacks'\nimport { useControllableState } from '@tamagui/use-controllable-state'\nimport * as React from 'react'\nimport { Animated, LayoutRectangle, PanResponder, View } from 'react-native'\n\nconst SWITCH_NAME = 'Switch'\n\n// TODO make customizable\nconst getSwitchHeight = (val: SizeTokens) => Math.round(getVariableValue(getSize(val)) * 0.65)\nconst getSwitchWidth = (val: SizeTokens) => getSwitchHeight(val) * 2\n\nconst scopeContexts = createContextScope(SWITCH_NAME)\nconst [createSwitchContext] = scopeContexts\nexport const createSwitchScope = scopeContexts[1]\n\nconst [SwitchProvider, useSwitchContext] = createSwitchContext<{\n checked: boolean\n disabled?: boolean\n size: SizeTokens\n frameLayout?: LayoutRectangle\n}>(SWITCH_NAME)\n\n/* -------------------------------------------------------------------------------------------------\n * SwitchThumb\n * -----------------------------------------------------------------------------------------------*/\n\nconst THUMB_NAME = 'SwitchThumb'\n\nexport const SwitchThumbFrame = styled(ThemeableStack, {\n name: 'SwitchThumb',\n backgroundColor: '$background',\n borderRadius: 1000,\n\n variants: {\n size: {\n '...size': (val) => ({\n height: getSwitchHeight(val),\n width: getSwitchHeight(val),\n }),\n },\n },\n\n defaultVariants: {\n size: '$4',\n },\n})\n\nexport type SwitchThumbProps = GetProps<typeof SwitchThumbFrame>\n\nexport const SwitchThumb = SwitchThumbFrame.extractable(\n React.forwardRef<React.ElementRef<'span'>, SwitchThumbProps>(\n (props: ScopedProps<SwitchThumbProps, 'Switch'>, forwardedRef) => {\n const { __scopeSwitch, ...thumbProps } = props\n const { size, disabled, checked, frameLayout } = useSwitchContext(THUMB_NAME, __scopeSwitch)\n const [position] = React.useState(new Animated.ValueXY(0))\n const [thumbWidth, setThumbWidth] = React.useState(0)\n const responder = React.useMemo(() => {\n return PanResponder.create({\n onStartShouldSetPanResponder: () => true,\n onPanResponderMove: Animated.event(\n [\n null,\n {\n dx: position.x,\n },\n ],\n {\n useNativeDriver: false,\n }\n ),\n })\n }, [])\n\n React.useEffect(() => {\n position.addListener((val) => {\n console.log('x', val)\n })\n }, [])\n\n const maxMove =\n getVariableValue(getSwitchWidth(size)) - getVariableValue(getSwitchHeight(size))\n\n const maxDrag = (frameLayout?.width || 0) - thumbWidth\n\n return (\n <Animated.View\n style={[\n {\n transform: [{ translateX: position.x }],\n },\n ]}\n >\n <SwitchThumbFrame\n size={size}\n data-state={getState(checked)}\n data-disabled={disabled ? '' : undefined}\n {...responder.panHandlers}\n {...thumbProps}\n onLayout={({ nativeEvent }) => {\n console.log('thumb', nativeEvent.layout)\n setThumbWidth(nativeEvent.layout.width)\n }}\n x={checked ? maxMove : 0}\n ref={forwardedRef}\n />\n </Animated.View>\n )\n }\n )\n)\n\nSwitchThumb.displayName = THUMB_NAME\n\n/* -------------------------------------------------------------------------------------------------\n * Switch\n * -----------------------------------------------------------------------------------------------*/\n\nexport const SwitchFrame = styled(XStack, {\n name: 'Switch',\n tag: 'button',\n borderRadius: 1000,\n borderWidth: 2,\n borderColor: 'transparent',\n backgroundColor: '$background',\n\n focusStyle: {\n borderColor: '$borderColorFocus',\n },\n\n variants: {\n size: {\n '...size': (val) => {\n const height = calc(getSwitchHeight(val), '+', 4)\n const width = calc(getSwitchWidth(val), '+', 4)\n return {\n height,\n minHeight: height,\n width,\n }\n },\n },\n },\n\n defaultVariants: {\n size: '$4',\n },\n})\n\ntype SwitchButtonProps = GetProps<typeof SwitchFrame>\n\nexport type SwitchProps = SwitchButtonProps & {\n labeledBy?: string\n name?: string\n value?: string\n checked?: boolean\n defaultChecked?: boolean\n required?: boolean\n onCheckedChange?(checked: boolean): void\n}\n\nexport const Switch = withStaticProperties(\n SwitchFrame.extractable(\n React.forwardRef<HTMLButtonElement | View, SwitchProps>(\n (props: ScopedProps<SwitchProps, 'Switch'>, forwardedRef) => {\n const {\n __scopeSwitch,\n labeledBy: ariaLabelledby,\n name,\n checked: checkedProp,\n defaultChecked,\n required,\n disabled,\n value = 'on',\n onCheckedChange,\n size = '$4',\n ...switchProps\n } = props\n const [button, setButton] = React.useState<HTMLButtonElement | null>(null)\n const composedRefs = useComposedRefs(forwardedRef, (node) => setButton(node as any))\n const labelId = useLabelContext(button)\n const labelledBy = ariaLabelledby || labelId\n const hasConsumerStoppedPropagationRef = React.useRef(false)\n const [frameLayout, setFrameLayout] = React.useState<LayoutRectangle>()\n // We set this to true by default so that events bubble to forms without JS (SSR)\n const isFormControl = isWeb ? (button ? Boolean(button.closest('form')) : true) : false\n const [checked = false, setChecked] = useControllableState({\n prop: checkedProp,\n defaultProp: defaultChecked || false,\n onChange: onCheckedChange,\n })\n\n if (!isWeb) {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n React.useEffect(() => {\n if (!props.id) return\n return registerFocusable(props.id, {\n focus: () => {\n setChecked((x) => !x)\n },\n })\n }, [props.id, setChecked])\n }\n\n return (\n <SwitchProvider\n scope={__scopeSwitch}\n checked={checked}\n disabled={disabled}\n size={size}\n frameLayout={frameLayout}\n >\n <SwitchFrame\n size={size}\n onLayout={({ nativeEvent }) => {\n console.log('?', nativeEvent.layout)\n setFrameLayout(nativeEvent.layout)\n }}\n // @ts-ignore\n role=\"switch\"\n aria-checked={checked}\n aria-labelledby={labelledBy}\n aria-required={required}\n data-state={getState(checked)}\n data-disabled={disabled ? '' : undefined}\n disabled={disabled}\n theme={checked ? 'active' : null}\n themeShallow\n // @ts-ignore\n tabIndex={disabled ? undefined : 0}\n // @ts-ignore\n value={value}\n {...switchProps}\n ref={composedRefs}\n onPress={(event) => {\n props.onPress?.(event)\n setChecked((prevChecked) => !prevChecked)\n if (isWeb && isFormControl) {\n hasConsumerStoppedPropagationRef.current = event.isPropagationStopped()\n // if switch is in a form, stop propagation from the button so that we only propagate\n // one click event (from the input). We propagate changes from an input so that native\n // form validation works and form events reflect switch updates.\n if (!hasConsumerStoppedPropagationRef.current) event.stopPropagation()\n }\n }}\n />\n {isWeb && isFormControl && (\n <BubbleInput\n control={button}\n bubbles={!hasConsumerStoppedPropagationRef.current}\n name={name}\n value={value}\n checked={checked}\n required={required}\n disabled={disabled}\n // We transform because the input is absolutely positioned but we have\n // rendered it **after** the button. This pulls it back to sit on top\n // of the button.\n style={{ transform: 'translateX(-100%)' }}\n />\n )}\n </SwitchProvider>\n )\n }\n )\n ),\n {\n Thumb: SwitchThumb,\n }\n)\n\n/* ---------------------------------------------------------------------------------------------- */\n\ntype InputProps = any //Radix.ComponentPropsWithoutRef<'input'>\ninterface BubbleInputProps extends Omit<InputProps, 'checked'> {\n checked: boolean\n control: HTMLElement | null\n bubbles: boolean\n}\n\n// TODO make this native friendly\nconst BubbleInput = (props: BubbleInputProps) => {\n const { control, checked, bubbles = true, ...inputProps } = props\n const ref = React.useRef<HTMLInputElement>(null)\n const prevChecked = usePrevious(checked)\n // const controlSize = useSize(control)\n\n // Bubble checked change to parents (e.g form change event)\n React.useEffect(() => {\n const input = ref.current!\n const inputProto = window.HTMLInputElement.prototype\n const descriptor = Object.getOwnPropertyDescriptor(inputProto, 'checked') as PropertyDescriptor\n const setChecked = descriptor.set\n if (prevChecked !== checked && setChecked) {\n const event = new Event('click', { bubbles })\n setChecked.call(input, checked)\n input.dispatchEvent(event)\n }\n }, [prevChecked, checked, bubbles])\n\n return (\n <input\n type=\"checkbox\"\n aria-hidden\n defaultChecked={checked}\n {...inputProps}\n tabIndex={-1}\n ref={ref}\n style={{\n ...props.style,\n // ...controlSize,\n position: 'absolute',\n pointerEvents: 'none',\n opacity: 0,\n margin: 0,\n }}\n />\n )\n}\n\nfunction getState(checked: boolean) {\n return checked ? 'checked' : 'unchecked'\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,241 @@
1
+ import { usePrevious } from "@radix-ui/react-use-previous";
2
+ import { useComposedRefs } from "@tamagui/compose-refs";
3
+ import {
4
+ calc,
5
+ getSize,
6
+ getVariableValue,
7
+ isWeb,
8
+ styled,
9
+ withStaticProperties
10
+ } from "@tamagui/core";
11
+ import { createContextScope } from "@tamagui/create-context";
12
+ import { registerFocusable } from "@tamagui/focusable";
13
+ import { useLabelContext } from "@tamagui/label";
14
+ import { ThemeableStack, XStack } from "@tamagui/stacks";
15
+ import { useControllableState } from "@tamagui/use-controllable-state";
16
+ import * as React from "react";
17
+ import { Animated, PanResponder } from "react-native";
18
+ const SWITCH_NAME = "Switch";
19
+ const getSwitchHeight = (val) => Math.round(getVariableValue(getSize(val)) * 0.65);
20
+ const getSwitchWidth = (val) => getSwitchHeight(val) * 2;
21
+ const scopeContexts = createContextScope(SWITCH_NAME);
22
+ const [createSwitchContext] = scopeContexts;
23
+ const createSwitchScope = scopeContexts[1];
24
+ const [SwitchProvider, useSwitchContext] = createSwitchContext(SWITCH_NAME);
25
+ const THUMB_NAME = "SwitchThumb";
26
+ const SwitchThumbFrame = styled(ThemeableStack, {
27
+ name: "SwitchThumb",
28
+ backgroundColor: "$background",
29
+ borderRadius: 1e3,
30
+ variants: {
31
+ size: {
32
+ "...size": (val) => ({
33
+ height: getSwitchHeight(val),
34
+ width: getSwitchHeight(val)
35
+ })
36
+ }
37
+ },
38
+ defaultVariants: {
39
+ size: "$4"
40
+ }
41
+ });
42
+ const SwitchThumb = SwitchThumbFrame.extractable(React.forwardRef((props, forwardedRef) => {
43
+ const { __scopeSwitch, ...thumbProps } = props;
44
+ const { size, disabled, checked, frameLayout } = useSwitchContext(THUMB_NAME, __scopeSwitch);
45
+ const [position] = React.useState(new Animated.ValueXY(0));
46
+ const [thumbWidth, setThumbWidth] = React.useState(0);
47
+ const responder = React.useMemo(() => {
48
+ return PanResponder.create({
49
+ onStartShouldSetPanResponder: () => true,
50
+ onPanResponderMove: Animated.event([
51
+ null,
52
+ {
53
+ dx: position.x
54
+ }
55
+ ], {
56
+ useNativeDriver: false
57
+ })
58
+ });
59
+ }, []);
60
+ React.useEffect(() => {
61
+ position.addListener((val) => {
62
+ console.log("x", val);
63
+ });
64
+ }, []);
65
+ const maxMove = getVariableValue(getSwitchWidth(size)) - getVariableValue(getSwitchHeight(size));
66
+ const maxDrag = ((frameLayout == null ? void 0 : frameLayout.width) || 0) - thumbWidth;
67
+ return /* @__PURE__ */ React.createElement(Animated.View, {
68
+ style: [
69
+ {
70
+ transform: [{ translateX: position.x }]
71
+ }
72
+ ]
73
+ }, /* @__PURE__ */ React.createElement(SwitchThumbFrame, {
74
+ size,
75
+ "data-state": getState(checked),
76
+ "data-disabled": disabled ? "" : void 0,
77
+ ...responder.panHandlers,
78
+ ...thumbProps,
79
+ onLayout: ({ nativeEvent }) => {
80
+ console.log("thumb", nativeEvent.layout);
81
+ setThumbWidth(nativeEvent.layout.width);
82
+ },
83
+ x: checked ? maxMove : 0,
84
+ ref: forwardedRef
85
+ }));
86
+ }));
87
+ SwitchThumb.displayName = THUMB_NAME;
88
+ const SwitchFrame = styled(XStack, {
89
+ name: "Switch",
90
+ tag: "button",
91
+ borderRadius: 1e3,
92
+ borderWidth: 2,
93
+ borderColor: "transparent",
94
+ backgroundColor: "$background",
95
+ focusStyle: {
96
+ borderColor: "$borderColorFocus"
97
+ },
98
+ variants: {
99
+ size: {
100
+ "...size": (val) => {
101
+ const height = calc(getSwitchHeight(val), "+", 4);
102
+ const width = calc(getSwitchWidth(val), "+", 4);
103
+ return {
104
+ height,
105
+ minHeight: height,
106
+ width
107
+ };
108
+ }
109
+ }
110
+ },
111
+ defaultVariants: {
112
+ size: "$4"
113
+ }
114
+ });
115
+ const Switch = withStaticProperties(SwitchFrame.extractable(React.forwardRef((props, forwardedRef) => {
116
+ const {
117
+ __scopeSwitch,
118
+ labeledBy: ariaLabelledby,
119
+ name,
120
+ checked: checkedProp,
121
+ defaultChecked,
122
+ required,
123
+ disabled,
124
+ value = "on",
125
+ onCheckedChange,
126
+ size = "$4",
127
+ ...switchProps
128
+ } = props;
129
+ const [button, setButton] = React.useState(null);
130
+ const composedRefs = useComposedRefs(forwardedRef, (node) => setButton(node));
131
+ const labelId = useLabelContext(button);
132
+ const labelledBy = ariaLabelledby || labelId;
133
+ const hasConsumerStoppedPropagationRef = React.useRef(false);
134
+ const [frameLayout, setFrameLayout] = React.useState();
135
+ const isFormControl = isWeb ? button ? Boolean(button.closest("form")) : true : false;
136
+ const [checked = false, setChecked] = useControllableState({
137
+ prop: checkedProp,
138
+ defaultProp: defaultChecked || false,
139
+ onChange: onCheckedChange
140
+ });
141
+ if (!isWeb) {
142
+ React.useEffect(() => {
143
+ if (!props.id)
144
+ return;
145
+ return registerFocusable(props.id, {
146
+ focus: () => {
147
+ setChecked((x) => !x);
148
+ }
149
+ });
150
+ }, [props.id, setChecked]);
151
+ }
152
+ return /* @__PURE__ */ React.createElement(SwitchProvider, {
153
+ scope: __scopeSwitch,
154
+ checked,
155
+ disabled,
156
+ size,
157
+ frameLayout
158
+ }, /* @__PURE__ */ React.createElement(SwitchFrame, {
159
+ size,
160
+ onLayout: ({ nativeEvent }) => {
161
+ console.log("?", nativeEvent.layout);
162
+ setFrameLayout(nativeEvent.layout);
163
+ },
164
+ role: "switch",
165
+ "aria-checked": checked,
166
+ "aria-labelledby": labelledBy,
167
+ "aria-required": required,
168
+ "data-state": getState(checked),
169
+ "data-disabled": disabled ? "" : void 0,
170
+ disabled,
171
+ theme: checked ? "active" : null,
172
+ themeShallow: true,
173
+ tabIndex: disabled ? void 0 : 0,
174
+ value,
175
+ ...switchProps,
176
+ ref: composedRefs,
177
+ onPress: (event) => {
178
+ var _a;
179
+ (_a = props.onPress) == null ? void 0 : _a.call(props, event);
180
+ setChecked((prevChecked) => !prevChecked);
181
+ if (isWeb && isFormControl) {
182
+ hasConsumerStoppedPropagationRef.current = event.isPropagationStopped();
183
+ if (!hasConsumerStoppedPropagationRef.current)
184
+ event.stopPropagation();
185
+ }
186
+ }
187
+ }), isWeb && isFormControl && /* @__PURE__ */ React.createElement(BubbleInput, {
188
+ control: button,
189
+ bubbles: !hasConsumerStoppedPropagationRef.current,
190
+ name,
191
+ value,
192
+ checked,
193
+ required,
194
+ disabled,
195
+ style: { transform: "translateX(-100%)" }
196
+ }));
197
+ })), {
198
+ Thumb: SwitchThumb
199
+ });
200
+ const BubbleInput = (props) => {
201
+ const { control, checked, bubbles = true, ...inputProps } = props;
202
+ const ref = React.useRef(null);
203
+ const prevChecked = usePrevious(checked);
204
+ React.useEffect(() => {
205
+ const input = ref.current;
206
+ const inputProto = window.HTMLInputElement.prototype;
207
+ const descriptor = Object.getOwnPropertyDescriptor(inputProto, "checked");
208
+ const setChecked = descriptor.set;
209
+ if (prevChecked !== checked && setChecked) {
210
+ const event = new Event("click", { bubbles });
211
+ setChecked.call(input, checked);
212
+ input.dispatchEvent(event);
213
+ }
214
+ }, [prevChecked, checked, bubbles]);
215
+ return /* @__PURE__ */ React.createElement("input", {
216
+ type: "checkbox",
217
+ "aria-hidden": true,
218
+ defaultChecked: checked,
219
+ ...inputProps,
220
+ tabIndex: -1,
221
+ ref,
222
+ style: {
223
+ ...props.style,
224
+ position: "absolute",
225
+ pointerEvents: "none",
226
+ opacity: 0,
227
+ margin: 0
228
+ }
229
+ });
230
+ };
231
+ function getState(checked) {
232
+ return checked ? "checked" : "unchecked";
233
+ }
234
+ export {
235
+ Switch,
236
+ SwitchFrame,
237
+ SwitchThumb,
238
+ SwitchThumbFrame,
239
+ createSwitchScope
240
+ };
241
+ //# sourceMappingURL=Switch.draggable-tmp.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/Switch.draggable-tmp.tsx"],
4
+ "sourcesContent": ["// via radix\n// https://github.com/radix-ui/primitives/blob/main/packages/react/switch/src/Switch.tsx\n\nimport { usePrevious } from '@radix-ui/react-use-previous'\nimport { useComposedRefs } from '@tamagui/compose-refs'\nimport {\n GetProps,\n SizeTokens,\n calc,\n getSize,\n getVariableValue,\n isWeb,\n styled,\n withStaticProperties,\n} from '@tamagui/core'\nimport { ScopedProps, createContextScope } from '@tamagui/create-context'\nimport { registerFocusable } from '@tamagui/focusable'\nimport { useLabelContext } from '@tamagui/label'\nimport { ThemeableStack, XStack } from '@tamagui/stacks'\nimport { useControllableState } from '@tamagui/use-controllable-state'\nimport * as React from 'react'\nimport { Animated, LayoutRectangle, PanResponder, View } from 'react-native'\n\nconst SWITCH_NAME = 'Switch'\n\n// TODO make customizable\nconst getSwitchHeight = (val: SizeTokens) => Math.round(getVariableValue(getSize(val)) * 0.65)\nconst getSwitchWidth = (val: SizeTokens) => getSwitchHeight(val) * 2\n\nconst scopeContexts = createContextScope(SWITCH_NAME)\nconst [createSwitchContext] = scopeContexts\nexport const createSwitchScope = scopeContexts[1]\n\nconst [SwitchProvider, useSwitchContext] = createSwitchContext<{\n checked: boolean\n disabled?: boolean\n size: SizeTokens\n frameLayout?: LayoutRectangle\n}>(SWITCH_NAME)\n\n/* -------------------------------------------------------------------------------------------------\n * SwitchThumb\n * -----------------------------------------------------------------------------------------------*/\n\nconst THUMB_NAME = 'SwitchThumb'\n\nexport const SwitchThumbFrame = styled(ThemeableStack, {\n name: 'SwitchThumb',\n backgroundColor: '$background',\n borderRadius: 1000,\n\n variants: {\n size: {\n '...size': (val) => ({\n height: getSwitchHeight(val),\n width: getSwitchHeight(val),\n }),\n },\n },\n\n defaultVariants: {\n size: '$4',\n },\n})\n\nexport type SwitchThumbProps = GetProps<typeof SwitchThumbFrame>\n\nexport const SwitchThumb = SwitchThumbFrame.extractable(\n React.forwardRef<React.ElementRef<'span'>, SwitchThumbProps>(\n (props: ScopedProps<SwitchThumbProps, 'Switch'>, forwardedRef) => {\n const { __scopeSwitch, ...thumbProps } = props\n const { size, disabled, checked, frameLayout } = useSwitchContext(THUMB_NAME, __scopeSwitch)\n const [position] = React.useState(new Animated.ValueXY(0))\n const [thumbWidth, setThumbWidth] = React.useState(0)\n const responder = React.useMemo(() => {\n return PanResponder.create({\n onStartShouldSetPanResponder: () => true,\n onPanResponderMove: Animated.event(\n [\n null,\n {\n dx: position.x,\n },\n ],\n {\n useNativeDriver: false,\n }\n ),\n })\n }, [])\n\n React.useEffect(() => {\n position.addListener((val) => {\n console.log('x', val)\n })\n }, [])\n\n const maxMove =\n getVariableValue(getSwitchWidth(size)) - getVariableValue(getSwitchHeight(size))\n\n const maxDrag = (frameLayout?.width || 0) - thumbWidth\n\n return (\n <Animated.View\n style={[\n {\n transform: [{ translateX: position.x }],\n },\n ]}\n >\n <SwitchThumbFrame\n size={size}\n data-state={getState(checked)}\n data-disabled={disabled ? '' : undefined}\n {...responder.panHandlers}\n {...thumbProps}\n onLayout={({ nativeEvent }) => {\n console.log('thumb', nativeEvent.layout)\n setThumbWidth(nativeEvent.layout.width)\n }}\n x={checked ? maxMove : 0}\n ref={forwardedRef}\n />\n </Animated.View>\n )\n }\n )\n)\n\nSwitchThumb.displayName = THUMB_NAME\n\n/* -------------------------------------------------------------------------------------------------\n * Switch\n * -----------------------------------------------------------------------------------------------*/\n\nexport const SwitchFrame = styled(XStack, {\n name: 'Switch',\n tag: 'button',\n borderRadius: 1000,\n borderWidth: 2,\n borderColor: 'transparent',\n backgroundColor: '$background',\n\n focusStyle: {\n borderColor: '$borderColorFocus',\n },\n\n variants: {\n size: {\n '...size': (val) => {\n const height = calc(getSwitchHeight(val), '+', 4)\n const width = calc(getSwitchWidth(val), '+', 4)\n return {\n height,\n minHeight: height,\n width,\n }\n },\n },\n },\n\n defaultVariants: {\n size: '$4',\n },\n})\n\ntype SwitchButtonProps = GetProps<typeof SwitchFrame>\n\nexport type SwitchProps = SwitchButtonProps & {\n labeledBy?: string\n name?: string\n value?: string\n checked?: boolean\n defaultChecked?: boolean\n required?: boolean\n onCheckedChange?(checked: boolean): void\n}\n\nexport const Switch = withStaticProperties(\n SwitchFrame.extractable(\n React.forwardRef<HTMLButtonElement | View, SwitchProps>(\n (props: ScopedProps<SwitchProps, 'Switch'>, forwardedRef) => {\n const {\n __scopeSwitch,\n labeledBy: ariaLabelledby,\n name,\n checked: checkedProp,\n defaultChecked,\n required,\n disabled,\n value = 'on',\n onCheckedChange,\n size = '$4',\n ...switchProps\n } = props\n const [button, setButton] = React.useState<HTMLButtonElement | null>(null)\n const composedRefs = useComposedRefs(forwardedRef, (node) => setButton(node as any))\n const labelId = useLabelContext(button)\n const labelledBy = ariaLabelledby || labelId\n const hasConsumerStoppedPropagationRef = React.useRef(false)\n const [frameLayout, setFrameLayout] = React.useState<LayoutRectangle>()\n // We set this to true by default so that events bubble to forms without JS (SSR)\n const isFormControl = isWeb ? (button ? Boolean(button.closest('form')) : true) : false\n const [checked = false, setChecked] = useControllableState({\n prop: checkedProp,\n defaultProp: defaultChecked || false,\n onChange: onCheckedChange,\n })\n\n if (!isWeb) {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n React.useEffect(() => {\n if (!props.id) return\n return registerFocusable(props.id, {\n focus: () => {\n setChecked((x) => !x)\n },\n })\n }, [props.id, setChecked])\n }\n\n return (\n <SwitchProvider\n scope={__scopeSwitch}\n checked={checked}\n disabled={disabled}\n size={size}\n frameLayout={frameLayout}\n >\n <SwitchFrame\n size={size}\n onLayout={({ nativeEvent }) => {\n console.log('?', nativeEvent.layout)\n setFrameLayout(nativeEvent.layout)\n }}\n // @ts-ignore\n role=\"switch\"\n aria-checked={checked}\n aria-labelledby={labelledBy}\n aria-required={required}\n data-state={getState(checked)}\n data-disabled={disabled ? '' : undefined}\n disabled={disabled}\n theme={checked ? 'active' : null}\n themeShallow\n // @ts-ignore\n tabIndex={disabled ? undefined : 0}\n // @ts-ignore\n value={value}\n {...switchProps}\n ref={composedRefs}\n onPress={(event) => {\n props.onPress?.(event)\n setChecked((prevChecked) => !prevChecked)\n if (isWeb && isFormControl) {\n hasConsumerStoppedPropagationRef.current = event.isPropagationStopped()\n // if switch is in a form, stop propagation from the button so that we only propagate\n // one click event (from the input). We propagate changes from an input so that native\n // form validation works and form events reflect switch updates.\n if (!hasConsumerStoppedPropagationRef.current) event.stopPropagation()\n }\n }}\n />\n {isWeb && isFormControl && (\n <BubbleInput\n control={button}\n bubbles={!hasConsumerStoppedPropagationRef.current}\n name={name}\n value={value}\n checked={checked}\n required={required}\n disabled={disabled}\n // We transform because the input is absolutely positioned but we have\n // rendered it **after** the button. This pulls it back to sit on top\n // of the button.\n style={{ transform: 'translateX(-100%)' }}\n />\n )}\n </SwitchProvider>\n )\n }\n )\n ),\n {\n Thumb: SwitchThumb,\n }\n)\n\n/* ---------------------------------------------------------------------------------------------- */\n\ntype InputProps = any //Radix.ComponentPropsWithoutRef<'input'>\ninterface BubbleInputProps extends Omit<InputProps, 'checked'> {\n checked: boolean\n control: HTMLElement | null\n bubbles: boolean\n}\n\n// TODO make this native friendly\nconst BubbleInput = (props: BubbleInputProps) => {\n const { control, checked, bubbles = true, ...inputProps } = props\n const ref = React.useRef<HTMLInputElement>(null)\n const prevChecked = usePrevious(checked)\n // const controlSize = useSize(control)\n\n // Bubble checked change to parents (e.g form change event)\n React.useEffect(() => {\n const input = ref.current!\n const inputProto = window.HTMLInputElement.prototype\n const descriptor = Object.getOwnPropertyDescriptor(inputProto, 'checked') as PropertyDescriptor\n const setChecked = descriptor.set\n if (prevChecked !== checked && setChecked) {\n const event = new Event('click', { bubbles })\n setChecked.call(input, checked)\n input.dispatchEvent(event)\n }\n }, [prevChecked, checked, bubbles])\n\n return (\n <input\n type=\"checkbox\"\n aria-hidden\n defaultChecked={checked}\n {...inputProps}\n tabIndex={-1}\n ref={ref}\n style={{\n ...props.style,\n // ...controlSize,\n position: 'absolute',\n pointerEvents: 'none',\n opacity: 0,\n margin: 0,\n }}\n />\n )\n}\n\nfunction getState(checked: boolean) {\n return checked ? 'checked' : 'unchecked'\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,193 @@
1
+ import { usePrevious } from "@radix-ui/react-use-previous";
2
+ import { useComposedRefs } from "@tamagui/compose-refs";
3
+ import {
4
+ calc,
5
+ getSize,
6
+ getVariableValue,
7
+ isWeb,
8
+ styled,
9
+ withStaticProperties
10
+ } from "@tamagui/core";
11
+ import { createContextScope } from "@tamagui/create-context";
12
+ import { registerFocusable } from "@tamagui/focusable";
13
+ import { useLabelContext } from "@tamagui/label";
14
+ import { ThemeableStack, XStack } from "@tamagui/stacks";
15
+ import { useControllableState } from "@tamagui/use-controllable-state";
16
+ import * as React from "react";
17
+ import { Animated, PanResponder } from "react-native";
18
+ const SWITCH_NAME = "Switch";
19
+ const getSwitchHeight = (val) => Math.round(getVariableValue(getSize(val)) * 0.65);
20
+ const getSwitchWidth = (val) => getSwitchHeight(val) * 2;
21
+ const scopeContexts = createContextScope(SWITCH_NAME);
22
+ const [createSwitchContext] = scopeContexts;
23
+ const createSwitchScope = scopeContexts[1];
24
+ const [SwitchProvider, useSwitchContext] = createSwitchContext(SWITCH_NAME);
25
+ const THUMB_NAME = "SwitchThumb";
26
+ const SwitchThumbFrame = styled(ThemeableStack, {
27
+ name: "SwitchThumb",
28
+ backgroundColor: "$background",
29
+ borderRadius: 1e3,
30
+ variants: {
31
+ size: {
32
+ "...size": (val) => ({
33
+ height: getSwitchHeight(val),
34
+ width: getSwitchHeight(val)
35
+ })
36
+ }
37
+ },
38
+ defaultVariants: {
39
+ size: "$4"
40
+ }
41
+ });
42
+ const SwitchThumb = SwitchThumbFrame.extractable(React.forwardRef((props, forwardedRef) => {
43
+ const { __scopeSwitch, ...thumbProps } = props;
44
+ const { size, disabled, checked, frameLayout } = useSwitchContext(THUMB_NAME, __scopeSwitch);
45
+ const [position] = React.useState(new Animated.ValueXY(0));
46
+ const [thumbWidth, setThumbWidth] = React.useState(0);
47
+ const responder = React.useMemo(() => {
48
+ return PanResponder.create({
49
+ onStartShouldSetPanResponder: () => true,
50
+ onPanResponderMove: Animated.event([
51
+ null,
52
+ {
53
+ dx: position.x
54
+ }
55
+ ], {
56
+ useNativeDriver: false
57
+ })
58
+ });
59
+ }, []);
60
+ React.useEffect(() => {
61
+ position.addListener((val) => {
62
+ console.log("x", val);
63
+ });
64
+ }, []);
65
+ const maxMove = getVariableValue(getSwitchWidth(size)) - getVariableValue(getSwitchHeight(size));
66
+ const maxDrag = ((frameLayout == null ? void 0 : frameLayout.width) || 0) - thumbWidth;
67
+ return <Animated.View style={[
68
+ {
69
+ transform: [{ translateX: position.x }]
70
+ }
71
+ ]}><SwitchThumbFrame size={size} data-state={getState(checked)} data-disabled={disabled ? "" : void 0} {...responder.panHandlers} {...thumbProps} onLayout={({ nativeEvent }) => {
72
+ console.log("thumb", nativeEvent.layout);
73
+ setThumbWidth(nativeEvent.layout.width);
74
+ }} x={checked ? maxMove : 0} ref={forwardedRef} /></Animated.View>;
75
+ }));
76
+ SwitchThumb.displayName = THUMB_NAME;
77
+ const SwitchFrame = styled(XStack, {
78
+ name: "Switch",
79
+ tag: "button",
80
+ borderRadius: 1e3,
81
+ borderWidth: 2,
82
+ borderColor: "transparent",
83
+ backgroundColor: "$background",
84
+ focusStyle: {
85
+ borderColor: "$borderColorFocus"
86
+ },
87
+ variants: {
88
+ size: {
89
+ "...size": (val) => {
90
+ const height = calc(getSwitchHeight(val), "+", 4);
91
+ const width = calc(getSwitchWidth(val), "+", 4);
92
+ return {
93
+ height,
94
+ minHeight: height,
95
+ width
96
+ };
97
+ }
98
+ }
99
+ },
100
+ defaultVariants: {
101
+ size: "$4"
102
+ }
103
+ });
104
+ const Switch = withStaticProperties(SwitchFrame.extractable(React.forwardRef((props, forwardedRef) => {
105
+ const {
106
+ __scopeSwitch,
107
+ labeledBy: ariaLabelledby,
108
+ name,
109
+ checked: checkedProp,
110
+ defaultChecked,
111
+ required,
112
+ disabled,
113
+ value = "on",
114
+ onCheckedChange,
115
+ size = "$4",
116
+ ...switchProps
117
+ } = props;
118
+ const [button, setButton] = React.useState(null);
119
+ const composedRefs = useComposedRefs(forwardedRef, (node) => setButton(node));
120
+ const labelId = useLabelContext(button);
121
+ const labelledBy = ariaLabelledby || labelId;
122
+ const hasConsumerStoppedPropagationRef = React.useRef(false);
123
+ const [frameLayout, setFrameLayout] = React.useState();
124
+ const isFormControl = isWeb ? button ? Boolean(button.closest("form")) : true : false;
125
+ const [checked = false, setChecked] = useControllableState({
126
+ prop: checkedProp,
127
+ defaultProp: defaultChecked || false,
128
+ onChange: onCheckedChange
129
+ });
130
+ if (!isWeb) {
131
+ React.useEffect(() => {
132
+ if (!props.id)
133
+ return;
134
+ return registerFocusable(props.id, {
135
+ focus: () => {
136
+ setChecked((x) => !x);
137
+ }
138
+ });
139
+ }, [props.id, setChecked]);
140
+ }
141
+ return <SwitchProvider scope={__scopeSwitch} checked={checked} disabled={disabled} size={size} frameLayout={frameLayout}>
142
+ <SwitchFrame size={size} onLayout={({ nativeEvent }) => {
143
+ console.log("?", nativeEvent.layout);
144
+ setFrameLayout(nativeEvent.layout);
145
+ }} role="switch" aria-checked={checked} aria-labelledby={labelledBy} aria-required={required} data-state={getState(checked)} data-disabled={disabled ? "" : void 0} disabled={disabled} theme={checked ? "active" : null} themeShallow tabIndex={disabled ? void 0 : 0} value={value} {...switchProps} ref={composedRefs} onPress={(event) => {
146
+ var _a;
147
+ (_a = props.onPress) == null ? void 0 : _a.call(props, event);
148
+ setChecked((prevChecked) => !prevChecked);
149
+ if (isWeb && isFormControl) {
150
+ hasConsumerStoppedPropagationRef.current = event.isPropagationStopped();
151
+ if (!hasConsumerStoppedPropagationRef.current)
152
+ event.stopPropagation();
153
+ }
154
+ }} />
155
+ {isWeb && isFormControl && <BubbleInput control={button} bubbles={!hasConsumerStoppedPropagationRef.current} name={name} value={value} checked={checked} required={required} disabled={disabled} style={{ transform: "translateX(-100%)" }} />}
156
+ </SwitchProvider>;
157
+ })), {
158
+ Thumb: SwitchThumb
159
+ });
160
+ const BubbleInput = (props) => {
161
+ const { control, checked, bubbles = true, ...inputProps } = props;
162
+ const ref = React.useRef(null);
163
+ const prevChecked = usePrevious(checked);
164
+ React.useEffect(() => {
165
+ const input = ref.current;
166
+ const inputProto = window.HTMLInputElement.prototype;
167
+ const descriptor = Object.getOwnPropertyDescriptor(inputProto, "checked");
168
+ const setChecked = descriptor.set;
169
+ if (prevChecked !== checked && setChecked) {
170
+ const event = new Event("click", { bubbles });
171
+ setChecked.call(input, checked);
172
+ input.dispatchEvent(event);
173
+ }
174
+ }, [prevChecked, checked, bubbles]);
175
+ return <input type="checkbox" aria-hidden defaultChecked={checked} {...inputProps} tabIndex={-1} ref={ref} style={{
176
+ ...props.style,
177
+ position: "absolute",
178
+ pointerEvents: "none",
179
+ opacity: 0,
180
+ margin: 0
181
+ }} />;
182
+ };
183
+ function getState(checked) {
184
+ return checked ? "checked" : "unchecked";
185
+ }
186
+ export {
187
+ Switch,
188
+ SwitchFrame,
189
+ SwitchThumb,
190
+ SwitchThumbFrame,
191
+ createSwitchScope
192
+ };
193
+ //# sourceMappingURL=Switch.draggable-tmp.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/Switch.draggable-tmp.tsx"],
4
+ "sourcesContent": ["// via radix\n// https://github.com/radix-ui/primitives/blob/main/packages/react/switch/src/Switch.tsx\n\nimport { usePrevious } from '@radix-ui/react-use-previous'\nimport { useComposedRefs } from '@tamagui/compose-refs'\nimport {\n GetProps,\n SizeTokens,\n calc,\n getSize,\n getVariableValue,\n isWeb,\n styled,\n withStaticProperties,\n} from '@tamagui/core'\nimport { ScopedProps, createContextScope } from '@tamagui/create-context'\nimport { registerFocusable } from '@tamagui/focusable'\nimport { useLabelContext } from '@tamagui/label'\nimport { ThemeableStack, XStack } from '@tamagui/stacks'\nimport { useControllableState } from '@tamagui/use-controllable-state'\nimport * as React from 'react'\nimport { Animated, LayoutRectangle, PanResponder, View } from 'react-native'\n\nconst SWITCH_NAME = 'Switch'\n\n// TODO make customizable\nconst getSwitchHeight = (val: SizeTokens) => Math.round(getVariableValue(getSize(val)) * 0.65)\nconst getSwitchWidth = (val: SizeTokens) => getSwitchHeight(val) * 2\n\nconst scopeContexts = createContextScope(SWITCH_NAME)\nconst [createSwitchContext] = scopeContexts\nexport const createSwitchScope = scopeContexts[1]\n\nconst [SwitchProvider, useSwitchContext] = createSwitchContext<{\n checked: boolean\n disabled?: boolean\n size: SizeTokens\n frameLayout?: LayoutRectangle\n}>(SWITCH_NAME)\n\n/* -------------------------------------------------------------------------------------------------\n * SwitchThumb\n * -----------------------------------------------------------------------------------------------*/\n\nconst THUMB_NAME = 'SwitchThumb'\n\nexport const SwitchThumbFrame = styled(ThemeableStack, {\n name: 'SwitchThumb',\n backgroundColor: '$background',\n borderRadius: 1000,\n\n variants: {\n size: {\n '...size': (val) => ({\n height: getSwitchHeight(val),\n width: getSwitchHeight(val),\n }),\n },\n },\n\n defaultVariants: {\n size: '$4',\n },\n})\n\nexport type SwitchThumbProps = GetProps<typeof SwitchThumbFrame>\n\nexport const SwitchThumb = SwitchThumbFrame.extractable(\n React.forwardRef<React.ElementRef<'span'>, SwitchThumbProps>(\n (props: ScopedProps<SwitchThumbProps, 'Switch'>, forwardedRef) => {\n const { __scopeSwitch, ...thumbProps } = props\n const { size, disabled, checked, frameLayout } = useSwitchContext(THUMB_NAME, __scopeSwitch)\n const [position] = React.useState(new Animated.ValueXY(0))\n const [thumbWidth, setThumbWidth] = React.useState(0)\n const responder = React.useMemo(() => {\n return PanResponder.create({\n onStartShouldSetPanResponder: () => true,\n onPanResponderMove: Animated.event(\n [\n null,\n {\n dx: position.x,\n },\n ],\n {\n useNativeDriver: false,\n }\n ),\n })\n }, [])\n\n React.useEffect(() => {\n position.addListener((val) => {\n console.log('x', val)\n })\n }, [])\n\n const maxMove =\n getVariableValue(getSwitchWidth(size)) - getVariableValue(getSwitchHeight(size))\n\n const maxDrag = (frameLayout?.width || 0) - thumbWidth\n\n return (\n <Animated.View\n style={[\n {\n transform: [{ translateX: position.x }],\n },\n ]}\n >\n <SwitchThumbFrame\n size={size}\n data-state={getState(checked)}\n data-disabled={disabled ? '' : undefined}\n {...responder.panHandlers}\n {...thumbProps}\n onLayout={({ nativeEvent }) => {\n console.log('thumb', nativeEvent.layout)\n setThumbWidth(nativeEvent.layout.width)\n }}\n x={checked ? maxMove : 0}\n ref={forwardedRef}\n />\n </Animated.View>\n )\n }\n )\n)\n\nSwitchThumb.displayName = THUMB_NAME\n\n/* -------------------------------------------------------------------------------------------------\n * Switch\n * -----------------------------------------------------------------------------------------------*/\n\nexport const SwitchFrame = styled(XStack, {\n name: 'Switch',\n tag: 'button',\n borderRadius: 1000,\n borderWidth: 2,\n borderColor: 'transparent',\n backgroundColor: '$background',\n\n focusStyle: {\n borderColor: '$borderColorFocus',\n },\n\n variants: {\n size: {\n '...size': (val) => {\n const height = calc(getSwitchHeight(val), '+', 4)\n const width = calc(getSwitchWidth(val), '+', 4)\n return {\n height,\n minHeight: height,\n width,\n }\n },\n },\n },\n\n defaultVariants: {\n size: '$4',\n },\n})\n\ntype SwitchButtonProps = GetProps<typeof SwitchFrame>\n\nexport type SwitchProps = SwitchButtonProps & {\n labeledBy?: string\n name?: string\n value?: string\n checked?: boolean\n defaultChecked?: boolean\n required?: boolean\n onCheckedChange?(checked: boolean): void\n}\n\nexport const Switch = withStaticProperties(\n SwitchFrame.extractable(\n React.forwardRef<HTMLButtonElement | View, SwitchProps>(\n (props: ScopedProps<SwitchProps, 'Switch'>, forwardedRef) => {\n const {\n __scopeSwitch,\n labeledBy: ariaLabelledby,\n name,\n checked: checkedProp,\n defaultChecked,\n required,\n disabled,\n value = 'on',\n onCheckedChange,\n size = '$4',\n ...switchProps\n } = props\n const [button, setButton] = React.useState<HTMLButtonElement | null>(null)\n const composedRefs = useComposedRefs(forwardedRef, (node) => setButton(node as any))\n const labelId = useLabelContext(button)\n const labelledBy = ariaLabelledby || labelId\n const hasConsumerStoppedPropagationRef = React.useRef(false)\n const [frameLayout, setFrameLayout] = React.useState<LayoutRectangle>()\n // We set this to true by default so that events bubble to forms without JS (SSR)\n const isFormControl = isWeb ? (button ? Boolean(button.closest('form')) : true) : false\n const [checked = false, setChecked] = useControllableState({\n prop: checkedProp,\n defaultProp: defaultChecked || false,\n onChange: onCheckedChange,\n })\n\n if (!isWeb) {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n React.useEffect(() => {\n if (!props.id) return\n return registerFocusable(props.id, {\n focus: () => {\n setChecked((x) => !x)\n },\n })\n }, [props.id, setChecked])\n }\n\n return (\n <SwitchProvider\n scope={__scopeSwitch}\n checked={checked}\n disabled={disabled}\n size={size}\n frameLayout={frameLayout}\n >\n <SwitchFrame\n size={size}\n onLayout={({ nativeEvent }) => {\n console.log('?', nativeEvent.layout)\n setFrameLayout(nativeEvent.layout)\n }}\n // @ts-ignore\n role=\"switch\"\n aria-checked={checked}\n aria-labelledby={labelledBy}\n aria-required={required}\n data-state={getState(checked)}\n data-disabled={disabled ? '' : undefined}\n disabled={disabled}\n theme={checked ? 'active' : null}\n themeShallow\n // @ts-ignore\n tabIndex={disabled ? undefined : 0}\n // @ts-ignore\n value={value}\n {...switchProps}\n ref={composedRefs}\n onPress={(event) => {\n props.onPress?.(event)\n setChecked((prevChecked) => !prevChecked)\n if (isWeb && isFormControl) {\n hasConsumerStoppedPropagationRef.current = event.isPropagationStopped()\n // if switch is in a form, stop propagation from the button so that we only propagate\n // one click event (from the input). We propagate changes from an input so that native\n // form validation works and form events reflect switch updates.\n if (!hasConsumerStoppedPropagationRef.current) event.stopPropagation()\n }\n }}\n />\n {isWeb && isFormControl && (\n <BubbleInput\n control={button}\n bubbles={!hasConsumerStoppedPropagationRef.current}\n name={name}\n value={value}\n checked={checked}\n required={required}\n disabled={disabled}\n // We transform because the input is absolutely positioned but we have\n // rendered it **after** the button. This pulls it back to sit on top\n // of the button.\n style={{ transform: 'translateX(-100%)' }}\n />\n )}\n </SwitchProvider>\n )\n }\n )\n ),\n {\n Thumb: SwitchThumb,\n }\n)\n\n/* ---------------------------------------------------------------------------------------------- */\n\ntype InputProps = any //Radix.ComponentPropsWithoutRef<'input'>\ninterface BubbleInputProps extends Omit<InputProps, 'checked'> {\n checked: boolean\n control: HTMLElement | null\n bubbles: boolean\n}\n\n// TODO make this native friendly\nconst BubbleInput = (props: BubbleInputProps) => {\n const { control, checked, bubbles = true, ...inputProps } = props\n const ref = React.useRef<HTMLInputElement>(null)\n const prevChecked = usePrevious(checked)\n // const controlSize = useSize(control)\n\n // Bubble checked change to parents (e.g form change event)\n React.useEffect(() => {\n const input = ref.current!\n const inputProto = window.HTMLInputElement.prototype\n const descriptor = Object.getOwnPropertyDescriptor(inputProto, 'checked') as PropertyDescriptor\n const setChecked = descriptor.set\n if (prevChecked !== checked && setChecked) {\n const event = new Event('click', { bubbles })\n setChecked.call(input, checked)\n input.dispatchEvent(event)\n }\n }, [prevChecked, checked, bubbles])\n\n return (\n <input\n type=\"checkbox\"\n aria-hidden\n defaultChecked={checked}\n {...inputProps}\n tabIndex={-1}\n ref={ref}\n style={{\n ...props.style,\n // ...controlSize,\n position: 'absolute',\n pointerEvents: 'none',\n opacity: 0,\n margin: 0,\n }}\n />\n )\n}\n\nfunction getState(checked: boolean) {\n return checked ? 'checked' : 'unchecked'\n}\n"],
5
+ "mappings": "AAGA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA,MAAM,cAAc;AAGpB,MAAM,kBAAkB,CAAC,QAAoB,KAAK,MAAM,iBAAiB,QAAQ,GAAG,CAAC,IAAI,IAAI;AAC7F,MAAM,iBAAiB,CAAC,QAAoB,gBAAgB,GAAG,IAAI;AAEnE,MAAM,gBAAgB,mBAAmB,WAAW;AACpD,MAAM,CAAC,uBAAuB;AACvB,MAAM,oBAAoB,cAAc;AAE/C,MAAM,CAAC,gBAAgB,oBAAoB,oBAKxC,WAAW;AAMd,MAAM,aAAa;AAEZ,MAAM,mBAAmB,OAAO,gBAAgB;AAAA,EACrD,MAAM;AAAA,EACN,iBAAiB;AAAA,EACjB,cAAc;AAAA,EAEd,UAAU;AAAA,IACR,MAAM;AAAA,MACJ,WAAW,CAAC,QAAS;AAAA,QACnB,QAAQ,gBAAgB,GAAG;AAAA,QAC3B,OAAO,gBAAgB,GAAG;AAAA,MAC5B;AAAA,IACF;AAAA,EACF;AAAA,EAEA,iBAAiB;AAAA,IACf,MAAM;AAAA,EACR;AACF,CAAC;AAIM,MAAM,cAAc,iBAAiB,YAC1C,MAAM,WACJ,CAAC,OAAgD,iBAAiB;AAChE,QAAM,EAAE,kBAAkB,eAAe;AACzC,QAAM,EAAE,MAAM,UAAU,SAAS,gBAAgB,iBAAiB,YAAY,aAAa;AAC3F,QAAM,CAAC,YAAY,MAAM,SAAS,IAAI,SAAS,QAAQ,CAAC,CAAC;AACzD,QAAM,CAAC,YAAY,iBAAiB,MAAM,SAAS,CAAC;AACpD,QAAM,YAAY,MAAM,QAAQ,MAAM;AACpC,WAAO,aAAa,OAAO;AAAA,MACzB,8BAA8B,MAAM;AAAA,MACpC,oBAAoB,SAAS,MAC3B;AAAA,QACE;AAAA,QACA;AAAA,UACE,IAAI,SAAS;AAAA,QACf;AAAA,MACF,GACA;AAAA,QACE,iBAAiB;AAAA,MACnB,CACF;AAAA,IACF,CAAC;AAAA,EACH,GAAG,CAAC,CAAC;AAEL,QAAM,UAAU,MAAM;AACpB,aAAS,YAAY,CAAC,QAAQ;AAC5B,cAAQ,IAAI,KAAK,GAAG;AAAA,IACtB,CAAC;AAAA,EACH,GAAG,CAAC,CAAC;AAEL,QAAM,UACJ,iBAAiB,eAAe,IAAI,CAAC,IAAI,iBAAiB,gBAAgB,IAAI,CAAC;AAEjF,QAAM,UAAW,6CAAa,UAAS,KAAK;AAE5C,SACE,CAAC,SAAS,KACR,OAAO;AAAA,IACL;AAAA,MACE,WAAW,CAAC,EAAE,YAAY,SAAS,EAAE,CAAC;AAAA,IACxC;AAAA,EACF,GAEA,CAAC,iBACC,MAAM,MACN,YAAY,SAAS,OAAO,GAC5B,eAAe,WAAW,KAAK,YAC3B,UAAU,iBACV,YACJ,UAAU,CAAC,EAAE,kBAAkB;AAC7B,YAAQ,IAAI,SAAS,YAAY,MAAM;AACvC,kBAAc,YAAY,OAAO,KAAK;AAAA,EACxC,GACA,GAAG,UAAU,UAAU,GACvB,KAAK,cACP,EACF,EApBC,SAAS;AAsBd,CACF,CACF;AAEA,YAAY,cAAc;AAMnB,MAAM,cAAc,OAAO,QAAQ;AAAA,EACxC,MAAM;AAAA,EACN,KAAK;AAAA,EACL,cAAc;AAAA,EACd,aAAa;AAAA,EACb,aAAa;AAAA,EACb,iBAAiB;AAAA,EAEjB,YAAY;AAAA,IACV,aAAa;AAAA,EACf;AAAA,EAEA,UAAU;AAAA,IACR,MAAM;AAAA,MACJ,WAAW,CAAC,QAAQ;AAClB,cAAM,SAAS,KAAK,gBAAgB,GAAG,GAAG,KAAK,CAAC;AAChD,cAAM,QAAQ,KAAK,eAAe,GAAG,GAAG,KAAK,CAAC;AAC9C,eAAO;AAAA,UACL;AAAA,UACA,WAAW;AAAA,UACX;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,iBAAiB;AAAA,IACf,MAAM;AAAA,EACR;AACF,CAAC;AAcM,MAAM,SAAS,qBACpB,YAAY,YACV,MAAM,WACJ,CAAC,OAA2C,iBAAiB;AAC3D,QAAM;AAAA,IACJ;AAAA,IACA,WAAW;AAAA,IACX;AAAA,IACA,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA,OAAO;AAAA,OACJ;AAAA,MACD;AACJ,QAAM,CAAC,QAAQ,aAAa,MAAM,SAAmC,IAAI;AACzE,QAAM,eAAe,gBAAgB,cAAc,CAAC,SAAS,UAAU,IAAW,CAAC;AACnF,QAAM,UAAU,gBAAgB,MAAM;AACtC,QAAM,aAAa,kBAAkB;AACrC,QAAM,mCAAmC,MAAM,OAAO,KAAK;AAC3D,QAAM,CAAC,aAAa,kBAAkB,MAAM,SAA0B;AAEtE,QAAM,gBAAgB,QAAS,SAAS,QAAQ,OAAO,QAAQ,MAAM,CAAC,IAAI,OAAQ;AAClF,QAAM,CAAC,UAAU,OAAO,cAAc,qBAAqB;AAAA,IACzD,MAAM;AAAA,IACN,aAAa,kBAAkB;AAAA,IAC/B,UAAU;AAAA,EACZ,CAAC;AAED,MAAI,CAAC,OAAO;AAEV,UAAM,UAAU,MAAM;AACpB,UAAI,CAAC,MAAM;AAAI;AACf,aAAO,kBAAkB,MAAM,IAAI;AAAA,QACjC,OAAO,MAAM;AACX,qBAAW,CAAC,MAAM,CAAC,CAAC;AAAA,QACtB;AAAA,MACF,CAAC;AAAA,IACH,GAAG,CAAC,MAAM,IAAI,UAAU,CAAC;AAAA,EAC3B;AAEA,SACE,CAAC,eACC,OAAO,eACP,SAAS,SACT,UAAU,UACV,MAAM,MACN,aAAa;AAAA,IAEb,CAAC,YACC,MAAM,MACN,UAAU,CAAC,EAAE,kBAAkB;AAC7B,cAAQ,IAAI,KAAK,YAAY,MAAM;AACnC,qBAAe,YAAY,MAAM;AAAA,IACnC,GAEA,KAAK,SACL,cAAc,SACd,iBAAiB,YACjB,eAAe,UACf,YAAY,SAAS,OAAO,GAC5B,eAAe,WAAW,KAAK,QAC/B,UAAU,UACV,OAAO,UAAU,WAAW,MAC5B,aAEA,UAAU,WAAW,SAAY,GAEjC,OAAO,WACH,aACJ,KAAK,cACL,SAAS,CAAC,UAAU;AA3PlC;AA4PgB,kBAAM,YAAN,+BAAgB;AAChB,iBAAW,CAAC,gBAAgB,CAAC,WAAW;AACxC,UAAI,SAAS,eAAe;AAC1B,yCAAiC,UAAU,MAAM,qBAAqB;AAItE,YAAI,CAAC,iCAAiC;AAAS,gBAAM,gBAAgB;AAAA,MACvE;AAAA,IACF,GACF;AAAA,KACC,SAAS,iBACR,CAAC,YACC,SAAS,QACT,SAAS,CAAC,iCAAiC,SAC3C,MAAM,MACN,OAAO,OACP,SAAS,SACT,UAAU,UACV,UAAU,UAIV,OAAO,EAAE,WAAW,oBAAoB,GAC1C;AAAA,EAEJ,EAxDC;AA0DL,CACF,CACF,GACA;AAAA,EACE,OAAO;AACT,CACF;AAYA,MAAM,cAAc,CAAC,UAA4B;AAC/C,QAAM,EAAE,SAAS,SAAS,UAAU,SAAS,eAAe;AAC5D,QAAM,MAAM,MAAM,OAAyB,IAAI;AAC/C,QAAM,cAAc,YAAY,OAAO;AAIvC,QAAM,UAAU,MAAM;AACpB,UAAM,QAAQ,IAAI;AAClB,UAAM,aAAa,OAAO,iBAAiB;AAC3C,UAAM,aAAa,OAAO,yBAAyB,YAAY,SAAS;AACxE,UAAM,aAAa,WAAW;AAC9B,QAAI,gBAAgB,WAAW,YAAY;AACzC,YAAM,QAAQ,IAAI,MAAM,SAAS,EAAE,QAAQ,CAAC;AAC5C,iBAAW,KAAK,OAAO,OAAO;AAC9B,YAAM,cAAc,KAAK;AAAA,IAC3B;AAAA,EACF,GAAG,CAAC,aAAa,SAAS,OAAO,CAAC;AAElC,SACE,CAAC,MACC,KAAK,WACL,YACA,gBAAgB,aACZ,YACJ,UAAU,IACV,KAAK,KACL,OAAO;AAAA,IACL,GAAG,MAAM;AAAA,IAET,UAAU;AAAA,IACV,eAAe;AAAA,IACf,SAAS;AAAA,IACT,QAAQ;AAAA,EACV,GACF;AAEJ;AAEA,kBAAkB,SAAkB;AAClC,SAAO,UAAU,YAAY;AAC/B;",
6
+ "names": []
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tamagui/switch",
3
- "version": "1.0.1-beta.136",
3
+ "version": "1.0.1-beta.139",
4
4
  "sideEffects": [
5
5
  "*.css"
6
6
  ],
@@ -17,18 +17,20 @@
17
17
  "scripts": {
18
18
  "build": "tamagui-build",
19
19
  "watch": "tamagui-build --watch",
20
+ "lint": "eslint",
21
+ "lint:fix": "yarn lint --fix",
20
22
  "clean": "tamagui-build clean",
21
23
  "clean:build": "tamagui-build clean:build"
22
24
  },
23
25
  "dependencies": {
24
26
  "@radix-ui/react-use-previous": "^0.1.1",
25
- "@tamagui/compose-refs": "^1.0.1-beta.136",
26
- "@tamagui/core": "^1.0.1-beta.136",
27
- "@tamagui/create-context": "^1.0.1-beta.136",
28
- "@tamagui/focusable": "^1.0.1-beta.136",
29
- "@tamagui/label": "^1.0.1-beta.136",
30
- "@tamagui/stacks": "^1.0.1-beta.136",
31
- "@tamagui/use-controllable-state": "^1.0.1-beta.136"
27
+ "@tamagui/compose-refs": "^1.0.1-beta.139",
28
+ "@tamagui/core": "^1.0.1-beta.139",
29
+ "@tamagui/create-context": "^1.0.1-beta.139",
30
+ "@tamagui/focusable": "^1.0.1-beta.139",
31
+ "@tamagui/label": "^1.0.1-beta.139",
32
+ "@tamagui/stacks": "^1.0.1-beta.139",
33
+ "@tamagui/use-controllable-state": "^1.0.1-beta.139"
32
34
  },
33
35
  "peerDependencies": {
34
36
  "react": "*",
@@ -36,7 +38,7 @@
36
38
  "react-native": "*"
37
39
  },
38
40
  "devDependencies": {
39
- "@tamagui/build": "^1.0.1-beta.136",
41
+ "@tamagui/build": "^1.0.1-beta.139",
40
42
  "@types/react-native": "^0.69.2",
41
43
  "react": "*",
42
44
  "react-dom": "*",
@@ -0,0 +1,340 @@
1
+ // via radix
2
+ // https://github.com/radix-ui/primitives/blob/main/packages/react/switch/src/Switch.tsx
3
+
4
+ import { usePrevious } from '@radix-ui/react-use-previous'
5
+ import { useComposedRefs } from '@tamagui/compose-refs'
6
+ import {
7
+ GetProps,
8
+ SizeTokens,
9
+ calc,
10
+ getSize,
11
+ getVariableValue,
12
+ isWeb,
13
+ styled,
14
+ withStaticProperties,
15
+ } from '@tamagui/core'
16
+ import { ScopedProps, createContextScope } from '@tamagui/create-context'
17
+ import { registerFocusable } from '@tamagui/focusable'
18
+ import { useLabelContext } from '@tamagui/label'
19
+ import { ThemeableStack, XStack } from '@tamagui/stacks'
20
+ import { useControllableState } from '@tamagui/use-controllable-state'
21
+ import * as React from 'react'
22
+ import { Animated, LayoutRectangle, PanResponder, View } from 'react-native'
23
+
24
+ const SWITCH_NAME = 'Switch'
25
+
26
+ // TODO make customizable
27
+ const getSwitchHeight = (val: SizeTokens) => Math.round(getVariableValue(getSize(val)) * 0.65)
28
+ const getSwitchWidth = (val: SizeTokens) => getSwitchHeight(val) * 2
29
+
30
+ const scopeContexts = createContextScope(SWITCH_NAME)
31
+ const [createSwitchContext] = scopeContexts
32
+ export const createSwitchScope = scopeContexts[1]
33
+
34
+ const [SwitchProvider, useSwitchContext] = createSwitchContext<{
35
+ checked: boolean
36
+ disabled?: boolean
37
+ size: SizeTokens
38
+ frameLayout?: LayoutRectangle
39
+ }>(SWITCH_NAME)
40
+
41
+ /* -------------------------------------------------------------------------------------------------
42
+ * SwitchThumb
43
+ * -----------------------------------------------------------------------------------------------*/
44
+
45
+ const THUMB_NAME = 'SwitchThumb'
46
+
47
+ export const SwitchThumbFrame = styled(ThemeableStack, {
48
+ name: 'SwitchThumb',
49
+ backgroundColor: '$background',
50
+ borderRadius: 1000,
51
+
52
+ variants: {
53
+ size: {
54
+ '...size': (val) => ({
55
+ height: getSwitchHeight(val),
56
+ width: getSwitchHeight(val),
57
+ }),
58
+ },
59
+ },
60
+
61
+ defaultVariants: {
62
+ size: '$4',
63
+ },
64
+ })
65
+
66
+ export type SwitchThumbProps = GetProps<typeof SwitchThumbFrame>
67
+
68
+ export const SwitchThumb = SwitchThumbFrame.extractable(
69
+ React.forwardRef<React.ElementRef<'span'>, SwitchThumbProps>(
70
+ (props: ScopedProps<SwitchThumbProps, 'Switch'>, forwardedRef) => {
71
+ const { __scopeSwitch, ...thumbProps } = props
72
+ const { size, disabled, checked, frameLayout } = useSwitchContext(THUMB_NAME, __scopeSwitch)
73
+ const [position] = React.useState(new Animated.ValueXY(0))
74
+ const [thumbWidth, setThumbWidth] = React.useState(0)
75
+ const responder = React.useMemo(() => {
76
+ return PanResponder.create({
77
+ onStartShouldSetPanResponder: () => true,
78
+ onPanResponderMove: Animated.event(
79
+ [
80
+ null,
81
+ {
82
+ dx: position.x,
83
+ },
84
+ ],
85
+ {
86
+ useNativeDriver: false,
87
+ }
88
+ ),
89
+ })
90
+ }, [])
91
+
92
+ React.useEffect(() => {
93
+ position.addListener((val) => {
94
+ console.log('x', val)
95
+ })
96
+ }, [])
97
+
98
+ const maxMove =
99
+ getVariableValue(getSwitchWidth(size)) - getVariableValue(getSwitchHeight(size))
100
+
101
+ const maxDrag = (frameLayout?.width || 0) - thumbWidth
102
+
103
+ return (
104
+ <Animated.View
105
+ style={[
106
+ {
107
+ transform: [{ translateX: position.x }],
108
+ },
109
+ ]}
110
+ >
111
+ <SwitchThumbFrame
112
+ size={size}
113
+ data-state={getState(checked)}
114
+ data-disabled={disabled ? '' : undefined}
115
+ {...responder.panHandlers}
116
+ {...thumbProps}
117
+ onLayout={({ nativeEvent }) => {
118
+ console.log('thumb', nativeEvent.layout)
119
+ setThumbWidth(nativeEvent.layout.width)
120
+ }}
121
+ x={checked ? maxMove : 0}
122
+ ref={forwardedRef}
123
+ />
124
+ </Animated.View>
125
+ )
126
+ }
127
+ )
128
+ )
129
+
130
+ SwitchThumb.displayName = THUMB_NAME
131
+
132
+ /* -------------------------------------------------------------------------------------------------
133
+ * Switch
134
+ * -----------------------------------------------------------------------------------------------*/
135
+
136
+ export const SwitchFrame = styled(XStack, {
137
+ name: 'Switch',
138
+ tag: 'button',
139
+ borderRadius: 1000,
140
+ borderWidth: 2,
141
+ borderColor: 'transparent',
142
+ backgroundColor: '$background',
143
+
144
+ focusStyle: {
145
+ borderColor: '$borderColorFocus',
146
+ },
147
+
148
+ variants: {
149
+ size: {
150
+ '...size': (val) => {
151
+ const height = calc(getSwitchHeight(val), '+', 4)
152
+ const width = calc(getSwitchWidth(val), '+', 4)
153
+ return {
154
+ height,
155
+ minHeight: height,
156
+ width,
157
+ }
158
+ },
159
+ },
160
+ },
161
+
162
+ defaultVariants: {
163
+ size: '$4',
164
+ },
165
+ })
166
+
167
+ type SwitchButtonProps = GetProps<typeof SwitchFrame>
168
+
169
+ export type SwitchProps = SwitchButtonProps & {
170
+ labeledBy?: string
171
+ name?: string
172
+ value?: string
173
+ checked?: boolean
174
+ defaultChecked?: boolean
175
+ required?: boolean
176
+ onCheckedChange?(checked: boolean): void
177
+ }
178
+
179
+ export const Switch = withStaticProperties(
180
+ SwitchFrame.extractable(
181
+ React.forwardRef<HTMLButtonElement | View, SwitchProps>(
182
+ (props: ScopedProps<SwitchProps, 'Switch'>, forwardedRef) => {
183
+ const {
184
+ __scopeSwitch,
185
+ labeledBy: ariaLabelledby,
186
+ name,
187
+ checked: checkedProp,
188
+ defaultChecked,
189
+ required,
190
+ disabled,
191
+ value = 'on',
192
+ onCheckedChange,
193
+ size = '$4',
194
+ ...switchProps
195
+ } = props
196
+ const [button, setButton] = React.useState<HTMLButtonElement | null>(null)
197
+ const composedRefs = useComposedRefs(forwardedRef, (node) => setButton(node as any))
198
+ const labelId = useLabelContext(button)
199
+ const labelledBy = ariaLabelledby || labelId
200
+ const hasConsumerStoppedPropagationRef = React.useRef(false)
201
+ const [frameLayout, setFrameLayout] = React.useState<LayoutRectangle>()
202
+ // We set this to true by default so that events bubble to forms without JS (SSR)
203
+ const isFormControl = isWeb ? (button ? Boolean(button.closest('form')) : true) : false
204
+ const [checked = false, setChecked] = useControllableState({
205
+ prop: checkedProp,
206
+ defaultProp: defaultChecked || false,
207
+ onChange: onCheckedChange,
208
+ })
209
+
210
+ if (!isWeb) {
211
+ // eslint-disable-next-line react-hooks/rules-of-hooks
212
+ React.useEffect(() => {
213
+ if (!props.id) return
214
+ return registerFocusable(props.id, {
215
+ focus: () => {
216
+ setChecked((x) => !x)
217
+ },
218
+ })
219
+ }, [props.id, setChecked])
220
+ }
221
+
222
+ return (
223
+ <SwitchProvider
224
+ scope={__scopeSwitch}
225
+ checked={checked}
226
+ disabled={disabled}
227
+ size={size}
228
+ frameLayout={frameLayout}
229
+ >
230
+ <SwitchFrame
231
+ size={size}
232
+ onLayout={({ nativeEvent }) => {
233
+ console.log('?', nativeEvent.layout)
234
+ setFrameLayout(nativeEvent.layout)
235
+ }}
236
+ // @ts-ignore
237
+ role="switch"
238
+ aria-checked={checked}
239
+ aria-labelledby={labelledBy}
240
+ aria-required={required}
241
+ data-state={getState(checked)}
242
+ data-disabled={disabled ? '' : undefined}
243
+ disabled={disabled}
244
+ theme={checked ? 'active' : null}
245
+ themeShallow
246
+ // @ts-ignore
247
+ tabIndex={disabled ? undefined : 0}
248
+ // @ts-ignore
249
+ value={value}
250
+ {...switchProps}
251
+ ref={composedRefs}
252
+ onPress={(event) => {
253
+ props.onPress?.(event)
254
+ setChecked((prevChecked) => !prevChecked)
255
+ if (isWeb && isFormControl) {
256
+ hasConsumerStoppedPropagationRef.current = event.isPropagationStopped()
257
+ // if switch is in a form, stop propagation from the button so that we only propagate
258
+ // one click event (from the input). We propagate changes from an input so that native
259
+ // form validation works and form events reflect switch updates.
260
+ if (!hasConsumerStoppedPropagationRef.current) event.stopPropagation()
261
+ }
262
+ }}
263
+ />
264
+ {isWeb && isFormControl && (
265
+ <BubbleInput
266
+ control={button}
267
+ bubbles={!hasConsumerStoppedPropagationRef.current}
268
+ name={name}
269
+ value={value}
270
+ checked={checked}
271
+ required={required}
272
+ disabled={disabled}
273
+ // We transform because the input is absolutely positioned but we have
274
+ // rendered it **after** the button. This pulls it back to sit on top
275
+ // of the button.
276
+ style={{ transform: 'translateX(-100%)' }}
277
+ />
278
+ )}
279
+ </SwitchProvider>
280
+ )
281
+ }
282
+ )
283
+ ),
284
+ {
285
+ Thumb: SwitchThumb,
286
+ }
287
+ )
288
+
289
+ /* ---------------------------------------------------------------------------------------------- */
290
+
291
+ type InputProps = any //Radix.ComponentPropsWithoutRef<'input'>
292
+ interface BubbleInputProps extends Omit<InputProps, 'checked'> {
293
+ checked: boolean
294
+ control: HTMLElement | null
295
+ bubbles: boolean
296
+ }
297
+
298
+ // TODO make this native friendly
299
+ const BubbleInput = (props: BubbleInputProps) => {
300
+ const { control, checked, bubbles = true, ...inputProps } = props
301
+ const ref = React.useRef<HTMLInputElement>(null)
302
+ const prevChecked = usePrevious(checked)
303
+ // const controlSize = useSize(control)
304
+
305
+ // Bubble checked change to parents (e.g form change event)
306
+ React.useEffect(() => {
307
+ const input = ref.current!
308
+ const inputProto = window.HTMLInputElement.prototype
309
+ const descriptor = Object.getOwnPropertyDescriptor(inputProto, 'checked') as PropertyDescriptor
310
+ const setChecked = descriptor.set
311
+ if (prevChecked !== checked && setChecked) {
312
+ const event = new Event('click', { bubbles })
313
+ setChecked.call(input, checked)
314
+ input.dispatchEvent(event)
315
+ }
316
+ }, [prevChecked, checked, bubbles])
317
+
318
+ return (
319
+ <input
320
+ type="checkbox"
321
+ aria-hidden
322
+ defaultChecked={checked}
323
+ {...inputProps}
324
+ tabIndex={-1}
325
+ ref={ref}
326
+ style={{
327
+ ...props.style,
328
+ // ...controlSize,
329
+ position: 'absolute',
330
+ pointerEvents: 'none',
331
+ opacity: 0,
332
+ margin: 0,
333
+ }}
334
+ />
335
+ )
336
+ }
337
+
338
+ function getState(checked: boolean) {
339
+ return checked ? 'checked' : 'unchecked'
340
+ }