@bind-ts/react-bind 0.0.1

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,22 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const d=require("react"),oe=require("@bind/core"),W=require("@tanstack/react-store");var h={exports:{}},R={};/**
2
+ * @license React
3
+ * react-jsx-runtime.production.js
4
+ *
5
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
6
+ *
7
+ * This source code is licensed under the MIT license found in the
8
+ * LICENSE file in the root directory of this source tree.
9
+ */var M;function ae(){if(M)return R;M=1;var a=Symbol.for("react.transitional.element"),n=Symbol.for("react.fragment");function l(i,t,u){var f=null;if(u!==void 0&&(f=""+u),t.key!==void 0&&(f=""+t.key),"key"in t){u={};for(var E in t)E!=="key"&&(u[E]=t[E])}else u=t;return t=u.ref,{$$typeof:a,type:i,key:f,ref:t!==void 0?t:null,props:u}}return R.Fragment=n,R.jsx=l,R.jsxs=l,R}var x={};/**
10
+ * @license React
11
+ * react-jsx-runtime.development.js
12
+ *
13
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
14
+ *
15
+ * This source code is licensed under the MIT license found in the
16
+ * LICENSE file in the root directory of this source tree.
17
+ */var L;function ue(){return L||(L=1,process.env.NODE_ENV!=="production"&&(function(){function a(e){if(e==null)return null;if(typeof e=="function")return e.$$typeof===re?null:e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case j:return"Fragment";case z:return"Profiler";case J:return"StrictMode";case Z:return"Suspense";case Q:return"SuspenseList";case ee:return"Activity"}if(typeof e=="object")switch(typeof e.tag=="number"&&console.error("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."),e.$$typeof){case V:return"Portal";case H:return e.displayName||"Context";case G:return(e._context.displayName||"Context")+".Consumer";case X:var r=e.render;return e=e.displayName,e||(e=r.displayName||r.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case K:return r=e.displayName||null,r!==null?r:a(e.type)||"Memo";case y:r=e._payload,e=e._init;try{return a(e(r))}catch{}}return null}function n(e){return""+e}function l(e){try{n(e);var r=!1}catch{r=!0}if(r){r=console;var o=r.error,s=typeof Symbol=="function"&&Symbol.toStringTag&&e[Symbol.toStringTag]||e.constructor.name||"Object";return o.call(r,"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",s),n(e)}}function i(e){if(e===j)return"<>";if(typeof e=="object"&&e!==null&&e.$$typeof===y)return"<...>";try{var r=a(e);return r?"<"+r+">":"<...>"}catch{return"<...>"}}function t(){var e=O.A;return e===null?null:e.getOwner()}function u(){return Error("react-stack-top-frame")}function f(e){if(N.call(e,"key")){var r=Object.getOwnPropertyDescriptor(e,"key").get;if(r&&r.isReactWarning)return!1}return e.key!==void 0}function E(e,r){function o(){Y||(Y=!0,console.error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",r))}o.isReactWarning=!0,Object.defineProperty(e,"key",{get:o,configurable:!0})}function T(){var e=a(this.type);return F[e]||(F[e]=!0,console.error("Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release.")),e=this.props.ref,e!==void 0?e:null}function k(e,r,o,s,S,g){var c=o.ref;return e={$$typeof:A,type:e,key:r,props:o,_owner:s},(c!==void 0?c:null)!==null?Object.defineProperty(e,"ref",{enumerable:!1,get:T}):Object.defineProperty(e,"ref",{enumerable:!1,value:null}),e._store={},Object.defineProperty(e._store,"validated",{configurable:!1,enumerable:!1,writable:!0,value:0}),Object.defineProperty(e,"_debugInfo",{configurable:!1,enumerable:!1,writable:!0,value:null}),Object.defineProperty(e,"_debugStack",{configurable:!1,enumerable:!1,writable:!0,value:S}),Object.defineProperty(e,"_debugTask",{configurable:!1,enumerable:!1,writable:!0,value:g}),Object.freeze&&(Object.freeze(e.props),Object.freeze(e)),e}function w(e,r,o,s,S,g){var c=r.children;if(c!==void 0)if(s)if(te(c)){for(s=0;s<c.length;s++)p(c[s]);Object.freeze&&Object.freeze(c)}else console.error("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");else p(c);if(N.call(r,"key")){c=a(e);var _=Object.keys(r).filter(function(ne){return ne!=="key"});s=0<_.length?"{key: someKey, "+_.join(": ..., ")+": ...}":"{key: someKey}",I[c+s]||(_=0<_.length?"{"+_.join(": ..., ")+": ...}":"{}",console.error(`A props object containing a "key" prop is being spread into JSX:
18
+ let props = %s;
19
+ <%s {...props} />
20
+ React keys must be passed directly to JSX without using spread:
21
+ let props = %s;
22
+ <%s key={someKey} {...props} />`,s,c,_,c),I[c+s]=!0)}if(c=null,o!==void 0&&(l(o),c=""+o),f(r)&&(l(r.key),c=""+r.key),"key"in r){o={};for(var C in r)C!=="key"&&(o[C]=r[C])}else o=r;return c&&E(o,typeof e=="function"?e.displayName||e.name||"Unknown":e),k(e,c,o,t(),S,g)}function p(e){v(e)?e._store&&(e._store.validated=1):typeof e=="object"&&e!==null&&e.$$typeof===y&&(e._payload.status==="fulfilled"?v(e._payload.value)&&e._payload.value._store&&(e._payload.value._store.validated=1):e._store&&(e._store.validated=1))}function v(e){return typeof e=="object"&&e!==null&&e.$$typeof===A}var b=d,A=Symbol.for("react.transitional.element"),V=Symbol.for("react.portal"),j=Symbol.for("react.fragment"),J=Symbol.for("react.strict_mode"),z=Symbol.for("react.profiler"),G=Symbol.for("react.consumer"),H=Symbol.for("react.context"),X=Symbol.for("react.forward_ref"),Z=Symbol.for("react.suspense"),Q=Symbol.for("react.suspense_list"),K=Symbol.for("react.memo"),y=Symbol.for("react.lazy"),ee=Symbol.for("react.activity"),re=Symbol.for("react.client.reference"),O=b.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,N=Object.prototype.hasOwnProperty,te=Array.isArray,P=console.createTask?console.createTask:function(){return null};b={react_stack_bottom_frame:function(e){return e()}};var Y,F={},$=b.react_stack_bottom_frame.bind(b,u)(),B=P(i(u)),I={};x.Fragment=j,x.jsx=function(e,r,o){var s=1e4>O.recentlyCreatedOwnerStacks++;return w(e,r,o,!1,s?Error("react-stack-top-frame"):$,s?P(i(e)):B)},x.jsxs=function(e,r,o){var s=1e4>O.recentlyCreatedOwnerStacks++;return w(e,r,o,!0,s?Error("react-stack-top-frame"):$,s?P(i(e)):B)}})()),x}var D;function se(){return D||(D=1,process.env.NODE_ENV==="production"?h.exports=ae():h.exports=ue()),h.exports}var m=se();const q=typeof window<"u"?d.useLayoutEffect:d.useEffect;function ce({bindApi:a,selector:n,children:l}){const i=W.useStore(a.store,n);return typeof l=="function"?m.jsx(m.Fragment,{children:l(i)}):m.jsx(m.Fragment,{children:l})}function le({bindApi:a,value:n,children:l}){const i=W.useStore(a.store,u=>u),t={state:{value:n},meta:{isActive:i.value===n},handleChange:()=>a.setValue(n)};return m.jsx(m.Fragment,{children:l(t)})}function U(a){const[n]=d.useState(()=>new oe.BindApi(a)),l=d.useMemo(()=>{const i=n;return i.Element=function(u){return m.jsx(le,{bindApi:n,value:u.value,children:u.children})},i.Subscribe=function(u){const f=u.selector??(E=>E);return m.jsx(ce,{bindApi:n,selector:f,children:u.children})},i},[n]);return q(n.mount,[]),q(()=>{n.update(a)}),l}d.createContext(null);d.createContext(null);function ie(){const a=d.createContext(null),n=d.createContext(null);function l(){const t=d.useContext(a);if(!t)throw new Error("`useElementContext` must be used within an `Element` component created by `createBindHook`");return t}function i(){const t=d.useContext(n);if(!t)throw new Error("`useBindContext` must be used within an `AppBind` component created by `createBindHook`");return t}return{elementContext:a,bindContext:n,useElementContext:l,useBindContext:i}}function fe({elementContext:a,bindContext:n,components:l}){function i(t,u){const f=U(u),E=l[t],T=d.useMemo(()=>({children:p})=>m.jsx(n.Provider,{value:f,children:p}),[f]),k=d.useMemo(()=>function(v){return m.jsx(f.Element,{value:v.value,children:b=>{const A={...b,...E};return m.jsx(a.Provider,{value:b,children:v.children(A)})}})},[f,E]);return d.useMemo(()=>Object.assign({},f,{Element:k,AppBind:T}),[f,k,T])}return{useAppBind:i}}exports.createBindContexts=ie;exports.createBindHook=fe;exports.useBind=U;
@@ -0,0 +1,112 @@
1
+ import type { BindOptions } from "@bind/bind-core";
2
+ import type { ComponentType, Context, FunctionComponent, PropsWithChildren, ReactNode } from "react";
3
+ import type { ReactBindExtendedApi } from "./useBind";
4
+ import type { ElementContext } from "./types";
5
+ /**
6
+ * Creates the contexts and hooks needed for bind composition.
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * export const { bindContext, elementContext, useElementContext, useBindContext } =
11
+ * createBindContexts();
12
+ * ```
13
+ */
14
+ export declare function createBindContexts(): {
15
+ elementContext: Context<ElementContext<readonly string[]>>;
16
+ bindContext: Context<ReactBindExtendedApi<readonly string[]>>;
17
+ useElementContext: <TValues extends readonly string[] = readonly string[]>() => ElementContext<TValues>;
18
+ useBindContext: <TValues extends readonly string[] = readonly string[]>() => ReactBindExtendedApi<TValues>;
19
+ };
20
+ /**
21
+ * A record of component groups, where each key is a group name
22
+ * and the value is a record of components in that group.
23
+ */
24
+ type ComponentGroups = Record<string, Record<string, ComponentType<any>>>;
25
+ /**
26
+ * Props for the createBindHook factory function.
27
+ */
28
+ interface CreateBindHookProps<TComponentGroups extends ComponentGroups> {
29
+ elementContext: Context<ElementContext<readonly string[]>>;
30
+ bindContext: Context<ReactBindExtendedApi<readonly string[]>>;
31
+ components: TComponentGroups;
32
+ }
33
+ /**
34
+ * Context passed to Element children, including the element render context
35
+ * and the selected component group.
36
+ */
37
+ export type AppElementContext<TValues extends readonly string[], TComponents extends Record<string, ComponentType<any>>> = ElementContext<TValues> & TComponents;
38
+ /**
39
+ * Props for the Element component in composition API.
40
+ */
41
+ export interface AppElementProps<TValues extends readonly string[], TComponents extends Record<string, ComponentType<any>>> {
42
+ /** The value this element represents */
43
+ value: TValues[number];
44
+ /** Render prop receiving the element context with components */
45
+ children: (context: AppElementContext<TValues, TComponents>) => ReactNode;
46
+ }
47
+ /**
48
+ * Extended bind API with composition support.
49
+ */
50
+ export type AppBindExtendedApi<TValues extends readonly string[], TComponents extends Record<string, ComponentType<any>>> = Omit<ReactBindExtendedApi<TValues>, "Element"> & {
51
+ /**
52
+ * An element component that provides both element context and the selected component group.
53
+ * Use this to render pre-bound custom components.
54
+ */
55
+ Element: (props: AppElementProps<TValues, TComponents>) => ReturnType<FunctionComponent>;
56
+ /**
57
+ * A wrapper component that provides bind context to children.
58
+ * Use this when you need access to the bind API in deeply nested components.
59
+ */
60
+ AppBind: ComponentType<PropsWithChildren<{}>>;
61
+ };
62
+ /**
63
+ * Creates a custom bind hook factory with pre-bound component groups.
64
+ *
65
+ * @example
66
+ * ```tsx
67
+ * const { elementContext, bindContext, useElementContext } = createBindContexts();
68
+ *
69
+ * function Tab({ children }: { children: React.ReactNode }) {
70
+ * const element = useElementContext();
71
+ * return <button onClick={element.handleChange}>{children}</button>;
72
+ * }
73
+ *
74
+ * function TabPanel({ children }: { children: React.ReactNode }) {
75
+ * const element = useElementContext();
76
+ * if (!element.meta.isActive) return null;
77
+ * return <div>{children}</div>;
78
+ * }
79
+ *
80
+ * const { useAppBind } = createBindHook({
81
+ * elementContext,
82
+ * bindContext,
83
+ * components: {
84
+ * Tab: { Tab, TabPanel },
85
+ * Wizard: { Step, Button },
86
+ * },
87
+ * });
88
+ *
89
+ * function MyTabs() {
90
+ * const tabs = useAppBind('Tab', {
91
+ * defaultValue: 'tab1',
92
+ * values: ['tab1', 'tab2'] as const,
93
+ * });
94
+ *
95
+ * return (
96
+ * <div>
97
+ * <tabs.Element value="tab1">
98
+ * {(bindApi) => <bindApi.Tab>Tab 1</bindApi.Tab>}
99
+ * </tabs.Element>
100
+ * <tabs.Element value="tab1">
101
+ * {(bindApi) => <bindApi.TabPanel>Panel 1</bindApi.TabPanel>}
102
+ * </tabs.Element>
103
+ * </div>
104
+ * );
105
+ * }
106
+ * ```
107
+ */
108
+ export declare function createBindHook<const TComponentGroups extends ComponentGroups>({ elementContext, bindContext, components, }: CreateBindHookProps<TComponentGroups>): {
109
+ useAppBind: <TGroupKey extends keyof TComponentGroups, TValues extends readonly string[]>(groupKey: TGroupKey, opts: BindOptions<TValues>) => AppBindExtendedApi<TValues, TComponentGroups[TGroupKey]>;
110
+ };
111
+ export {};
112
+ //# sourceMappingURL=createBindHook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createBindHook.d.ts","sourceRoot":"","sources":["../../src/createBindHook.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAa,MAAM,iBAAiB,CAAC;AAC9D,OAAO,KAAK,EACX,aAAa,EACb,OAAO,EACP,iBAAiB,EACjB,iBAAiB,EACjB,SAAS,EACT,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAc9C;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB;;;wBAKN,OAAO,SAAS,SAAS,MAAM,EAAE,2BASzC,cAAc,CAAC,OAAO,CAAC;qBAGlB,OAAO,SAAS,SAAS,MAAM,EAAE,2BAS3B,oBAAoB,CAAC,OAAO,CAAC;EAS3D;AAED;;;GAGG;AACH,KAAK,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AAE1E;;GAEG;AACH,UAAU,mBAAmB,CAAC,gBAAgB,SAAS,eAAe;IACrE,cAAc,EAAE,OAAO,CAAC,cAAc,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;IAC3D,WAAW,EAAE,OAAO,CAAC,oBAAoB,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;IAC9D,UAAU,EAAE,gBAAgB,CAAC;CAC7B;AAED;;;GAGG;AACH,MAAM,MAAM,iBAAiB,CAC5B,OAAO,SAAS,SAAS,MAAM,EAAE,EACjC,WAAW,SAAS,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC,IACnD,cAAc,CAAC,OAAO,CAAC,GAAG,WAAW,CAAC;AAE1C;;GAEG;AACH,MAAM,WAAW,eAAe,CAC/B,OAAO,SAAS,SAAS,MAAM,EAAE,EACjC,WAAW,SAAS,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC;IAEtD,wCAAwC;IACxC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IACvB,gEAAgE;IAChE,QAAQ,EAAE,CAAC,OAAO,EAAE,iBAAiB,CAAC,OAAO,EAAE,WAAW,CAAC,KAAK,SAAS,CAAC;CAC1E;AAED;;GAEG;AACH,MAAM,MAAM,kBAAkB,CAC7B,OAAO,SAAS,SAAS,MAAM,EAAE,EACjC,WAAW,SAAS,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC,IACnD,IAAI,CAAC,oBAAoB,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC,GAAG;IACpD;;;OAGG;IACH,OAAO,EAAE,CAAC,KAAK,EAAE,eAAe,CAAC,OAAO,EAAE,WAAW,CAAC,KAAK,UAAU,CAAC,iBAAiB,CAAC,CAAC;IAEzF;;;OAGG;IACH,OAAO,EAAE,aAAa,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC,CAAC;CAC9C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,wBAAgB,cAAc,CAAC,KAAK,CAAC,gBAAgB,SAAS,eAAe,EAAE,EAC9E,cAAc,EACd,WAAW,EACX,UAAU,GACV,EAAE,mBAAmB,CAAC,gBAAgB,CAAC;iBAEtC,SAAS,SAAS,MAAM,gBAAgB,EACxC,OAAO,SAAS,SAAS,MAAM,EAAE,YAEvB,SAAS,QACb,WAAW,CAAC,OAAO,CAAC,KACxB,kBAAkB,CAAC,OAAO,EAAE,gBAAgB,CAAC,SAAS,CAAC,CAAC;EAqD3D"}
@@ -0,0 +1,7 @@
1
+ export { useBind } from "./useBind";
2
+ export type { ReactBindApi, ReactBindExtendedApi } from "./useBind";
3
+ export type { ElementContext, ElementState, ElementMeta, ElementProps, SubscribeProps, } from "./types";
4
+ export { createBindContexts, createBindHook } from "./createBindHook";
5
+ export type { AppElementContext, AppElementProps, AppBindExtendedApi } from "./createBindHook";
6
+ export type { BindOptions, BindState } from "@bind/core";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,YAAY,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACpE,YAAY,EACX,cAAc,EACd,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,cAAc,GACd,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACtE,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAG/F,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,388 @@
1
+ import ae, { useLayoutEffect as ue, useEffect as se, useState as ce, useMemo as w, createContext as S, useContext as D } from "react";
2
+ import { BindApi as le } from "@bind/core";
3
+ import { useStore as q } from "@tanstack/react-store";
4
+ var h = { exports: {} }, v = {};
5
+ /**
6
+ * @license React
7
+ * react-jsx-runtime.production.js
8
+ *
9
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
10
+ *
11
+ * This source code is licensed under the MIT license found in the
12
+ * LICENSE file in the root directory of this source tree.
13
+ */
14
+ var M;
15
+ function ie() {
16
+ if (M) return v;
17
+ M = 1;
18
+ var a = Symbol.for("react.transitional.element"), n = Symbol.for("react.fragment");
19
+ function l(i, t, u) {
20
+ var f = null;
21
+ if (u !== void 0 && (f = "" + u), t.key !== void 0 && (f = "" + t.key), "key" in t) {
22
+ u = {};
23
+ for (var m in t)
24
+ m !== "key" && (u[m] = t[m]);
25
+ } else u = t;
26
+ return t = u.ref, {
27
+ $$typeof: a,
28
+ type: i,
29
+ key: f,
30
+ ref: t !== void 0 ? t : null,
31
+ props: u
32
+ };
33
+ }
34
+ return v.Fragment = n, v.jsx = l, v.jsxs = l, v;
35
+ }
36
+ var R = {};
37
+ /**
38
+ * @license React
39
+ * react-jsx-runtime.development.js
40
+ *
41
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
42
+ *
43
+ * This source code is licensed under the MIT license found in the
44
+ * LICENSE file in the root directory of this source tree.
45
+ */
46
+ var W;
47
+ function fe() {
48
+ return W || (W = 1, process.env.NODE_ENV !== "production" && (function() {
49
+ function a(e) {
50
+ if (e == null) return null;
51
+ if (typeof e == "function")
52
+ return e.$$typeof === te ? null : e.displayName || e.name || null;
53
+ if (typeof e == "string") return e;
54
+ switch (e) {
55
+ case O:
56
+ return "Fragment";
57
+ case G:
58
+ return "Profiler";
59
+ case z:
60
+ return "StrictMode";
61
+ case Q:
62
+ return "Suspense";
63
+ case K:
64
+ return "SuspenseList";
65
+ case re:
66
+ return "Activity";
67
+ }
68
+ if (typeof e == "object")
69
+ switch (typeof e.tag == "number" && console.error(
70
+ "Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."
71
+ ), e.$$typeof) {
72
+ case J:
73
+ return "Portal";
74
+ case X:
75
+ return e.displayName || "Context";
76
+ case H:
77
+ return (e._context.displayName || "Context") + ".Consumer";
78
+ case Z:
79
+ var r = e.render;
80
+ return e = e.displayName, e || (e = r.displayName || r.name || "", e = e !== "" ? "ForwardRef(" + e + ")" : "ForwardRef"), e;
81
+ case ee:
82
+ return r = e.displayName || null, r !== null ? r : a(e.type) || "Memo";
83
+ case y:
84
+ r = e._payload, e = e._init;
85
+ try {
86
+ return a(e(r));
87
+ } catch {
88
+ }
89
+ }
90
+ return null;
91
+ }
92
+ function n(e) {
93
+ return "" + e;
94
+ }
95
+ function l(e) {
96
+ try {
97
+ n(e);
98
+ var r = !1;
99
+ } catch {
100
+ r = !0;
101
+ }
102
+ if (r) {
103
+ r = console;
104
+ var o = r.error, s = typeof Symbol == "function" && Symbol.toStringTag && e[Symbol.toStringTag] || e.constructor.name || "Object";
105
+ return o.call(
106
+ r,
107
+ "The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",
108
+ s
109
+ ), n(e);
110
+ }
111
+ }
112
+ function i(e) {
113
+ if (e === O) return "<>";
114
+ if (typeof e == "object" && e !== null && e.$$typeof === y)
115
+ return "<...>";
116
+ try {
117
+ var r = a(e);
118
+ return r ? "<" + r + ">" : "<...>";
119
+ } catch {
120
+ return "<...>";
121
+ }
122
+ }
123
+ function t() {
124
+ var e = P.A;
125
+ return e === null ? null : e.getOwner();
126
+ }
127
+ function u() {
128
+ return Error("react-stack-top-frame");
129
+ }
130
+ function f(e) {
131
+ if (Y.call(e, "key")) {
132
+ var r = Object.getOwnPropertyDescriptor(e, "key").get;
133
+ if (r && r.isReactWarning) return !1;
134
+ }
135
+ return e.key !== void 0;
136
+ }
137
+ function m(e, r) {
138
+ function o() {
139
+ F || (F = !0, console.error(
140
+ "%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",
141
+ r
142
+ ));
143
+ }
144
+ o.isReactWarning = !0, Object.defineProperty(e, "key", {
145
+ get: o,
146
+ configurable: !0
147
+ });
148
+ }
149
+ function T() {
150
+ var e = a(this.type);
151
+ return $[e] || ($[e] = !0, console.error(
152
+ "Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release."
153
+ )), e = this.props.ref, e !== void 0 ? e : null;
154
+ }
155
+ function x(e, r, o, s, A, C) {
156
+ var c = o.ref;
157
+ return e = {
158
+ $$typeof: k,
159
+ type: e,
160
+ key: r,
161
+ props: o,
162
+ _owner: s
163
+ }, (c !== void 0 ? c : null) !== null ? Object.defineProperty(e, "ref", {
164
+ enumerable: !1,
165
+ get: T
166
+ }) : Object.defineProperty(e, "ref", { enumerable: !1, value: null }), e._store = {}, Object.defineProperty(e._store, "validated", {
167
+ configurable: !1,
168
+ enumerable: !1,
169
+ writable: !0,
170
+ value: 0
171
+ }), Object.defineProperty(e, "_debugInfo", {
172
+ configurable: !1,
173
+ enumerable: !1,
174
+ writable: !0,
175
+ value: null
176
+ }), Object.defineProperty(e, "_debugStack", {
177
+ configurable: !1,
178
+ enumerable: !1,
179
+ writable: !0,
180
+ value: A
181
+ }), Object.defineProperty(e, "_debugTask", {
182
+ configurable: !1,
183
+ enumerable: !1,
184
+ writable: !0,
185
+ value: C
186
+ }), Object.freeze && (Object.freeze(e.props), Object.freeze(e)), e;
187
+ }
188
+ function j(e, r, o, s, A, C) {
189
+ var c = r.children;
190
+ if (c !== void 0)
191
+ if (s)
192
+ if (ne(c)) {
193
+ for (s = 0; s < c.length; s++)
194
+ b(c[s]);
195
+ Object.freeze && Object.freeze(c);
196
+ } else
197
+ console.error(
198
+ "React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead."
199
+ );
200
+ else b(c);
201
+ if (Y.call(r, "key")) {
202
+ c = a(e);
203
+ var p = Object.keys(r).filter(function(oe) {
204
+ return oe !== "key";
205
+ });
206
+ s = 0 < p.length ? "{key: someKey, " + p.join(": ..., ") + ": ...}" : "{key: someKey}", B[c + s] || (p = 0 < p.length ? "{" + p.join(": ..., ") + ": ...}" : "{}", console.error(
207
+ `A props object containing a "key" prop is being spread into JSX:
208
+ let props = %s;
209
+ <%s {...props} />
210
+ React keys must be passed directly to JSX without using spread:
211
+ let props = %s;
212
+ <%s key={someKey} {...props} />`,
213
+ s,
214
+ c,
215
+ p,
216
+ c
217
+ ), B[c + s] = !0);
218
+ }
219
+ if (c = null, o !== void 0 && (l(o), c = "" + o), f(r) && (l(r.key), c = "" + r.key), "key" in r) {
220
+ o = {};
221
+ for (var N in r)
222
+ N !== "key" && (o[N] = r[N]);
223
+ } else o = r;
224
+ return c && m(
225
+ o,
226
+ typeof e == "function" ? e.displayName || e.name || "Unknown" : e
227
+ ), x(
228
+ e,
229
+ c,
230
+ o,
231
+ t(),
232
+ A,
233
+ C
234
+ );
235
+ }
236
+ function b(e) {
237
+ _(e) ? e._store && (e._store.validated = 1) : typeof e == "object" && e !== null && e.$$typeof === y && (e._payload.status === "fulfilled" ? _(e._payload.value) && e._payload.value._store && (e._payload.value._store.validated = 1) : e._store && (e._store.validated = 1));
238
+ }
239
+ function _(e) {
240
+ return typeof e == "object" && e !== null && e.$$typeof === k;
241
+ }
242
+ var E = ae, k = Symbol.for("react.transitional.element"), J = Symbol.for("react.portal"), O = Symbol.for("react.fragment"), z = Symbol.for("react.strict_mode"), G = Symbol.for("react.profiler"), H = Symbol.for("react.consumer"), X = Symbol.for("react.context"), Z = Symbol.for("react.forward_ref"), Q = Symbol.for("react.suspense"), K = Symbol.for("react.suspense_list"), ee = Symbol.for("react.memo"), y = Symbol.for("react.lazy"), re = Symbol.for("react.activity"), te = Symbol.for("react.client.reference"), P = E.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, Y = Object.prototype.hasOwnProperty, ne = Array.isArray, g = console.createTask ? console.createTask : function() {
243
+ return null;
244
+ };
245
+ E = {
246
+ react_stack_bottom_frame: function(e) {
247
+ return e();
248
+ }
249
+ };
250
+ var F, $ = {}, I = E.react_stack_bottom_frame.bind(
251
+ E,
252
+ u
253
+ )(), L = g(i(u)), B = {};
254
+ R.Fragment = O, R.jsx = function(e, r, o) {
255
+ var s = 1e4 > P.recentlyCreatedOwnerStacks++;
256
+ return j(
257
+ e,
258
+ r,
259
+ o,
260
+ !1,
261
+ s ? Error("react-stack-top-frame") : I,
262
+ s ? g(i(e)) : L
263
+ );
264
+ }, R.jsxs = function(e, r, o) {
265
+ var s = 1e4 > P.recentlyCreatedOwnerStacks++;
266
+ return j(
267
+ e,
268
+ r,
269
+ o,
270
+ !0,
271
+ s ? Error("react-stack-top-frame") : I,
272
+ s ? g(i(e)) : L
273
+ );
274
+ };
275
+ })()), R;
276
+ }
277
+ var U;
278
+ function de() {
279
+ return U || (U = 1, process.env.NODE_ENV === "production" ? h.exports = ie() : h.exports = fe()), h.exports;
280
+ }
281
+ var d = de();
282
+ const V = typeof window < "u" ? ue : se;
283
+ function me({
284
+ bindApi: a,
285
+ selector: n,
286
+ children: l
287
+ }) {
288
+ const i = q(a.store, n);
289
+ return typeof l == "function" ? /* @__PURE__ */ d.jsx(d.Fragment, { children: l(i) }) : /* @__PURE__ */ d.jsx(d.Fragment, { children: l });
290
+ }
291
+ function Ee({
292
+ bindApi: a,
293
+ value: n,
294
+ children: l
295
+ }) {
296
+ const i = q(a.store, (u) => u), t = {
297
+ state: {
298
+ value: n
299
+ },
300
+ meta: {
301
+ isActive: i.value === n
302
+ },
303
+ handleChange: () => a.setValue(n)
304
+ };
305
+ return /* @__PURE__ */ d.jsx(d.Fragment, { children: l(t) });
306
+ }
307
+ function pe(a) {
308
+ const [n] = ce(() => new le(a)), l = w(() => {
309
+ const i = n;
310
+ return i.Element = function(u) {
311
+ return /* @__PURE__ */ d.jsx(Ee, { bindApi: n, value: u.value, children: u.children });
312
+ }, i.Subscribe = function(u) {
313
+ const f = u.selector ?? ((m) => m);
314
+ return /* @__PURE__ */ d.jsx(me, { bindApi: n, selector: f, children: u.children });
315
+ }, i;
316
+ }, [n]);
317
+ return V(n.mount, []), V(() => {
318
+ n.update(a);
319
+ }), l;
320
+ }
321
+ S(null);
322
+ S(null);
323
+ function Re() {
324
+ const a = S(null), n = S(null);
325
+ function l() {
326
+ const t = D(a);
327
+ if (!t)
328
+ throw new Error(
329
+ "`useElementContext` must be used within an `Element` component created by `createBindHook`"
330
+ );
331
+ return t;
332
+ }
333
+ function i() {
334
+ const t = D(n);
335
+ if (!t)
336
+ throw new Error(
337
+ "`useBindContext` must be used within an `AppBind` component created by `createBindHook`"
338
+ );
339
+ return t;
340
+ }
341
+ return {
342
+ elementContext: a,
343
+ bindContext: n,
344
+ useElementContext: l,
345
+ useBindContext: i
346
+ };
347
+ }
348
+ function Te({
349
+ elementContext: a,
350
+ bindContext: n,
351
+ components: l
352
+ }) {
353
+ function i(t, u) {
354
+ const f = pe(u), m = l[t], T = w(() => ({ children: b }) => /* @__PURE__ */ d.jsx(
355
+ n.Provider,
356
+ {
357
+ value: f,
358
+ children: b
359
+ }
360
+ ), [f]), x = w(() => function(_) {
361
+ return /* @__PURE__ */ d.jsx(f.Element, { value: _.value, children: (E) => {
362
+ const k = {
363
+ ...E,
364
+ ...m
365
+ };
366
+ return /* @__PURE__ */ d.jsx(
367
+ a.Provider,
368
+ {
369
+ value: E,
370
+ children: _.children(k)
371
+ }
372
+ );
373
+ } });
374
+ }, [f, m]);
375
+ return w(() => Object.assign({}, f, {
376
+ Element: x,
377
+ AppBind: T
378
+ }), [f, x, T]);
379
+ }
380
+ return {
381
+ useAppBind: i
382
+ };
383
+ }
384
+ export {
385
+ Re as createBindContexts,
386
+ Te as createBindHook,
387
+ pe as useBind
388
+ };
@@ -0,0 +1,46 @@
1
+ import type { BindState } from "@bind/core";
2
+ import type { ReactNode } from "react";
3
+ /**
4
+ * State object for Element context
5
+ */
6
+ export interface ElementState<TValues extends readonly string[]> {
7
+ /** The value this element represents */
8
+ value: TValues[number];
9
+ }
10
+ /**
11
+ * Meta information for Element context
12
+ */
13
+ export interface ElementMeta {
14
+ /** Whether this value is currently active */
15
+ isActive: boolean;
16
+ }
17
+ /**
18
+ * Context provided to Element render props
19
+ */
20
+ export interface ElementContext<TValues extends readonly string[]> {
21
+ /** State object containing the element's value */
22
+ state: ElementState<TValues>;
23
+ /** Meta information about the element */
24
+ meta: ElementMeta;
25
+ /** Handler to set this value as active */
26
+ handleChange: () => void;
27
+ }
28
+ /**
29
+ * Props for the Element component
30
+ */
31
+ export interface ElementProps<TValues extends readonly string[]> {
32
+ /** The value this element represents */
33
+ value: TValues[number];
34
+ /** Render prop receiving the element context */
35
+ children: (context: ElementContext<TValues>) => ReactNode;
36
+ }
37
+ /**
38
+ * Props for the Subscribe component
39
+ */
40
+ export interface SubscribeProps<TValues extends readonly string[], TSelected = BindState<TValues>> {
41
+ /** Optional selector to pick specific state */
42
+ selector?: (state: BindState<TValues>) => TSelected;
43
+ /** Render prop receiving the selected state */
44
+ children: ((state: TSelected) => ReactNode) | ReactNode;
45
+ }
46
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC;;GAEG;AACH,MAAM,WAAW,YAAY,CAAC,OAAO,SAAS,SAAS,MAAM,EAAE;IAC9D,wCAAwC;IACxC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC3B,6CAA6C;IAC7C,QAAQ,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc,CAAC,OAAO,SAAS,SAAS,MAAM,EAAE;IAChE,kDAAkD;IAClD,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC;IAC7B,yCAAyC;IACzC,IAAI,EAAE,WAAW,CAAC;IAClB,0CAA0C;IAC1C,YAAY,EAAE,MAAM,IAAI,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY,CAAC,OAAO,SAAS,SAAS,MAAM,EAAE;IAC9D,wCAAwC;IACxC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IACvB,gDAAgD;IAChD,QAAQ,EAAE,CAAC,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,KAAK,SAAS,CAAC;CAC1D;AAED;;GAEG;AACH,MAAM,WAAW,cAAc,CAAC,OAAO,SAAS,SAAS,MAAM,EAAE,EAAE,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC;IAChG,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,OAAO,CAAC,KAAK,SAAS,CAAC;IACpD,+CAA+C;IAC/C,QAAQ,EAAE,CAAC,CAAC,KAAK,EAAE,SAAS,KAAK,SAAS,CAAC,GAAG,SAAS,CAAC;CACxD"}
@@ -0,0 +1,49 @@
1
+ import { BindApi, type BindOptions, type BindState } from "@bind/core";
2
+ import type { ElementProps, SubscribeProps } from "./types";
3
+ import type { FunctionComponent } from "react";
4
+ /**
5
+ * React-specific API additions to BindApi
6
+ */
7
+ export interface ReactBindApi<TValues extends readonly string[]> {
8
+ /**
9
+ * A component that renders based on a specific value.
10
+ * Provides context for both reading state and setting the value.
11
+ */
12
+ Element: (props: ElementProps<TValues>) => ReturnType<FunctionComponent>;
13
+ /**
14
+ * A component that subscribes to bind state changes.
15
+ * Use the selector prop to optimize re-renders.
16
+ */
17
+ Subscribe: <TSelected = BindState<TValues>>(props: SubscribeProps<TValues, TSelected>) => ReturnType<FunctionComponent>;
18
+ }
19
+ /**
20
+ * The extended API returned by useBind, combining core API with React components
21
+ */
22
+ export type ReactBindExtendedApi<TValues extends readonly string[]> = BindApi<TValues> & ReactBindApi<TValues>;
23
+ /**
24
+ * A custom React Hook that returns an extended instance of the `BindApi` class.
25
+ *
26
+ * This API encapsulates all the necessary functionalities for managing exclusive selection state
27
+ * like tabs, accordions, and multi-step forms.
28
+ *
29
+ * @example
30
+ * ```tsx
31
+ * const tabs = useBind({
32
+ * defaultValue: "tab1",
33
+ * values: ["tab1", "tab2"] as const,
34
+ * });
35
+ *
36
+ * return (
37
+ * <div>
38
+ * <tabs.Element value="tab1">
39
+ * {(bindApi) => <button onClick={bind.handleChange}>Tab 1</button>}
40
+ * </tabs.Element>
41
+ * <tabs.Element value="tab1">
42
+ * {(bindApi) => bind.meta.isActive && <div>Panel 1</div>}
43
+ * </tabs.Element>
44
+ * </div>
45
+ * );
46
+ * ```
47
+ */
48
+ export declare function useBind<TValues extends readonly string[]>(opts: BindOptions<TValues>): ReactBindExtendedApi<TValues>;
49
+ //# sourceMappingURL=useBind.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useBind.d.ts","sourceRoot":"","sources":["../../src/useBind.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAIvE,OAAO,KAAK,EAAE,YAAY,EAAkB,cAAc,EAAE,MAAM,SAAS,CAAC;AAC5E,OAAO,KAAK,EAAE,iBAAiB,EAAa,MAAM,OAAO,CAAC;AAE1D;;GAEG;AACH,MAAM,WAAW,YAAY,CAAC,OAAO,SAAS,SAAS,MAAM,EAAE;IAC9D;;;OAGG;IACH,OAAO,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,KAAK,UAAU,CAAC,iBAAiB,CAAC,CAAC;IAEzE;;;OAGG;IACH,SAAS,EAAE,CAAC,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,EACzC,KAAK,EAAE,cAAc,CAAC,OAAO,EAAE,SAAS,CAAC,KACrC,UAAU,CAAC,iBAAiB,CAAC,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,MAAM,oBAAoB,CAAC,OAAO,SAAS,SAAS,MAAM,EAAE,IAAI,OAAO,CAAC,OAAO,CAAC,GACrF,YAAY,CAAC,OAAO,CAAC,CAAC;AAiDvB;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,OAAO,CAAC,OAAO,SAAS,SAAS,MAAM,EAAE,EACxD,IAAI,EAAE,WAAW,CAAC,OAAO,CAAC,GACxB,oBAAoB,CAAC,OAAO,CAAC,CAmC/B"}
@@ -0,0 +1,6 @@
1
+ import { useLayoutEffect } from "react";
2
+ /**
3
+ * useLayoutEffect on client, useEffect on server (SSR safe)
4
+ */
5
+ export declare const useIsomorphicLayoutEffect: typeof useLayoutEffect;
6
+ //# sourceMappingURL=useIsomorphicLayoutEffect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useIsomorphicLayoutEffect.d.ts","sourceRoot":"","sources":["../../src/useIsomorphicLayoutEffect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,eAAe,EAAE,MAAM,OAAO,CAAC;AAEnD;;GAEG;AACH,eAAO,MAAM,yBAAyB,wBACsB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@bind-ts/react-bind",
3
+ "version": "0.0.1",
4
+ "description": "React bindings for @bind-ts/core - type-safe compound component binding",
5
+ "author": "",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "types": "dist/esm/index.d.ts",
9
+ "main": "dist/cjs/index.cjs",
10
+ "module": "dist/esm/index.js",
11
+ "exports": {
12
+ ".": {
13
+ "import": {
14
+ "types": "./dist/esm/index.d.ts",
15
+ "default": "./dist/esm/index.js"
16
+ },
17
+ "require": {
18
+ "types": "./dist/cjs/index.d.cts",
19
+ "default": "./dist/cjs/index.cjs"
20
+ }
21
+ },
22
+ "./package.json": "./package.json"
23
+ },
24
+ "sideEffects": false,
25
+ "files": [
26
+ "dist",
27
+ "src",
28
+ "!**/__tests__"
29
+ ],
30
+ "scripts": {
31
+ "build": "vite build && tsc --emitDeclarationOnly --outDir dist/esm",
32
+ "test": "vitest run",
33
+ "test:watch": "vitest",
34
+ "check-types": "tsc --noEmit"
35
+ },
36
+ "dependencies": {
37
+ "@bind-ts/bind-core": "^0.0.1",
38
+ "@tanstack/react-store": "^0.7.0"
39
+ },
40
+ "peerDependencies": {
41
+ "react": ">=18.0.0"
42
+ },
43
+ "devDependencies": {
44
+ "@testing-library/jest-dom": "^6.6.0",
45
+ "@testing-library/react": "^16.0.0",
46
+ "@types/react": "^19.0.0",
47
+ "jsdom": "^26.1.0",
48
+ "react": "^19.0.0",
49
+ "react-dom": "^19.0.0",
50
+ "typescript": "^5.9.2",
51
+ "vite": "^6.3.5",
52
+ "vitest": "^3.2.4"
53
+ },
54
+ "publishConfig": {
55
+ "access": "public"
56
+ }
57
+ }
@@ -0,0 +1,241 @@
1
+ "use client";
2
+
3
+ import { createContext, useContext, useMemo } from "react";
4
+ import { useBind } from "./useBind";
5
+ import type { BindOptions, BindState } from "@bind/bind-core";
6
+ import type {
7
+ ComponentType,
8
+ Context,
9
+ FunctionComponent,
10
+ PropsWithChildren,
11
+ ReactNode,
12
+ } from "react";
13
+ import type { ReactBindExtendedApi } from "./useBind";
14
+ import type { ElementContext } from "./types";
15
+
16
+ /**
17
+ * Context for the element render context, providing access to value state
18
+ * within custom element components.
19
+ */
20
+ const defaultElementContext = createContext<ElementContext<readonly string[]>>(null as never);
21
+
22
+ /**
23
+ * Context for the bind API, providing access to the bind instance
24
+ * within custom components.
25
+ */
26
+ const defaultBindContext = createContext<ReactBindExtendedApi<readonly string[]>>(null as never);
27
+
28
+ /**
29
+ * Creates the contexts and hooks needed for bind composition.
30
+ *
31
+ * @example
32
+ * ```tsx
33
+ * export const { bindContext, elementContext, useElementContext, useBindContext } =
34
+ * createBindContexts();
35
+ * ```
36
+ */
37
+ export function createBindContexts() {
38
+ const elementContext = createContext<ElementContext<readonly string[]>>(null as never);
39
+
40
+ const bindContext = createContext<ReactBindExtendedApi<readonly string[]>>(null as never);
41
+
42
+ function useElementContext<TValues extends readonly string[] = readonly string[]>() {
43
+ const element = useContext(elementContext);
44
+
45
+ if (!element) {
46
+ throw new Error(
47
+ "`useElementContext` must be used within an `Element` component created by `createBindHook`",
48
+ );
49
+ }
50
+
51
+ return element as ElementContext<TValues>;
52
+ }
53
+
54
+ function useBindContext<TValues extends readonly string[] = readonly string[]>() {
55
+ const bindApi = useContext(bindContext);
56
+
57
+ if (!bindApi) {
58
+ throw new Error(
59
+ "`useBindContext` must be used within an `AppBind` component created by `createBindHook`",
60
+ );
61
+ }
62
+
63
+ return bindApi as unknown as ReactBindExtendedApi<TValues>;
64
+ }
65
+
66
+ return {
67
+ elementContext,
68
+ bindContext,
69
+ useElementContext,
70
+ useBindContext,
71
+ };
72
+ }
73
+
74
+ /**
75
+ * A record of component groups, where each key is a group name
76
+ * and the value is a record of components in that group.
77
+ */
78
+ type ComponentGroups = Record<string, Record<string, ComponentType<any>>>;
79
+
80
+ /**
81
+ * Props for the createBindHook factory function.
82
+ */
83
+ interface CreateBindHookProps<TComponentGroups extends ComponentGroups> {
84
+ elementContext: Context<ElementContext<readonly string[]>>;
85
+ bindContext: Context<ReactBindExtendedApi<readonly string[]>>;
86
+ components: TComponentGroups;
87
+ }
88
+
89
+ /**
90
+ * Context passed to Element children, including the element render context
91
+ * and the selected component group.
92
+ */
93
+ export type AppElementContext<
94
+ TValues extends readonly string[],
95
+ TComponents extends Record<string, ComponentType<any>>,
96
+ > = ElementContext<TValues> & TComponents;
97
+
98
+ /**
99
+ * Props for the Element component in composition API.
100
+ */
101
+ export interface AppElementProps<
102
+ TValues extends readonly string[],
103
+ TComponents extends Record<string, ComponentType<any>>,
104
+ > {
105
+ /** The value this element represents */
106
+ value: TValues[number];
107
+ /** Render prop receiving the element context with components */
108
+ children: (context: AppElementContext<TValues, TComponents>) => ReactNode;
109
+ }
110
+
111
+ /**
112
+ * Extended bind API with composition support.
113
+ */
114
+ export type AppBindExtendedApi<
115
+ TValues extends readonly string[],
116
+ TComponents extends Record<string, ComponentType<any>>,
117
+ > = Omit<ReactBindExtendedApi<TValues>, "Element"> & {
118
+ /**
119
+ * An element component that provides both element context and the selected component group.
120
+ * Use this to render pre-bound custom components.
121
+ */
122
+ Element: (props: AppElementProps<TValues, TComponents>) => ReturnType<FunctionComponent>;
123
+
124
+ /**
125
+ * A wrapper component that provides bind context to children.
126
+ * Use this when you need access to the bind API in deeply nested components.
127
+ */
128
+ AppBind: ComponentType<PropsWithChildren<{}>>;
129
+ };
130
+
131
+ /**
132
+ * Creates a custom bind hook factory with pre-bound component groups.
133
+ *
134
+ * @example
135
+ * ```tsx
136
+ * const { elementContext, bindContext, useElementContext } = createBindContexts();
137
+ *
138
+ * function Tab({ children }: { children: React.ReactNode }) {
139
+ * const element = useElementContext();
140
+ * return <button onClick={element.handleChange}>{children}</button>;
141
+ * }
142
+ *
143
+ * function TabPanel({ children }: { children: React.ReactNode }) {
144
+ * const element = useElementContext();
145
+ * if (!element.meta.isActive) return null;
146
+ * return <div>{children}</div>;
147
+ * }
148
+ *
149
+ * const { useAppBind } = createBindHook({
150
+ * elementContext,
151
+ * bindContext,
152
+ * components: {
153
+ * Tab: { Tab, TabPanel },
154
+ * Wizard: { Step, Button },
155
+ * },
156
+ * });
157
+ *
158
+ * function MyTabs() {
159
+ * const tabs = useAppBind('Tab', {
160
+ * defaultValue: 'tab1',
161
+ * values: ['tab1', 'tab2'] as const,
162
+ * });
163
+ *
164
+ * return (
165
+ * <div>
166
+ * <tabs.Element value="tab1">
167
+ * {(bindApi) => <bindApi.Tab>Tab 1</bindApi.Tab>}
168
+ * </tabs.Element>
169
+ * <tabs.Element value="tab1">
170
+ * {(bindApi) => <bindApi.TabPanel>Panel 1</bindApi.TabPanel>}
171
+ * </tabs.Element>
172
+ * </div>
173
+ * );
174
+ * }
175
+ * ```
176
+ */
177
+ export function createBindHook<const TComponentGroups extends ComponentGroups>({
178
+ elementContext,
179
+ bindContext,
180
+ components,
181
+ }: CreateBindHookProps<TComponentGroups>) {
182
+ function useAppBind<
183
+ TGroupKey extends keyof TComponentGroups,
184
+ TValues extends readonly string[],
185
+ >(
186
+ groupKey: TGroupKey,
187
+ opts: BindOptions<TValues>,
188
+ ): AppBindExtendedApi<TValues, TComponentGroups[TGroupKey]> {
189
+ const bindApi = useBind(opts);
190
+ const componentGroup = components[groupKey];
191
+
192
+ const AppBind = useMemo<ComponentType<PropsWithChildren<{}>>>(() => {
193
+ return ({ children }) => {
194
+ return (
195
+ <bindContext.Provider
196
+ value={bindApi as unknown as ReactBindExtendedApi<readonly string[]>}
197
+ >
198
+ {children}
199
+ </bindContext.Provider>
200
+ );
201
+ };
202
+ }, [bindApi]);
203
+
204
+ const Element = useMemo(() => {
205
+ return function Element(props: AppElementProps<TValues, TComponentGroups[TGroupKey]>) {
206
+ return (
207
+ <bindApi.Element value={props.value}>
208
+ {(bindApi) => {
209
+ // Merge element context with component group
210
+ const appElementContext = {
211
+ ...bindApi,
212
+ ...componentGroup,
213
+ } as AppElementContext<TValues, TComponentGroups[TGroupKey]>;
214
+
215
+ return (
216
+ <elementContext.Provider
217
+ value={bindApi as ElementContext<readonly string[]>}
218
+ >
219
+ {props.children(appElementContext)}
220
+ </elementContext.Provider>
221
+ );
222
+ }}
223
+ </bindApi.Element>
224
+ );
225
+ };
226
+ }, [bindApi, componentGroup]);
227
+
228
+ const extendedApi = useMemo(() => {
229
+ return Object.assign({}, bindApi, {
230
+ Element,
231
+ AppBind,
232
+ }) as AppBindExtendedApi<TValues, TComponentGroups[TGroupKey]>;
233
+ }, [bindApi, Element, AppBind]);
234
+
235
+ return extendedApi;
236
+ }
237
+
238
+ return {
239
+ useAppBind,
240
+ };
241
+ }
package/src/index.ts ADDED
@@ -0,0 +1,16 @@
1
+ export { useBind } from "./useBind";
2
+ export type { ReactBindApi, ReactBindExtendedApi } from "./useBind";
3
+ export type {
4
+ ElementContext,
5
+ ElementState,
6
+ ElementMeta,
7
+ ElementProps,
8
+ SubscribeProps,
9
+ } from "./types";
10
+
11
+ // Composition APIs
12
+ export { createBindContexts, createBindHook } from "./createBindHook";
13
+ export type { AppElementContext, AppElementProps, AppBindExtendedApi } from "./createBindHook";
14
+
15
+ // Re-export core types for convenience
16
+ export type { BindOptions, BindState } from "@bind/core";
package/src/types.ts ADDED
@@ -0,0 +1,50 @@
1
+ import type { BindState } from "@bind/core";
2
+ import type { ReactNode } from "react";
3
+
4
+ /**
5
+ * State object for Element context
6
+ */
7
+ export interface ElementState<TValues extends readonly string[]> {
8
+ /** The value this element represents */
9
+ value: TValues[number];
10
+ }
11
+
12
+ /**
13
+ * Meta information for Element context
14
+ */
15
+ export interface ElementMeta {
16
+ /** Whether this value is currently active */
17
+ isActive: boolean;
18
+ }
19
+
20
+ /**
21
+ * Context provided to Element render props
22
+ */
23
+ export interface ElementContext<TValues extends readonly string[]> {
24
+ /** State object containing the element's value */
25
+ state: ElementState<TValues>;
26
+ /** Meta information about the element */
27
+ meta: ElementMeta;
28
+ /** Handler to set this value as active */
29
+ handleChange: () => void;
30
+ }
31
+
32
+ /**
33
+ * Props for the Element component
34
+ */
35
+ export interface ElementProps<TValues extends readonly string[]> {
36
+ /** The value this element represents */
37
+ value: TValues[number];
38
+ /** Render prop receiving the element context */
39
+ children: (context: ElementContext<TValues>) => ReactNode;
40
+ }
41
+
42
+ /**
43
+ * Props for the Subscribe component
44
+ */
45
+ export interface SubscribeProps<TValues extends readonly string[], TSelected = BindState<TValues>> {
46
+ /** Optional selector to pick specific state */
47
+ selector?: (state: BindState<TValues>) => TSelected;
48
+ /** Render prop receiving the selected state */
49
+ children: ((state: TSelected) => ReactNode) | ReactNode;
50
+ }
@@ -0,0 +1,144 @@
1
+ "use client";
2
+
3
+ import { BindApi, type BindOptions, type BindState } from "@bind/core";
4
+ import { useStore } from "@tanstack/react-store";
5
+ import { useMemo, useState } from "react";
6
+ import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect";
7
+ import type { ElementProps, ElementContext, SubscribeProps } from "./types";
8
+ import type { FunctionComponent, ReactNode } from "react";
9
+
10
+ /**
11
+ * React-specific API additions to BindApi
12
+ */
13
+ export interface ReactBindApi<TValues extends readonly string[]> {
14
+ /**
15
+ * A component that renders based on a specific value.
16
+ * Provides context for both reading state and setting the value.
17
+ */
18
+ Element: (props: ElementProps<TValues>) => ReturnType<FunctionComponent>;
19
+
20
+ /**
21
+ * A component that subscribes to bind state changes.
22
+ * Use the selector prop to optimize re-renders.
23
+ */
24
+ Subscribe: <TSelected = BindState<TValues>>(
25
+ props: SubscribeProps<TValues, TSelected>,
26
+ ) => ReturnType<FunctionComponent>;
27
+ }
28
+
29
+ /**
30
+ * The extended API returned by useBind, combining core API with React components
31
+ */
32
+ export type ReactBindExtendedApi<TValues extends readonly string[]> = BindApi<TValues> &
33
+ ReactBindApi<TValues>;
34
+
35
+ /**
36
+ * Internal Subscribe component that handles store subscription
37
+ */
38
+ function LocalSubscribe<TValues extends readonly string[], TSelected>({
39
+ bindApi,
40
+ selector,
41
+ children,
42
+ }: {
43
+ bindApi: BindApi<TValues>;
44
+ selector: (state: BindState<TValues>) => TSelected;
45
+ children: ((state: TSelected) => ReactNode) | ReactNode;
46
+ }): ReturnType<FunctionComponent> {
47
+ const data = useStore(bindApi.store, selector);
48
+
49
+ if (typeof children === "function") {
50
+ return <>{children(data)}</>;
51
+ }
52
+ return <>{children}</>;
53
+ }
54
+
55
+ /**
56
+ * Internal Element component that renders based on value state
57
+ */
58
+ function LocalElement<TValues extends readonly string[]>({
59
+ bindApi,
60
+ value,
61
+ children,
62
+ }: {
63
+ bindApi: BindApi<TValues>;
64
+ value: TValues[number];
65
+ children: (context: ElementContext<TValues>) => ReactNode;
66
+ }): ReturnType<FunctionComponent> {
67
+ const storeState = useStore(bindApi.store, (s: BindState<TValues>) => s);
68
+
69
+ const context: ElementContext<TValues> = {
70
+ state: {
71
+ value: value,
72
+ },
73
+ meta: {
74
+ isActive: storeState.value === value,
75
+ },
76
+ handleChange: () => bindApi.setValue(value),
77
+ };
78
+
79
+ return <>{children(context)}</>;
80
+ }
81
+
82
+ /**
83
+ * A custom React Hook that returns an extended instance of the `BindApi` class.
84
+ *
85
+ * This API encapsulates all the necessary functionalities for managing exclusive selection state
86
+ * like tabs, accordions, and multi-step forms.
87
+ *
88
+ * @example
89
+ * ```tsx
90
+ * const tabs = useBind({
91
+ * defaultValue: "tab1",
92
+ * values: ["tab1", "tab2"] as const,
93
+ * });
94
+ *
95
+ * return (
96
+ * <div>
97
+ * <tabs.Element value="tab1">
98
+ * {(bindApi) => <button onClick={bind.handleChange}>Tab 1</button>}
99
+ * </tabs.Element>
100
+ * <tabs.Element value="tab1">
101
+ * {(bindApi) => bind.meta.isActive && <div>Panel 1</div>}
102
+ * </tabs.Element>
103
+ * </div>
104
+ * );
105
+ * ```
106
+ */
107
+ export function useBind<TValues extends readonly string[]>(
108
+ opts: BindOptions<TValues>,
109
+ ): ReactBindExtendedApi<TValues> {
110
+ const [bindApi] = useState(() => new BindApi<TValues>(opts));
111
+
112
+ const extendedApi = useMemo(() => {
113
+ const api = bindApi as ReactBindExtendedApi<TValues>;
114
+
115
+ api.Element = function Element(props: ElementProps<TValues>) {
116
+ return (
117
+ <LocalElement bindApi={bindApi} value={props.value}>
118
+ {props.children}
119
+ </LocalElement>
120
+ );
121
+ };
122
+
123
+ api.Subscribe = function Subscribe<TSelected = BindState<TValues>>(
124
+ props: SubscribeProps<TValues, TSelected>,
125
+ ) {
126
+ const selector = props.selector ?? ((s) => s as unknown as TSelected);
127
+ return (
128
+ <LocalSubscribe bindApi={bindApi} selector={selector} children={props.children} />
129
+ );
130
+ };
131
+
132
+ return api;
133
+ }, [bindApi]);
134
+
135
+ // Mount/unmount lifecycle
136
+ useIsomorphicLayoutEffect(bindApi.mount, []);
137
+
138
+ // Keep options in sync (like a ref, no side effects)
139
+ useIsomorphicLayoutEffect(() => {
140
+ bindApi.update(opts);
141
+ });
142
+
143
+ return extendedApi;
144
+ }
@@ -0,0 +1,7 @@
1
+ import { useEffect, useLayoutEffect } from "react";
2
+
3
+ /**
4
+ * useLayoutEffect on client, useEffect on server (SSR safe)
5
+ */
6
+ export const useIsomorphicLayoutEffect =
7
+ typeof window !== "undefined" ? useLayoutEffect : useEffect;