@lucasbernardosn/react-utils 0.0.0-stage → 0.1.0

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lucas Bernardo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,2 @@
1
+ import { useCopyToClipboard } from "./use-copy-to-clipboard.js";
2
+ export { useCopyToClipboard };
@@ -0,0 +1,2 @@
1
+ import { useCopyToClipboard } from "./use-copy-to-clipboard.js";
2
+ export { useCopyToClipboard };
@@ -0,0 +1,26 @@
1
+ //#region src/hooks/use-copy-to-clipboard.d.ts
2
+ /** Texto atualmente copiado para a área de transferência, ou null se nenhum texto foi copiado. */
3
+ type CopiedValue = string | null;
4
+ /** Função que copia um texto para a área de transferência e retorna se a operação foi bem-sucedida. */
5
+ type CopyFn = (text: string) => Promise<boolean>;
6
+ /**
7
+ * Retorno do hook useCopyToClipboard.
8
+ */
9
+ type UseCopyToClipboardReturn = {
10
+ /** Último texto copiado com sucesso para a área de transferência, ou null se ainda não houve cópia. */
11
+ copiedText: CopiedValue;
12
+ /** Função para copiar um texto para a área de transferência. */
13
+ copy: CopyFn;
14
+ };
15
+ /**
16
+ * Gerencia a cópia de texto para a área de transferência do navegador.
17
+ *
18
+ * Expõe o último texto copiado com sucesso e uma função de cópia que retorna
19
+ * um booleano indicando se a operação foi concluída. A função é segura para
20
+ * ambientes que não suportam a API Clipboard.
21
+ *
22
+ * @returns Objeto com o texto atualmente copiado e a função de cópia.
23
+ */
24
+ export declare const useCopyToClipboard: () => UseCopyToClipboardReturn;
25
+ //#endregion
26
+ //# sourceMappingURL=use-copy-to-clipboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-copy-to-clipboard.d.ts","names":[],"sources":["../../src/hooks/use-copy-to-clipboard.ts"],"mappings":";;KAGK;;KAGA,UAAU,iBAAiB;;;;KAK3B;;EAEH,YAAY;;EAEZ,MAAM;;;;;;;;;;;qBAYK,0BAAyB"}
@@ -0,0 +1,36 @@
1
+ import { useCallback, useState } from "react";
2
+ //#region src/hooks/use-copy-to-clipboard.ts
3
+ /**
4
+ * Gerencia a cópia de texto para a área de transferência do navegador.
5
+ *
6
+ * Expõe o último texto copiado com sucesso e uma função de cópia que retorna
7
+ * um booleano indicando se a operação foi concluída. A função é segura para
8
+ * ambientes que não suportam a API Clipboard.
9
+ *
10
+ * @returns Objeto com o texto atualmente copiado e a função de cópia.
11
+ */
12
+ const useCopyToClipboard = () => {
13
+ const [copiedText, setCopiedText] = useState(null);
14
+ return {
15
+ copiedText,
16
+ copy: useCallback(async (text) => {
17
+ if (typeof navigator === "undefined" || !navigator.clipboard) {
18
+ console.warn("useCopyToClipboard: Clipboard not supported");
19
+ return false;
20
+ }
21
+ try {
22
+ await navigator.clipboard.writeText(text);
23
+ setCopiedText(text);
24
+ return true;
25
+ } catch (error) {
26
+ console.warn("useCopyToClipboard: Copy failed", error);
27
+ setCopiedText(null);
28
+ return false;
29
+ }
30
+ }, [])
31
+ };
32
+ };
33
+ //#endregion
34
+ export { useCopyToClipboard };
35
+
36
+ //# sourceMappingURL=use-copy-to-clipboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-copy-to-clipboard.js","names":[],"sources":["../../src/hooks/use-copy-to-clipboard.ts"],"sourcesContent":["import { useCallback, useState } from 'react';\n\n/** Texto atualmente copiado para a área de transferência, ou null se nenhum texto foi copiado. */\ntype CopiedValue = string | null;\n\n/** Função que copia um texto para a área de transferência e retorna se a operação foi bem-sucedida. */\ntype CopyFn = (text: string) => Promise<boolean>;\n\n/**\n * Retorno do hook useCopyToClipboard.\n */\ntype UseCopyToClipboardReturn = {\n /** Último texto copiado com sucesso para a área de transferência, ou null se ainda não houve cópia. */\n copiedText: CopiedValue;\n /** Função para copiar um texto para a área de transferência. */\n copy: CopyFn;\n};\n\n/**\n * Gerencia a cópia de texto para a área de transferência do navegador.\n *\n * Expõe o último texto copiado com sucesso e uma função de cópia que retorna\n * um booleano indicando se a operação foi concluída. A função é segura para\n * ambientes que não suportam a API Clipboard.\n *\n * @returns Objeto com o texto atualmente copiado e a função de cópia.\n */\nexport const useCopyToClipboard = (): UseCopyToClipboardReturn => {\n const [copiedText, setCopiedText] = useState<CopiedValue>(null);\n\n const copy: CopyFn = useCallback(async (text) => {\n /*\n * A API Clipboard pode não estar disponível em contextos sem HTTPS,\n * em ambientes de teste ou em SSR (onde `navigator` nem existe) —\n * verificar antes de usar é necessário.\n */\n if (typeof navigator === 'undefined' || !navigator.clipboard) {\n console.warn('useCopyToClipboard: Clipboard not supported');\n return false;\n }\n\n try {\n await navigator.clipboard.writeText(text);\n setCopiedText(text);\n return true;\n } catch (error) {\n console.warn('useCopyToClipboard: Copy failed', error);\n setCopiedText(null);\n return false;\n }\n }, []);\n\n return { copiedText, copy };\n};\n"],"mappings":";;;;;;;;;;;AA2BA,MAAa,2BAAqD;CAChE,MAAM,CAAC,YAAY,iBAAiB,SAAsB,IAAI;CAwB9D,OAAO;EAAE;EAAY,MAtBA,YAAY,OAAO,SAAS;GAM/C,IAAI,OAAO,cAAc,eAAe,CAAC,UAAU,WAAW;IAC5D,QAAQ,KAAK,6CAA6C;IAC1D,OAAO;GACT;GAEA,IAAI;IACF,MAAM,UAAU,UAAU,UAAU,IAAI;IACxC,cAAc,IAAI;IAClB,OAAO;GACT,SAAS,OAAO;IACd,QAAQ,KAAK,mCAAmC,KAAK;IACrD,cAAc,IAAI;IAClB,OAAO;GACT;EACF,GAAG,CAAC,CAEoB;CAAE;AAC5B"}
@@ -0,0 +1,24 @@
1
+ //#region src/hooks/use-media-query.d.ts
2
+ /**
3
+ * Configurações opcionais para controlar o comportamento inicial do hook useMediaQuery.
4
+ */
5
+ type UseMediaQueryOptions = {
6
+ /** Valor padrão retornado quando `initializeWithValue` é false. */
7
+ defaultValue?: boolean;
8
+ /** Define se deve inicializar com o valor atual da media query. */
9
+ initializeWithValue?: boolean;
10
+ };
11
+ /**
12
+ * Monitora continuamente mudanças em media queries CSS, reagindo automaticamente
13
+ * a alterações no tamanho da viewport ou outras características do dispositivo.
14
+ *
15
+ * O hook gerencia automaticamente a adição e remoção de listeners, prevenindo
16
+ * vazamentos de memória quando o componente é desmontado.
17
+ *
18
+ * @param query - String da media query CSS a ser monitorada (ex: `"(min-width: 768px)"`).
19
+ * @param options - Configurações opcionais para controlar o comportamento inicial do hook.
20
+ * @returns Verdadeiro quando a media query atual corresponde à condição especificada, falso caso contrário.
21
+ */
22
+ export declare const useMediaQuery: (query: string, options?: UseMediaQueryOptions) => boolean;
23
+ //#endregion
24
+ //# sourceMappingURL=use-media-query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-media-query.d.ts","names":[],"sources":["../../src/hooks/use-media-query.ts"],"mappings":";;;;KAKK;;EAEH;;EAEA;;;;;;;;;;;;;qBAcW,gBACX,eACA,UAAS"}
@@ -0,0 +1,41 @@
1
+ import { useCallback, useLayoutEffect, useState } from "react";
2
+ //#region src/hooks/use-media-query.ts
3
+ /**
4
+ * Monitora continuamente mudanças em media queries CSS, reagindo automaticamente
5
+ * a alterações no tamanho da viewport ou outras características do dispositivo.
6
+ *
7
+ * O hook gerencia automaticamente a adição e remoção de listeners, prevenindo
8
+ * vazamentos de memória quando o componente é desmontado.
9
+ *
10
+ * @param query - String da media query CSS a ser monitorada (ex: `"(min-width: 768px)"`).
11
+ * @param options - Configurações opcionais para controlar o comportamento inicial do hook.
12
+ * @returns Verdadeiro quando a media query atual corresponde à condição especificada, falso caso contrário.
13
+ */
14
+ const useMediaQuery = (query, options = {}) => {
15
+ const { defaultValue = false, initializeWithValue = true } = options;
16
+ const getMatches = useCallback((query) => {
17
+ return window.matchMedia(query).matches;
18
+ }, []);
19
+ const [matches, setMatches] = useState(() => {
20
+ if (initializeWithValue) return getMatches(query);
21
+ return defaultValue;
22
+ });
23
+ const handleChange = useCallback(() => {
24
+ setMatches(getMatches(query));
25
+ }, [query, getMatches]);
26
+ useLayoutEffect(() => {
27
+ const matchMedia = window.matchMedia(query);
28
+ handleChange();
29
+ if (matchMedia.addListener) matchMedia.addListener(handleChange);
30
+ else matchMedia.addEventListener("change", handleChange);
31
+ return () => {
32
+ if (matchMedia.removeListener) matchMedia.removeListener(handleChange);
33
+ else matchMedia.removeEventListener("change", handleChange);
34
+ };
35
+ }, [query, handleChange]);
36
+ return matches;
37
+ };
38
+ //#endregion
39
+ export { useMediaQuery };
40
+
41
+ //# sourceMappingURL=use-media-query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-media-query.js","names":[],"sources":["../../src/hooks/use-media-query.ts"],"sourcesContent":["import { useCallback, useLayoutEffect, useState } from 'react';\n\n/**\n * Configurações opcionais para controlar o comportamento inicial do hook useMediaQuery.\n */\ntype UseMediaQueryOptions = {\n /** Valor padrão retornado quando `initializeWithValue` é false. */\n defaultValue?: boolean;\n /** Define se deve inicializar com o valor atual da media query. */\n initializeWithValue?: boolean;\n};\n\n/**\n * Monitora continuamente mudanças em media queries CSS, reagindo automaticamente\n * a alterações no tamanho da viewport ou outras características do dispositivo.\n *\n * O hook gerencia automaticamente a adição e remoção de listeners, prevenindo\n * vazamentos de memória quando o componente é desmontado.\n *\n * @param query - String da media query CSS a ser monitorada (ex: `\"(min-width: 768px)\"`).\n * @param options - Configurações opcionais para controlar o comportamento inicial do hook.\n * @returns Verdadeiro quando a media query atual corresponde à condição especificada, falso caso contrário.\n */\nexport const useMediaQuery = (\n query: string,\n options: UseMediaQueryOptions = {},\n): boolean => {\n const { defaultValue = false, initializeWithValue = true } = options;\n\n const getMatches = useCallback((query: string): boolean => {\n return window.matchMedia(query).matches;\n }, []);\n\n const [matches, setMatches] = useState<boolean>(() => {\n if (initializeWithValue) {\n return getMatches(query);\n }\n\n return defaultValue;\n });\n\n const handleChange = useCallback(() => {\n setMatches(getMatches(query));\n }, [query, getMatches]);\n\n useLayoutEffect(() => {\n const matchMedia = window.matchMedia(query);\n\n handleChange();\n\n /*\n * Safari < 14 não suporta `addEventListener` em MediaQueryList —\n * usa os métodos deprecated `addListener`/`removeListener` como fallback.\n */\n if (matchMedia.addListener) {\n matchMedia.addListener(handleChange);\n } else {\n matchMedia.addEventListener('change', handleChange);\n }\n\n return () => {\n if (matchMedia.removeListener) {\n matchMedia.removeListener(handleChange);\n } else {\n matchMedia.removeEventListener('change', handleChange);\n }\n };\n }, [query, handleChange]);\n\n return matches;\n};\n"],"mappings":";;;;;;;;;;;;;AAuBA,MAAa,iBACX,OACA,UAAgC,CAAC,MACrB;CACZ,MAAM,EAAE,eAAe,OAAO,sBAAsB,SAAS;CAE7D,MAAM,aAAa,aAAa,UAA2B;EACzD,OAAO,OAAO,WAAW,KAAK,CAAC,CAAC;CAClC,GAAG,CAAC,CAAC;CAEL,MAAM,CAAC,SAAS,cAAc,eAAwB;EACpD,IAAI,qBACF,OAAO,WAAW,KAAK;EAGzB,OAAO;CACT,CAAC;CAED,MAAM,eAAe,kBAAkB;EACrC,WAAW,WAAW,KAAK,CAAC;CAC9B,GAAG,CAAC,OAAO,UAAU,CAAC;CAEtB,sBAAsB;EACpB,MAAM,aAAa,OAAO,WAAW,KAAK;EAE1C,aAAa;EAMb,IAAI,WAAW,aACb,WAAW,YAAY,YAAY;OAEnC,WAAW,iBAAiB,UAAU,YAAY;EAGpD,aAAa;GACX,IAAI,WAAW,gBACb,WAAW,eAAe,YAAY;QAEtC,WAAW,oBAAoB,UAAU,YAAY;EAEzD;CACF,GAAG,CAAC,OAAO,YAAY,CAAC;CAExB,OAAO;AACT"}
@@ -0,0 +1,5 @@
1
+ import { useCopyToClipboard } from "./hooks/use-copy-to-clipboard.js";
2
+ import { CreateContextOptions, CreateContextReturn, createContext } from "./utils/create-context.js";
3
+ import { mergeRefs } from "./utils/merge-refs.js";
4
+ import "./utils/index.js";
5
+ export { CreateContextOptions, CreateContextReturn, createContext, mergeRefs, useCopyToClipboard };
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ import { useCopyToClipboard } from "./hooks/use-copy-to-clipboard.js";
2
+ import { createContext } from "./utils/create-context.js";
3
+ import { mergeRefs } from "./utils/merge-refs.js";
4
+ export { createContext, mergeRefs, useCopyToClipboard };
@@ -0,0 +1,59 @@
1
+ import React from "react";
2
+ //#region src/utils/create-context.d.ts
3
+ /**
4
+ * Opções do `createContext`.
5
+ */
6
+ export type CreateContextOptions = {
7
+ /**
8
+ * Nome exibido no React DevTools e na mensagem de erro.
9
+ *
10
+ * @example 'TabsContext'
11
+ */
12
+ name: string;
13
+ /**
14
+ * Componentes públicos que fornecem o contexto, citados na mensagem de erro.
15
+ *
16
+ * Na lib, o Provider é interno: quem é de fora fornece o contexto pelo
17
+ * `Root` ou pelo `RootProvider`, então são esses nomes que entram aqui. Em
18
+ * contexto de app, em que o Provider é a própria API pública, entra ele.
19
+ *
20
+ * @example ['Splitter.Root', 'Splitter.RootProvider']
21
+ */
22
+ providers: [string, ...string[]];
23
+ };
24
+ /**
25
+ * Retorno do `createContext`: o Provider e o hook que lê o valor dele.
26
+ *
27
+ * O Provider é tipado como `React.Provider` (e não `React.Context`) para que o
28
+ * valor só seja lido pelo hook: `use(Provider)` não compila.
29
+ *
30
+ * @template TValue - Tipo do valor armazenado no contexto.
31
+ */
32
+ export type CreateContextReturn<TValue> = [provider: React.Provider<TValue>, useContext: () => TValue];
33
+ /**
34
+ * Cria um contexto estrito: o hook lança `ContextError` quando chamado fora do
35
+ * Provider.
36
+ *
37
+ * Os nomes do Provider e do hook são escolhidos na desestruturação. Convenção:
38
+ * `XContextProvider` e `useXContext`.
39
+ *
40
+ * @template TValue - Tipo do valor armazenado no contexto.
41
+ * @param options - Nome do contexto e do componente que o provê.
42
+ * @returns Tupla com o Provider e o hook.
43
+ *
44
+ * @example
45
+ * ```tsx
46
+ * export const [SplitterContextProvider, useSplitterContext] =
47
+ * createContext<UseSplitterContext>({
48
+ * name: 'SplitterContext',
49
+ * providers: ['Splitter.Root', 'Splitter.RootProvider'],
50
+ * });
51
+ *
52
+ * <SplitterContextProvider value={splitter}>{children}</SplitterContextProvider>;
53
+ *
54
+ * const splitter = useSplitterContext();
55
+ * ```
56
+ */
57
+ export declare const createContext: <TValue>(options: CreateContextOptions) => CreateContextReturn<TValue>;
58
+ //#endregion
59
+ //# sourceMappingURL=create-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-context.d.ts","names":[],"sources":["../../src/utils/create-context.ts"],"mappings":";;;;;YAaY;;;;;;EAMV;;;;;;;;;;EAUA;;;;;;;;;;YA0BU,oBAAoB,WAC9B,UAAU,MAAM,SAAS,SACzB,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;qBA2BP,gBAAiB,QAC5B,SAAS,yBACR,oBAAoB"}
@@ -0,0 +1,57 @@
1
+ import { createContext as createContext$1, use } from "react";
2
+ //#region src/utils/create-context.ts
3
+ /**
4
+ * Valor default de todo contexto criado aqui: só é lido quando não há Provider
5
+ * acima. Como é um símbolo privado, nenhum valor legítimo (`0`, `false`, `''`,
6
+ * `undefined`) se confunde com ele.
7
+ */
8
+ const MISSING_PROVIDER = Symbol("MissingProvider");
9
+ /**
10
+ * Junta os nomes com vírgula e "ou" antes do último, cada um entre crases.
11
+ */
12
+ const providersListFormat = new Intl.ListFormat("pt-BR", { type: "disjunction" });
13
+ /**
14
+ * Cria um contexto estrito: o hook lança `ContextError` quando chamado fora do
15
+ * Provider.
16
+ *
17
+ * Os nomes do Provider e do hook são escolhidos na desestruturação. Convenção:
18
+ * `XContextProvider` e `useXContext`.
19
+ *
20
+ * @template TValue - Tipo do valor armazenado no contexto.
21
+ * @param options - Nome do contexto e do componente que o provê.
22
+ * @returns Tupla com o Provider e o hook.
23
+ *
24
+ * @example
25
+ * ```tsx
26
+ * export const [SplitterContextProvider, useSplitterContext] =
27
+ * createContext<UseSplitterContext>({
28
+ * name: 'SplitterContext',
29
+ * providers: ['Splitter.Root', 'Splitter.RootProvider'],
30
+ * });
31
+ *
32
+ * <SplitterContextProvider value={splitter}>{children}</SplitterContextProvider>;
33
+ *
34
+ * const splitter = useSplitterContext();
35
+ * ```
36
+ */
37
+ const createContext = (options) => {
38
+ const { name, providers } = options;
39
+ const errorMessage = `\`${name}\` não encontrado: o componente precisa estar dentro de ${providersListFormat.format(providers.map((provider) => `\`${provider}\``))}.`;
40
+ const Context = createContext$1(MISSING_PROVIDER);
41
+ Context.displayName = name;
42
+ const useContext = () => {
43
+ const value = use(Context);
44
+ if (value === MISSING_PROVIDER) {
45
+ const error = new Error(errorMessage);
46
+ error.name = "ContextError";
47
+ Error.captureStackTrace?.(error, useContext);
48
+ throw error;
49
+ }
50
+ return value;
51
+ };
52
+ return [Context, useContext];
53
+ };
54
+ //#endregion
55
+ export { createContext };
56
+
57
+ //# sourceMappingURL=create-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-context.js","names":["createReactContext"],"sources":["../../src/utils/create-context.ts"],"sourcesContent":["import type React from 'react';\nimport { createContext as createReactContext, use } from 'react';\n\n/**\n * Valor default de todo contexto criado aqui: só é lido quando não há Provider\n * acima. Como é um símbolo privado, nenhum valor legítimo (`0`, `false`, `''`,\n * `undefined`) se confunde com ele.\n */\nconst MISSING_PROVIDER: unique symbol = Symbol('MissingProvider');\n\n/**\n * Opções do `createContext`.\n */\nexport type CreateContextOptions = {\n /**\n * Nome exibido no React DevTools e na mensagem de erro.\n *\n * @example 'TabsContext'\n */\n name: string;\n /**\n * Componentes públicos que fornecem o contexto, citados na mensagem de erro.\n *\n * Na lib, o Provider é interno: quem é de fora fornece o contexto pelo\n * `Root` ou pelo `RootProvider`, então são esses nomes que entram aqui. Em\n * contexto de app, em que o Provider é a própria API pública, entra ele.\n *\n * @example ['Splitter.Root', 'Splitter.RootProvider']\n */\n providers: [string, ...string[]];\n};\n\n/**\n * `Error.captureStackTrace` é exclusivo do V8 e fica fora dos tipos da lib\n * padrão do TypeScript.\n */\ntype V8ErrorConstructor = ErrorConstructor & {\n captureStackTrace?: (target: object, constructorOpt?: () => unknown) => void;\n};\n\n/**\n * Junta os nomes com vírgula e \"ou\" antes do último, cada um entre crases.\n */\nconst providersListFormat = new Intl.ListFormat('pt-BR', {\n type: 'disjunction',\n});\n\n/**\n * Retorno do `createContext`: o Provider e o hook que lê o valor dele.\n *\n * O Provider é tipado como `React.Provider` (e não `React.Context`) para que o\n * valor só seja lido pelo hook: `use(Provider)` não compila.\n *\n * @template TValue - Tipo do valor armazenado no contexto.\n */\nexport type CreateContextReturn<TValue> = [\n provider: React.Provider<TValue>,\n useContext: () => TValue,\n];\n\n/**\n * Cria um contexto estrito: o hook lança `ContextError` quando chamado fora do\n * Provider.\n *\n * Os nomes do Provider e do hook são escolhidos na desestruturação. Convenção:\n * `XContextProvider` e `useXContext`.\n *\n * @template TValue - Tipo do valor armazenado no contexto.\n * @param options - Nome do contexto e do componente que o provê.\n * @returns Tupla com o Provider e o hook.\n *\n * @example\n * ```tsx\n * export const [SplitterContextProvider, useSplitterContext] =\n * createContext<UseSplitterContext>({\n * name: 'SplitterContext',\n * providers: ['Splitter.Root', 'Splitter.RootProvider'],\n * });\n *\n * <SplitterContextProvider value={splitter}>{children}</SplitterContextProvider>;\n *\n * const splitter = useSplitterContext();\n * ```\n */\nexport const createContext = <TValue>(\n options: CreateContextOptions,\n): CreateContextReturn<TValue> => {\n const { name, providers } = options;\n\n const errorMessage = `\\`${name}\\` não encontrado: o componente precisa estar dentro de ${providersListFormat.format(\n providers.map((provider) => `\\`${provider}\\``),\n )}.`;\n\n const Context = createReactContext<TValue | typeof MISSING_PROVIDER>(\n MISSING_PROVIDER,\n );\n\n Context.displayName = name;\n\n const useContext = (): TValue => {\n const value = use(Context);\n\n if (value === MISSING_PROVIDER) {\n const error = new Error(errorMessage);\n\n error.name = 'ContextError';\n\n // Tira o hook do stack: a primeira linha passa a ser o componente que o\n // chamou. Só existe em engines V8; nas outras, o stack fica completo.\n (Error as V8ErrorConstructor).captureStackTrace?.(error, useContext);\n\n throw error;\n }\n\n return value;\n };\n\n return [Context, useContext];\n};\n"],"mappings":";;;;;;;AAQA,MAAM,mBAAkC,OAAO,iBAAiB;;;;AAmChE,MAAM,sBAAsB,IAAI,KAAK,WAAW,SAAS,EACvD,MAAM,cACR,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;AAuCD,MAAa,iBACX,YACgC;CAChC,MAAM,EAAE,MAAM,cAAc;CAE5B,MAAM,eAAe,KAAK,KAAK,0DAA0D,oBAAoB,OAC3G,UAAU,KAAK,aAAa,KAAK,SAAS,GAAG,CAC/C,EAAE;CAEF,MAAM,UAAUA,gBACd,gBACF;CAEA,QAAQ,cAAc;CAEtB,MAAM,mBAA2B;EAC/B,MAAM,QAAQ,IAAI,OAAO;EAEzB,IAAI,UAAU,kBAAkB;GAC9B,MAAM,QAAQ,IAAI,MAAM,YAAY;GAEpC,MAAM,OAAO;GAIb,MAA8B,oBAAoB,OAAO,UAAU;GAEnE,MAAM;EACR;EAEA,OAAO;CACT;CAEA,OAAO,CAAC,SAAS,UAAU;AAC7B"}
@@ -0,0 +1,3 @@
1
+ import { CreateContextOptions, CreateContextReturn, createContext } from "./create-context.js";
2
+ import { mergeRefs } from "./merge-refs.js";
3
+ export { CreateContextOptions, CreateContextReturn, createContext, mergeRefs };
@@ -0,0 +1,3 @@
1
+ import { createContext } from "./create-context.js";
2
+ import { mergeRefs } from "./merge-refs.js";
3
+ export { createContext, mergeRefs };
@@ -0,0 +1,16 @@
1
+ import React from "react";
2
+ //#region src/utils/merge-refs.d.ts
3
+ /**
4
+ * Combina múltiplas refs em uma única ref callback.
5
+ *
6
+ * Refs `undefined`/`null` são ignoradas silenciosamente. Útil para expor um
7
+ * elemento tanto para uma ref interna quanto para uma ref encaminhada
8
+ * (`forwardRef`) simultaneamente.
9
+ *
10
+ * @template T - Tipo do valor referenciado (ex: elemento DOM ou instância).
11
+ * @param refs - Lista de refs (callback, RefObject, `undefined` ou `null`) a serem combinadas.
12
+ * @returns Ref callback única que propaga o node para todas as refs recebidas.
13
+ */
14
+ export declare function mergeRefs<T>(...refs: (React.Ref<T> | undefined)[]): React.RefCallback<T>;
15
+ //#endregion
16
+ //# sourceMappingURL=merge-refs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-refs.d.ts","names":[],"sources":["../../src/utils/merge-refs.ts"],"mappings":";;;;;;;;;;;;;wBAgCgB,UAAU,MACrB,OAAO,MAAM,IAAI,oBACnB,MAAM,YAAY"}
@@ -0,0 +1,36 @@
1
+ //#region src/utils/merge-refs.ts
2
+ /**
3
+ * Atribui `value` a uma ref, normalizando callback refs e RefObjects.
4
+ *
5
+ * Necessário porque o React expõe dois formatos de ref e não há uma API
6
+ * unificada para escrevê-los — esta função esconde esse detalhe dos
7
+ * consumidores.
8
+ *
9
+ * @template T - Tipo do valor referenciado (ex: elemento DOM ou instância).
10
+ * @param ref - Ref callback, RefObject ou `undefined`/`null` (ignorada).
11
+ * @param value - Valor a ser atribuído à ref.
12
+ */
13
+ function setRef(ref, value) {
14
+ if (typeof ref === "function") ref(value);
15
+ else if (ref != null) ref.current = value;
16
+ }
17
+ /**
18
+ * Combina múltiplas refs em uma única ref callback.
19
+ *
20
+ * Refs `undefined`/`null` são ignoradas silenciosamente. Útil para expor um
21
+ * elemento tanto para uma ref interna quanto para uma ref encaminhada
22
+ * (`forwardRef`) simultaneamente.
23
+ *
24
+ * @template T - Tipo do valor referenciado (ex: elemento DOM ou instância).
25
+ * @param refs - Lista de refs (callback, RefObject, `undefined` ou `null`) a serem combinadas.
26
+ * @returns Ref callback única que propaga o node para todas as refs recebidas.
27
+ */
28
+ function mergeRefs(...refs) {
29
+ return (node) => {
30
+ for (const ref of refs) setRef(ref, node);
31
+ };
32
+ }
33
+ //#endregion
34
+ export { mergeRefs };
35
+
36
+ //# sourceMappingURL=merge-refs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-refs.js","names":[],"sources":["../../src/utils/merge-refs.ts"],"sourcesContent":["import type React from 'react';\n\n/**\n * Atribui `value` a uma ref, normalizando callback refs e RefObjects.\n *\n * Necessário porque o React expõe dois formatos de ref e não há uma API\n * unificada para escrevê-los — esta função esconde esse detalhe dos\n * consumidores.\n *\n * @template T - Tipo do valor referenciado (ex: elemento DOM ou instância).\n * @param ref - Ref callback, RefObject ou `undefined`/`null` (ignorada).\n * @param value - Valor a ser atribuído à ref.\n */\nfunction setRef<T>(ref: React.Ref<T> | undefined, value: T | null): void {\n if (typeof ref === 'function') {\n (ref as React.RefCallback<T>)(value);\n } else if (ref != null) {\n (ref as React.RefObject<T | null>).current = value;\n }\n}\n\n/**\n * Combina múltiplas refs em uma única ref callback.\n *\n * Refs `undefined`/`null` são ignoradas silenciosamente. Útil para expor um\n * elemento tanto para uma ref interna quanto para uma ref encaminhada\n * (`forwardRef`) simultaneamente.\n *\n * @template T - Tipo do valor referenciado (ex: elemento DOM ou instância).\n * @param refs - Lista de refs (callback, RefObject, `undefined` ou `null`) a serem combinadas.\n * @returns Ref callback única que propaga o node para todas as refs recebidas.\n */\nexport function mergeRefs<T>(\n ...refs: (React.Ref<T> | undefined)[]\n): React.RefCallback<T> {\n return (node: T | null) => {\n for (const ref of refs) {\n setRef(ref, node);\n }\n };\n}\n"],"mappings":";;;;;;;;;;;;AAaA,SAAS,OAAU,KAA+B,OAAuB;CACvE,IAAI,OAAO,QAAQ,YACjB,IAA8B,KAAK;MAC9B,IAAI,OAAO,MAChB,IAAmC,UAAU;AAEjD;;;;;;;;;;;;AAaA,SAAgB,UACd,GAAG,MACmB;CACtB,QAAQ,SAAmB;EACzB,KAAK,MAAM,OAAO,MAChB,OAAO,KAAK,IAAI;CAEpB;AACF"}
package/package.json CHANGED
@@ -1,6 +1,56 @@
1
1
  {
2
2
  "name": "@lucasbernardosn/react-utils",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "React hooks and utilities.",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "default": "./dist/index.js"
10
+ }
11
+ },
12
+ "dependencies": {},
13
+ "devDependencies": {
14
+ "@biomejs/biome": "2.5.15",
15
+ "@lucasbernardosn/tsconfig": "1.0.0",
16
+ "@testing-library/jest-dom": "7.0.1",
17
+ "@testing-library/react": "16.3.3",
18
+ "@testing-library/user-event": "14.6.7",
19
+ "@types/node": "26.6.3",
20
+ "@types/react": "19.3.0",
21
+ "@types/react-dom": "19.3.0",
22
+ "@vitejs/plugin-react": "6.1.1",
23
+ "jsdom": "30.1.1",
24
+ "react": "19.3.0",
25
+ "react-dom": "19.3.0",
26
+ "tsdown": "0.23.0",
27
+ "typescript": "6.0.3",
28
+ "vitest": "5.0.3"
29
+ },
30
+ "peerDependencies": {
31
+ "react": "^19.0.0",
32
+ "react-dom": "^19.0.0"
33
+ },
34
+ "files": [
35
+ "dist"
36
+ ],
37
+ "sideEffects": false,
38
+ "license": "MIT",
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "git+https://github.com/LucasBernardoSN/react-monorepo.git",
45
+ "directory": "packages/react/utils"
46
+ },
47
+ "scripts": {
48
+ "build": "tsdown",
49
+ "build:report": "tsdown --report",
50
+ "lint": "biome lint --write",
51
+ "format": "biome format --write",
52
+ "check": "biome check --write",
53
+ "test": "vitest",
54
+ "test:run": "vitest run"
55
+ }
6
56
  }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.