@cdx-ui/utils 0.0.1-beta.9 → 0.0.1-beta.91

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.
Files changed (53) hide show
  1. package/README.md +47 -23
  2. package/lib/commonjs/common/mergeRefs.js +39 -1
  3. package/lib/commonjs/common/mergeRefs.js.map +1 -1
  4. package/lib/commonjs/context/createContext.js +7 -3
  5. package/lib/commonjs/context/createContext.js.map +1 -1
  6. package/lib/commonjs/form-control/index.js +6 -0
  7. package/lib/commonjs/form-control/index.js.map +1 -1
  8. package/lib/commonjs/form-control/useFormControl.js +33 -0
  9. package/lib/commonjs/form-control/useFormControl.js.map +1 -1
  10. package/lib/commonjs/style/index.js +0 -11
  11. package/lib/commonjs/style/index.js.map +1 -1
  12. package/lib/module/common/mergeRefs.js +39 -1
  13. package/lib/module/common/mergeRefs.js.map +1 -1
  14. package/lib/module/context/createContext.js +7 -3
  15. package/lib/module/context/createContext.js.map +1 -1
  16. package/lib/module/form-control/index.js +1 -1
  17. package/lib/module/form-control/index.js.map +1 -1
  18. package/lib/module/form-control/useFormControl.js +33 -0
  19. package/lib/module/form-control/useFormControl.js.map +1 -1
  20. package/lib/module/style/index.js +0 -1
  21. package/lib/module/style/index.js.map +1 -1
  22. package/lib/typescript/common/mergeRefs.d.ts +8 -0
  23. package/lib/typescript/common/mergeRefs.d.ts.map +1 -1
  24. package/lib/typescript/context/createContext.d.ts +16 -1
  25. package/lib/typescript/context/createContext.d.ts.map +1 -1
  26. package/lib/typescript/form-control/index.d.ts +1 -1
  27. package/lib/typescript/form-control/index.d.ts.map +1 -1
  28. package/lib/typescript/form-control/useFormControl.d.ts +28 -3
  29. package/lib/typescript/form-control/useFormControl.d.ts.map +1 -1
  30. package/lib/typescript/style/index.d.ts +0 -1
  31. package/lib/typescript/style/index.d.ts.map +1 -1
  32. package/package.json +4 -2
  33. package/src/common/accessibilityUtils.test.ts +12 -0
  34. package/src/common/cn.test.ts +19 -0
  35. package/src/common/composeEventHandlers.test.ts +35 -0
  36. package/src/common/getSpacedChild.test.tsx +64 -0
  37. package/src/common/mergeRefs.test.ts +148 -0
  38. package/src/common/mergeRefs.ts +45 -2
  39. package/src/common/useControllableState.test.tsx +90 -0
  40. package/src/context/createContext.test.tsx +31 -0
  41. package/src/context/createContext.tsx +21 -4
  42. package/src/form-control/index.ts +2 -0
  43. package/src/form-control/useFormControl.test.tsx +214 -0
  44. package/src/form-control/useFormControl.tsx +48 -4
  45. package/src/style/context/index.test.tsx +31 -0
  46. package/src/style/index.tsx +0 -1
  47. package/lib/commonjs/style/withStyleContext/index.js +0 -33
  48. package/lib/commonjs/style/withStyleContext/index.js.map +0 -1
  49. package/lib/module/style/withStyleContext/index.js +0 -28
  50. package/lib/module/style/withStyleContext/index.js.map +0 -1
  51. package/lib/typescript/style/withStyleContext/index.d.ts +0 -6
  52. package/lib/typescript/style/withStyleContext/index.d.ts.map +0 -1
  53. package/src/style/withStyleContext/index.tsx +0 -36
