@kopexa/use-controllable-state 0.0.0-canary-20250718183330

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/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # @kopexa/use-controllable-state
2
+
3
+ A React hook for building controlled and uncontrolled components, designed for clarity and reliability in the style of the Google API Design Guide.
4
+
5
+ ---
6
+
7
+ ## Overview
8
+
9
+ `@kopexa/use-controllable-state` provides a robust pattern for managing state in React components that can be either controlled (via props) or uncontrolled (internal state). This hook is ideal for reusable UI components and libraries.
10
+
11
+ - **Author:** Kopexa (<https://kopexa.com>)
12
+ - **License:** MIT
13
+ - **Repository:** [kopexa-grc/sight](https://github.com/kopexa-grc/sight)
14
+
15
+ ## Features
16
+
17
+ - Supports both controlled and uncontrolled usage
18
+ - Customizable change detection with `shouldUpdate`
19
+ - TypeScript support
20
+ - Lightweight and dependency-free (except React)
21
+
22
+ ## Installation
23
+
24
+ ```sh
25
+ pnpm add @kopexa/use-controllable-state
26
+ # or
27
+ yarn add @kopexa/use-controllable-state
28
+ # or
29
+ npm install @kopexa/use-controllable-state
30
+ ```
31
+
32
+ ## Usage
33
+
34
+ ```tsx
35
+ import { useControllableState } from '@kopexa/use-controllable-state';
36
+
37
+ function MyComponent(props) {
38
+ const [value, setValue] = useControllableState({
39
+ value: props.value,
40
+ defaultValue: 0,
41
+ onChange: props.onChange,
42
+ });
43
+
44
+ return <input value={value} onChange={e => setValue(e.target.value)} />;
45
+ }
46
+ ```
47
+
48
+ ## API
49
+
50
+ ### `useControllableState<T>(props: UseControllableStateProps<T>): [T, Dispatch<SetStateAction<T>>]`
51
+
52
+ - **props**: See below for details.
53
+ - **Returns**: `[value, setValue]` — the current value and a setter function.
54
+
55
+ #### `UseControllableStateProps<T>`
56
+ - `value?`: Controlled value
57
+ - `defaultValue?`: Initial value for uncontrolled usage
58
+ - `onChange?`: Callback when value changes
59
+ - `shouldUpdate?`: Custom comparison function
60
+
61
+ ## Best Practices
62
+
63
+ - Use for components that can be both controlled and uncontrolled.
64
+ - Provide clear documentation for consumers of your component.
65
+
66
+ ## Why Kopexa?
67
+
68
+ Kopexa (<https://kopexa.com>) builds reliable, developer-friendly open source tools. This package is designed for clarity, stability, and best practices, inspired by the Google API Design Guide.
69
+
70
+ ## License
71
+
72
+ MIT © Kopexa
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Determines if a component is controlled (receives its value from props) or uncontrolled (manages its own state).
3
+ * Returns a tuple: [isControlled, value].
4
+ *
5
+ * @template T - The type of the value.
6
+ * @param prop - The controlled value from props, or undefined if uncontrolled.
7
+ * @param state - The internal state value.
8
+ * @returns [boolean, T] - Whether the component is controlled, and the current value.
9
+ */
10
+ declare function useControllableProp<T>(prop: T | undefined, state: T): [boolean, T];
11
+ /**
12
+ * Props for the useControllableState hook.
13
+ *
14
+ * @template T - The type of the value.
15
+ * @property value - The controlled value (if provided).
16
+ * @property defaultValue - The initial value for uncontrolled usage.
17
+ * @property onChange - Callback fired when the value changes.
18
+ * @property shouldUpdate - Custom comparison function to determine if the value should update.
19
+ */
20
+ interface UseControllableStateProps<T> {
21
+ value?: T;
22
+ defaultValue?: T | (() => T);
23
+ onChange?: (value: T) => void;
24
+ shouldUpdate?: (prev: T, next: T) => boolean;
25
+ }
26
+ /**
27
+ * A React hook for building controlled or uncontrolled components.
28
+ *
29
+ * - If `value` is provided, the component is controlled and must be updated via `onChange`.
30
+ * - If `value` is not provided, the component manages its own state internally.
31
+ *
32
+ * Returns a tuple: [value, setValue].
33
+ *
34
+ * @template T - The type of the value.
35
+ * @param props - The props for controlling the state.
36
+ * @returns [T, Dispatch<SetStateAction<T>>] - The current value and a setter function.
37
+ *
38
+ * @example
39
+ * const [value, setValue] = useControllableState({
40
+ * value: props.value,
41
+ * defaultValue: 0,
42
+ * onChange: props.onChange,
43
+ * });
44
+ */
45
+ declare function useControllableState<T>(props: UseControllableStateProps<T>): [T, React.Dispatch<React.SetStateAction<T>>];
46
+
47
+ export { type UseControllableStateProps, useControllableProp, useControllableState };
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Determines if a component is controlled (receives its value from props) or uncontrolled (manages its own state).
3
+ * Returns a tuple: [isControlled, value].
4
+ *
5
+ * @template T - The type of the value.
6
+ * @param prop - The controlled value from props, or undefined if uncontrolled.
7
+ * @param state - The internal state value.
8
+ * @returns [boolean, T] - Whether the component is controlled, and the current value.
9
+ */
10
+ declare function useControllableProp<T>(prop: T | undefined, state: T): [boolean, T];
11
+ /**
12
+ * Props for the useControllableState hook.
13
+ *
14
+ * @template T - The type of the value.
15
+ * @property value - The controlled value (if provided).
16
+ * @property defaultValue - The initial value for uncontrolled usage.
17
+ * @property onChange - Callback fired when the value changes.
18
+ * @property shouldUpdate - Custom comparison function to determine if the value should update.
19
+ */
20
+ interface UseControllableStateProps<T> {
21
+ value?: T;
22
+ defaultValue?: T | (() => T);
23
+ onChange?: (value: T) => void;
24
+ shouldUpdate?: (prev: T, next: T) => boolean;
25
+ }
26
+ /**
27
+ * A React hook for building controlled or uncontrolled components.
28
+ *
29
+ * - If `value` is provided, the component is controlled and must be updated via `onChange`.
30
+ * - If `value` is not provided, the component manages its own state internally.
31
+ *
32
+ * Returns a tuple: [value, setValue].
33
+ *
34
+ * @template T - The type of the value.
35
+ * @param props - The props for controlling the state.
36
+ * @returns [T, Dispatch<SetStateAction<T>>] - The current value and a setter function.
37
+ *
38
+ * @example
39
+ * const [value, setValue] = useControllableState({
40
+ * value: props.value,
41
+ * defaultValue: 0,
42
+ * onChange: props.onChange,
43
+ * });
44
+ */
45
+ declare function useControllableState<T>(props: UseControllableStateProps<T>): [T, React.Dispatch<React.SetStateAction<T>>];
46
+
47
+ export { type UseControllableStateProps, useControllableProp, useControllableState };
package/dist/index.js ADDED
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ useControllableProp: () => useControllableProp,
24
+ useControllableState: () => useControllableState
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+ var import_use_callback_ref = require("@kopexa/use-callback-ref");
28
+ var import_react = require("react");
29
+ function useControllableProp(prop, state) {
30
+ const controlled = typeof prop !== "undefined";
31
+ const value = controlled ? prop : state;
32
+ return (0, import_react.useMemo)(() => [controlled, value], [controlled, value]);
33
+ }
34
+ function useControllableState(props) {
35
+ const {
36
+ value: valueProp,
37
+ defaultValue,
38
+ onChange,
39
+ shouldUpdate = (prev, next) => prev !== next
40
+ } = props;
41
+ const onChangeProp = (0, import_use_callback_ref.useCallbackRef)(onChange);
42
+ const shouldUpdateProp = (0, import_use_callback_ref.useCallbackRef)(shouldUpdate);
43
+ const [uncontrolledState, setUncontrolledState] = (0, import_react.useState)(defaultValue);
44
+ const controlled = valueProp !== void 0;
45
+ const value = controlled ? valueProp : uncontrolledState;
46
+ const setValue = (0, import_use_callback_ref.useCallbackRef)(
47
+ (next) => {
48
+ const setter = next;
49
+ const nextValue = typeof next === "function" ? setter(value) : next;
50
+ if (!shouldUpdateProp(value, nextValue)) {
51
+ return;
52
+ }
53
+ if (!controlled) {
54
+ setUncontrolledState(nextValue);
55
+ }
56
+ onChangeProp(nextValue);
57
+ },
58
+ [controlled, onChangeProp, value, shouldUpdateProp]
59
+ );
60
+ return [value, setValue];
61
+ }
62
+ // Annotate the CommonJS export names for ESM import in node:
63
+ 0 && (module.exports = {
64
+ useControllableProp,
65
+ useControllableState
66
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,40 @@
1
+ // src/index.ts
2
+ import { useCallbackRef } from "@kopexa/use-callback-ref";
3
+ import { useMemo, useState } from "react";
4
+ function useControllableProp(prop, state) {
5
+ const controlled = typeof prop !== "undefined";
6
+ const value = controlled ? prop : state;
7
+ return useMemo(() => [controlled, value], [controlled, value]);
8
+ }
9
+ function useControllableState(props) {
10
+ const {
11
+ value: valueProp,
12
+ defaultValue,
13
+ onChange,
14
+ shouldUpdate = (prev, next) => prev !== next
15
+ } = props;
16
+ const onChangeProp = useCallbackRef(onChange);
17
+ const shouldUpdateProp = useCallbackRef(shouldUpdate);
18
+ const [uncontrolledState, setUncontrolledState] = useState(defaultValue);
19
+ const controlled = valueProp !== void 0;
20
+ const value = controlled ? valueProp : uncontrolledState;
21
+ const setValue = useCallbackRef(
22
+ (next) => {
23
+ const setter = next;
24
+ const nextValue = typeof next === "function" ? setter(value) : next;
25
+ if (!shouldUpdateProp(value, nextValue)) {
26
+ return;
27
+ }
28
+ if (!controlled) {
29
+ setUncontrolledState(nextValue);
30
+ }
31
+ onChangeProp(nextValue);
32
+ },
33
+ [controlled, onChangeProp, value, shouldUpdateProp]
34
+ );
35
+ return [value, setValue];
36
+ }
37
+ export {
38
+ useControllableProp,
39
+ useControllableState
40
+ };
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@kopexa/use-controllable-state",
3
+ "version": "0.0.0-canary-20250718183330",
4
+ "description": "A hook to provide a controllable state",
5
+ "keywords": [
6
+ "use-controllable-state"
7
+ ],
8
+ "author": "Kopexa <hello@kopexa.com>",
9
+ "homepage": "https://kopexa.com",
10
+ "license": "MIT",
11
+ "main": "dist/index.js",
12
+ "sideEffects": false,
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/kopexa-grc/sight.git",
22
+ "directory": "packages/hooks/use-controllable-state"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/kopexa-grc/sight/issues"
26
+ },
27
+ "peerDependencies": {
28
+ "react": ">=19.0.0-rc.0"
29
+ },
30
+ "dependencies": {
31
+ "@kopexa/use-callback-ref": "0.0.0-canary-20250718183330"
32
+ },
33
+ "clean-package": "../../../clean-package.config.json",
34
+ "tsup": {
35
+ "clean": true,
36
+ "target": "es2019",
37
+ "format": [
38
+ "cjs",
39
+ "esm"
40
+ ]
41
+ },
42
+ "module": "dist/index.mjs",
43
+ "types": "dist/index.d.ts",
44
+ "exports": {
45
+ ".": {
46
+ "types": "./dist/index.d.ts",
47
+ "import": "./dist/index.mjs",
48
+ "require": "./dist/index.js"
49
+ },
50
+ "./package.json": "./package.json"
51
+ },
52
+ "scripts": {
53
+ "build": "tsup src --dts",
54
+ "build:fast": "tsup src",
55
+ "dev": "pnpm build:fast --watch",
56
+ "clean": "rimraf dist .turbo",
57
+ "typecheck": "tsc --noEmit"
58
+ }
59
+ }