@@ -0,0 +1,214 @@
1
+ import type { ReactNode } from 'react';
2
+ import { Platform, type TextInput } from 'react-native';
3
+ import { act, renderHook } from '@testing-library/react-native';
4
+ import {
5
+ FormControlContext,
6
+ useFormControl,
7
+ useFormControlContext,
8
+ useFormControlRoot,
9
+ useReportFormControlLabelFocus,
10
+ } from './useFormControl';
11
+
12
+ function FormControlTestProvider({
13
+ children,
14
+ rootProps,
15
+ hasFeedbackText = false,
16
+ hasHelpText = false,
17
+ }: {
18
+ children: ReactNode;
19
+ rootProps?: Parameters<typeof useFormControlRoot>[0];
20
+ hasFeedbackText?: boolean;
21
+ hasHelpText?: boolean;
22
+ }) {
23
+ const root = useFormControlRoot(rootProps ?? { id: 'email' });
24
+
25
+ return (
26
+ <FormControlContext.Provider value={{ ...root, hasFeedbackText, hasHelpText }}>
27
+ {children}
28
+ </FormControlContext.Provider>
29
+ );
30
+ }
31
+
32
+ describe('useFormControlRoot', () => {
33
+ it('uses provided id and coerces boolean state flags', () => {
34
+ const { result } = renderHook(() =>
35
+ useFormControlRoot({
36
+ id: 'custom-id',
37
+ isRequired: true,
38
+ isInvalid: true,
39
+ isDisabled: false,
40
+ isReadOnly: false,
41
+ name: 'email',
42
+ 'data-test': 'root',
43
+ }),
44
+ );
45
+
46
+ expect(result.current.id).toBe('custom-id');
47
+ expect(result.current.labelId).toBe('custom-id-label');
48
+ expect(result.current.isRequired).toBe(true);
49
+ expect(result.current.isInvalid).toBe(true);
50
+ expect(result.current.isDisabled).toBe(false);
51
+ expect(result.current.isReadOnly).toBe(false);
52
+ expect(result.current.name).toBe('email');
53
+ expect(result.current.htmlProps).toEqual({ 'data-test': 'root' });
54
+ });
55
+
56
+ it('generates an id when none is provided', () => {
57
+ const { result } = renderHook(() => useFormControlRoot({}));
58
+
59
+ expect(result.current.id).toMatch(/^field-/);
60
+ expect(result.current.feedbackId).toBe(`${result.current.id}-feedback`);
61
+ });
62
+
63
+ it('ignores empty string id and generates a fallback', () => {
64
+ const { result } = renderHook(() => useFormControlRoot({ id: '' }));
65
+
66
+ expect(result.current.id).toMatch(/^field-/);
67
+ expect(result.current.id).not.toBe('');
68
+ });
69
+
70
+ it('exposes native focus helpers by default', () => {
71
+ const { result } = renderHook(() => useFormControlRoot({}));
72
+
73
+ expect(result.current.inputRef).toBeDefined();
74
+ expect(result.current.focusInput).toBeDefined();
75
+
76
+ const { inputRef, focusInput } = result.current;
77
+ if (!inputRef || !focusInput) {
78
+ throw new Error('expected native focus helpers');
79
+ }
80
+
81
+ const focus = jest.fn();
82
+ act(() => {
83
+ inputRef.current = { focus } as TextInput;
84
+ focusInput();
85
+ });
86
+
87
+ expect(focus).toHaveBeenCalled();
88
+ });
89
+
90
+ it('no-ops focusInput when ref is empty or focus is unavailable', () => {
91
+ const { result } = renderHook(() => useFormControlRoot({}));
92
+ const { focusInput, inputRef } = result.current;
93
+
94
+ if (!focusInput || !inputRef) {
95
+ throw new Error('expected native focus helpers');
96
+ }
97
+
98
+ expect(() => {
99
+ focusInput();
100
+ }).not.toThrow();
101
+
102
+ act(() => {
103
+ inputRef.current = {} as TextInput;
104
+ focusInput();
105
+ });
106
+
107
+ expect(inputRef.current).toEqual({});
108
+ });
109
+
110
+ it('omits native focus helpers on web', () => {
111
+ const originalDescriptor = Object.getOwnPropertyDescriptor(Platform, 'OS');
112
+ Object.defineProperty(Platform, 'OS', { configurable: true, get: () => 'web' });
113
+
114
+ const { result } = renderHook(() => useFormControlRoot({}));
115
+
116
+ expect(result.current.inputRef).toBeUndefined();
117
+ expect(result.current.focusInput).toBeUndefined();
118
+
119
+ if (originalDescriptor) {
120
+ Object.defineProperty(Platform, 'OS', originalDescriptor);
121
+ }
122
+ });
123
+ });
124
+
125
+ describe('useFormControl', () => {
126
+ it('merges local props with context and composes aria-describedby', () => {
127
+ const { result } = renderHook(() => useFormControl({ id: 'override', isDisabled: true }), {
128
+ wrapper: ({ children }) => (
129
+ <FormControlTestProvider
130
+ rootProps={{
131
+ id: 'email',
132
+ name: 'email',
133
+ isRequired: true,
134
+ isInvalid: true,
135
+ isDisabled: false,
136
+ isReadOnly: false,
137
+ }}
138
+ hasFeedbackText
139
+ hasHelpText
140
+ >
141
+ {children}
142
+ </FormControlTestProvider>
143
+ ),
144
+ });
145
+
146
+ expect(result.current.id).toBe('override');
147
+ expect(result.current.name).toBe('email');
148
+ expect(result.current.disabled).toBe(true);
149
+ expect(result.current.required).toBe(true);
150
+ expect(result.current['aria-invalid']).toBe(true);
151
+ expect(result.current['aria-labelledby']).toBe('email-label');
152
+ expect(result.current['aria-describedby']).toBe('email-feedback email-helptext');
153
+ });
154
+
155
+ it('returns undefined aria-describedby when no help or feedback is present', () => {
156
+ const { result } = renderHook(() => useFormControl({}), {
157
+ wrapper: ({ children }) => <FormControlTestProvider>{children}</FormControlTestProvider>,
158
+ });
159
+
160
+ expect(result.current['aria-describedby']).toBeUndefined();
161
+ expect(result.current.id).toBe('email-input');
162
+ });
163
+
164
+ it('composes aria-describedby from feedback or help text alone', () => {
165
+ const feedbackOnly = renderHook(() => useFormControl({}), {
166
+ wrapper: ({ children }) => (
167
+ <FormControlTestProvider hasFeedbackText>{children}</FormControlTestProvider>
168
+ ),
169
+ }).result.current['aria-describedby'];
170
+
171
+ const helpOnly = renderHook(() => useFormControl({}), {
172
+ wrapper: ({ children }) => (
173
+ <FormControlTestProvider hasHelpText>{children}</FormControlTestProvider>
174
+ ),
175
+ }).result.current['aria-describedby'];
176
+
177
+ expect(feedbackOnly).toBe('email-feedback');
178
+ expect(helpOnly).toBe('email-helptext');
179
+ });
180
+
181
+ it('merges readOnly from local props and omits id without field context', () => {
182
+ const { result } = renderHook(() => useFormControl({ isReadOnly: true }));
183
+
184
+ expect(result.current.readOnly).toBe(true);
185
+ expect(result.current.id).toBeUndefined();
186
+ });
187
+ });
188
+
189
+ describe('useReportFormControlLabelFocus', () => {
190
+ it('reports active state to the form control context', () => {
191
+ const { result, rerender } = renderHook(
192
+ ({ active }: { active: boolean }) => {
193
+ useReportFormControlLabelFocus(active);
194
+ return useFormControlContext().isLabelFocused;
195
+ },
196
+ {
197
+ wrapper: ({ children }) => <FormControlTestProvider>{children}</FormControlTestProvider>,
198
+ initialProps: { active: true },
199
+ },
200
+ );
201
+
202
+ expect(result.current).toBe(true);
203
+
204
+ rerender({ active: false });
205
+
206
+ expect(result.current).toBe(false);
207
+ });
208
+
209
+ it('no-ops when setIsLabelFocused is unavailable', () => {
210
+ expect(() => {
211
+ renderHook(() => useReportFormControlLabelFocus(true));
212
+ }).not.toThrow();
213
+ });
214
+ });
@@ -1,9 +1,22 @@
1
1
  import React from 'react';
2
- import { Platform, type TextInput } from 'react-native';
2
+ import { Platform } from 'react-native';
3
3
  import { ariaAttr } from '../common/accessibilityUtils';
4
4
 
5
5
  type HTMLProps = Record<string, unknown>;
6
6
 
7
+ /**
8
+ * Minimal contract for any control registered on `FormControlContextValue.inputRef`.
9
+ *
10
+ * `Field.Label` / `focusInput()` only need to call `.focus()` on the registered node;
11
+ * narrower control-specific shapes (`TextInput`, `HTMLInputElement`, custom focus
12
+ * bridges like `Select.Trigger`'s) all satisfy this contract. Consumers that need
13
+ * more (e.g. reading `.value`) should cast locally — keeping the context type
14
+ * minimal avoids a typed lie when a non-input bridge is registered.
15
+ */
16
+ export interface Focusable {
17
+ focus(): void;
18
+ }
19
+
7
20
  export interface FormControlState {
8
21
  isRequired: boolean;
9
22
  isInvalid: boolean;
@@ -25,9 +38,16 @@ export interface FormControlContextValue extends FormControlState {
25
38
  /**
26
39
  * @platform native — set by `Input.Field` / `Select.Trigger` for label press-to-focus.
27
40
  * On web, use `<label htmlFor>` with a labelable control (`input`, `button`, `select`, …).
41
+ *
42
+ * Typed as `Focusable` so any control (TextInput, HTMLInputElement, custom focus
43
+ * bridge) can register without a cast at the assignment site. Consumers that need
44
+ * the narrower shape cast locally.
28
45
  */
29
- inputRef?: React.RefObject<TextInput | null>;
46
+ inputRef?: React.RefObject<Focusable | null>;
30
47
  focusInput?: () => void;
48
+ /** True when the primary child control is focused or (for Select) open — drives `Field.Label` styling. */
49
+ isLabelFocused?: boolean;
50
+ setIsLabelFocused?: React.Dispatch<React.SetStateAction<boolean>>;
31
51
  }
32
52
 
33
53
  export const FormControlContext = React.createContext<Partial<FormControlContextValue>>({});
@@ -57,8 +77,10 @@ export interface FormControlRootReturn {
57
77
  feedbackId: string;
58
78
  helpTextId: string;
59
79
  htmlProps: HTMLProps;
60
- inputRef?: React.RefObject<TextInput | null>;
80
+ inputRef?: React.RefObject<Focusable | null>;
61
81
  focusInput?: () => void;
82
+ isLabelFocused: boolean;
83
+ setIsLabelFocused: React.Dispatch<React.SetStateAction<boolean>>;
62
84
  }
63
85
 
64
86
  /**
@@ -87,7 +109,9 @@ export function useFormControlRoot(props: FormControlRootProps): FormControlRoot
87
109
  */
88
110
  const [hasHelpText, setHasHelpText] = React.useState(false);
89
111
 
90
- const inputRef = React.useRef<TextInput | null>(null);
112
+ const [isLabelFocused, setIsLabelFocused] = React.useState(false);
113
+
114
+ const inputRef = React.useRef<Focusable | null>(null);
91
115
 
92
116
  const focusInput = React.useCallback(() => {
93
117
  const node = inputRef.current;
@@ -111,12 +135,32 @@ export function useFormControlRoot(props: FormControlRootProps): FormControlRoot
111
135
  feedbackId,
112
136
  helpTextId,
113
137
  htmlProps,
138
+ isLabelFocused,
139
+ setIsLabelFocused,
114
140
  ...(Platform.OS !== 'web' ? { inputRef, focusInput } : {}),
115
141
  };
116
142
 
117
143
  return context;
118
144
  }
119
145
 
146
+ /**
147
+ * Reports focus/active state from `Input` / `Select.Trigger` to the parent `Field` for label styling.
148
+ * No-ops outside a `Field` (no `setIsLabelFocused` on context).
149
+ */
150
+ export function useReportFormControlLabelFocus(isActive: boolean) {
151
+ const { setIsLabelFocused } = useFormControlContext();
152
+
153
+ React.useLayoutEffect(() => {
154
+ if (typeof setIsLabelFocused !== 'function') {
155
+ return undefined;
156
+ }
157
+ setIsLabelFocused(isActive);
158
+ return () => {
159
+ setIsLabelFocused(false);
160
+ };
161
+ }, [isActive, setIsLabelFocused]);
162
+ }
163
+
120
164
  /**
121
165
  * Hook that merges local field props with the nearest form field context
122
166
  * and returns normalised HTML / ARIA attributes for input elements.
@@ -0,0 +1,31 @@
1
+ import type { ReactNode } from 'react';
2
+ import { renderHook } from '@testing-library/react-native';
3
+ import { ParentContext, useParentContext, useStyleContext } from './index';
4
+
5
+ describe('style context hooks', () => {
6
+ it('reads parent context values', () => {
7
+ const wrapper = ({ children }: { children: ReactNode }) => (
8
+ <ParentContext.Provider value={{ Button: { size: 'lg' }, Card: { padded: true } }}>
9
+ {children}
10
+ </ParentContext.Provider>
11
+ );
12
+
13
+ const { result: parentResult } = renderHook(() => useParentContext(), { wrapper });
14
+ const { result: styleResult } = renderHook(() => useStyleContext('Button'), { wrapper });
15
+
16
+ expect(parentResult.current).toEqual({ Button: { size: 'lg' }, Card: { padded: true } });
17
+ expect(styleResult.current).toEqual({ size: 'lg' });
18
+ });
19
+
20
+ it('defaults to Global scope', () => {
21
+ const wrapper = ({ children }: { children: ReactNode }) => (
22
+ <ParentContext.Provider value={{ Global: { theme: 'dark' } }}>
23
+ {children}
24
+ </ParentContext.Provider>
25
+ );
26
+
27
+ const { result } = renderHook(() => useStyleContext(), { wrapper });
28
+
29
+ expect(result.current).toEqual({ theme: 'dark' });
30
+ });
31
+ });
@@ -1,2 +1 @@
1
1
  export * from './context';
2
- export * from './withStyleContext';
@@ -1,33 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.withStyleContext = void 0;
7
- var _react = require("react");
8
- var _context = require("../context");
9
- var _jsxRuntime = require("react/jsx-runtime");
10
- const withStyleContext = (Component, scope = 'Global') => {
11
- const Wrapped = /*#__PURE__*/(0, _react.forwardRef)(({
12
- context,
13
- ...props
14
- }, ref) => {
15
- const parentContextValues = (0, _context.useParentContext)();
16
- const contextValues = (0, _react.useMemo)(() => {
17
- return {
18
- ...parentContextValues,
19
- [scope]: context
20
- };
21
- }, [parentContextValues, context]);
22
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_context.ParentContext.Provider, {
23
- value: contextValues,
24
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(Component, {
25
- ...props,
26
- ref: ref
27
- })
28
- });
29
- });
30
- return Wrapped;
31
- };
32
- exports.withStyleContext = withStyleContext;
33
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["_react","require","_context","_jsxRuntime","withStyleContext","Component","scope","Wrapped","forwardRef","context","props","ref","parentContextValues","useParentContext","contextValues","useMemo","jsx","ParentContext","Provider","value","children","exports"],"sourceRoot":"../../../../src","sources":["style/withStyleContext/index.tsx"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AASA,IAAAC,QAAA,GAAAD,OAAA;AAAmF,IAAAE,WAAA,GAAAF,OAAA;AAM5E,MAAMG,gBAAgB,GAAGA,CAAwBC,SAAY,EAAEC,KAAK,GAAG,QAAQ,KAAK;EAIzF,MAAMC,OAAO,gBAAG,IAAAC,iBAAU,EAAa,CAAC;IAAEC,OAAO;IAAE,GAAGC;EAAM,CAAC,EAAEC,GAAG,KAAK;IACrE,MAAMC,mBAAmB,GAAG,IAAAC,yBAAgB,EAAC,CAAC;IAE9C,MAAMC,aAA8B,GAAG,IAAAC,cAAO,EAAC,MAAM;MACnD,OAAO;QAAE,GAAGH,mBAAmB;QAAE,CAACN,KAAK,GAAGG;MAAQ,CAAC;IACrD,CAAC,EAAE,CAACG,mBAAmB,EAAEH,OAAO,CAAC,CAAC;IAElC,oBACE,IAAAN,WAAA,CAAAa,GAAA,EAACd,QAAA,CAAAe,aAAa,CAACC,QAAQ;MAACC,KAAK,EAAEL,aAAc;MAAAM,QAAA,eAE3C,IAAAjB,WAAA,CAAAa,GAAA,EAACX,SAAS;QAAA,GAAKK,KAAK;QAAEC,GAAG,EAAEA;MAAI,CAAE;IAAC,CACZ,CAAC;EAE7B,CAAC,CAAC;EAEF,OAAOJ,OAAO;AAChB,CAAC;AAACc,OAAA,CAAAjB,gBAAA,GAAAA,gBAAA","ignoreList":[]}
@@ -1,28 +0,0 @@
1
- "use strict";
2
-
3
- import { forwardRef, useMemo } from 'react';
4
- import { ParentContext, useParentContext } from '../context';
5
- import { jsx as _jsx } from "react/jsx-runtime";
6
- export const withStyleContext = (Component, scope = 'Global') => {
7
- const Wrapped = /*#__PURE__*/forwardRef(({
8
- context,
9
- ...props
10
- }, ref) => {
11
- const parentContextValues = useParentContext();
12
- const contextValues = useMemo(() => {
13
- return {
14
- ...parentContextValues,
15
- [scope]: context
16
- };
17
- }, [parentContextValues, context]);
18
- return /*#__PURE__*/_jsx(ParentContext.Provider, {
19
- value: contextValues,
20
- children: /*#__PURE__*/_jsx(Component, {
21
- ...props,
22
- ref: ref
23
- })
24
- });
25
- });
26
- return Wrapped;
27
- };
28
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["forwardRef","useMemo","ParentContext","useParentContext","jsx","_jsx","withStyleContext","Component","scope","Wrapped","context","props","ref","parentContextValues","contextValues","Provider","value","children"],"sourceRoot":"../../../../src","sources":["style/withStyleContext/index.tsx"],"mappings":";;AAAA,SAMEA,UAAU,EACVC,OAAO,QACF,OAAO;AACd,SAASC,aAAa,EAAwBC,gBAAgB,QAAQ,YAAY;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAMnF,OAAO,MAAMC,gBAAgB,GAAGA,CAAwBC,SAAY,EAAEC,KAAK,GAAG,QAAQ,KAAK;EAIzF,MAAMC,OAAO,gBAAGT,UAAU,CAAa,CAAC;IAAEU,OAAO;IAAE,GAAGC;EAAM,CAAC,EAAEC,GAAG,KAAK;IACrE,MAAMC,mBAAmB,GAAGV,gBAAgB,CAAC,CAAC;IAE9C,MAAMW,aAA8B,GAAGb,OAAO,CAAC,MAAM;MACnD,OAAO;QAAE,GAAGY,mBAAmB;QAAE,CAACL,KAAK,GAAGE;MAAQ,CAAC;IACrD,CAAC,EAAE,CAACG,mBAAmB,EAAEH,OAAO,CAAC,CAAC;IAElC,oBACEL,IAAA,CAACH,aAAa,CAACa,QAAQ;MAACC,KAAK,EAAEF,aAAc;MAAAG,QAAA,eAE3CZ,IAAA,CAACE,SAAS;QAAA,GAAKI,KAAK;QAAEC,GAAG,EAAEA;MAAI,CAAE;IAAC,CACZ,CAAC;EAE7B,CAAC,CAAC;EAEF,OAAOH,OAAO;AAChB,CAAC","ignoreList":[]}
@@ -1,6 +0,0 @@
1
- import { type ComponentPropsWithoutRef, type ComponentRef, type ElementType, type ForwardRefExoticComponent, type RefAttributes } from 'react';
2
- export interface WithStyleContextProps {
3
- context?: Record<string, unknown>;
4
- }
5
- export declare const withStyleContext: <T extends ElementType>(Component: T, scope?: string) => ForwardRefExoticComponent<(ComponentPropsWithoutRef<T> & WithStyleContextProps) & RefAttributes<ComponentRef<T>>>;
6
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/style/withStyleContext/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,wBAAwB,EAC7B,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,yBAAyB,EAC9B,KAAK,aAAa,EAGnB,MAAM,OAAO,CAAC;AAGf,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,eAAO,MAAM,gBAAgB,GAAI,CAAC,SAAS,WAAW,EAAE,WAAW,CAAC,EAAE,cAAgB,KAmBlE,yBAAyB,CAAC,wDAAQ,aAAa,iBAAK,CACvE,CAAC"}
@@ -1,36 +0,0 @@
1
- import {
2
- type ComponentPropsWithoutRef,
3
- type ComponentRef,
4
- type ElementType,
5
- type ForwardRefExoticComponent,
6
- type RefAttributes,
7
- forwardRef,
8
- useMemo,
9
- } from 'react';
10
- import { ParentContext, type StyleContextMap, useParentContext } from '../context';
11
-
12
- export interface WithStyleContextProps {
13
- context?: Record<string, unknown>;
14
- }
15
-
16
- export const withStyleContext = <T extends ElementType>(Component: T, scope = 'Global') => {
17
- type Ref = ComponentRef<T>;
18
- type Props = ComponentPropsWithoutRef<T> & WithStyleContextProps;
19
-
20
- const Wrapped = forwardRef<Ref, Props>(({ context, ...props }, ref) => {
21
- const parentContextValues = useParentContext();
22
-
23
- const contextValues: StyleContextMap = useMemo(() => {
24
- return { ...parentContextValues, [scope]: context };
25
- }, [parentContextValues, context]);
26
-
27
- return (
28
- <ParentContext.Provider value={contextValues}>
29
- {/* @ts-expect-error -- generic component spread is not fully inferrable */}
30
- <Component {...props} ref={ref} />
31
- </ParentContext.Provider>
32
- );
33
- });
34
-
35
- return Wrapped as ForwardRefExoticComponent<Props & RefAttributes<Ref>>;
36
- };