@cleanweb/react 2.1.0 → 2.1.1-beta.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.
Files changed (42) hide show
  1. package/README.md +1 -1
  2. package/build/base/merged-state.d.ts +4 -0
  3. package/build/base/merged-state.js +4 -0
  4. package/build/base/methods.d.ts +15 -5
  5. package/build/base/methods.js +74 -11
  6. package/build/base/state/class.d.ts +2 -0
  7. package/build/base/state/class.js +2 -5
  8. package/build/base/state/hook-types.d.ts +25 -1
  9. package/build/base/state/hooks.d.ts +8 -2
  10. package/build/base/state/hooks.js +8 -2
  11. package/build/base/state/index.d.ts +4 -0
  12. package/build/base/state/index.js +28 -1
  13. package/build/classy/class/index.d.ts +13 -3
  14. package/build/classy/class/index.js +11 -2
  15. package/build/classy/instance/index.d.ts +27 -5
  16. package/build/classy/instance/index.js +21 -8
  17. package/build/classy/instance/mount-callbacks.d.ts +1 -0
  18. package/build/classy/instance/mount-callbacks.js +1 -0
  19. package/build/classy/instance/types/hook.d.ts +3 -2
  20. package/build/classy/logic/index.d.ts +28 -6
  21. package/build/classy/logic/index.js +69 -12
  22. package/build/classy/logic/types/hook.d.ts +3 -4
  23. package/build/docs-src/api/base-classes.d.ts +3 -0
  24. package/build/docs-src/api/base-classes.js +9 -0
  25. package/build/docs-src/api/index.d.ts +13 -0
  26. package/build/docs-src/api/index.js +44 -0
  27. package/build/docs-src/api/references.d.ts +5 -0
  28. package/build/docs-src/api/references.js +31 -0
  29. package/build/globals.d.ts +7 -6
  30. package/build/helpers/hmr.d.ts +18 -0
  31. package/build/helpers/hmr.js +48 -0
  32. package/build/helpers/index.d.ts +11 -0
  33. package/build/helpers/index.js +14 -0
  34. package/build/helpers/rerender.d.ts +4 -0
  35. package/build/helpers/rerender.js +4 -0
  36. package/build/helpers/type-guards.d.ts +1 -0
  37. package/build/helpers/type-guards.js +9 -0
  38. package/package.json +11 -2
  39. /package/build/{classy/class → helpers}/use-component/index.d.ts +0 -0
  40. /package/build/{classy/class → helpers}/use-component/index.js +0 -0
  41. /package/build/{classy/class → helpers}/use-component/types.d.ts +0 -0
  42. /package/build/{classy/class → helpers}/use-component/types.js +0 -0
package/README.md CHANGED
@@ -55,7 +55,7 @@ const Button = (props) => {
55
55
 
56
56
  > **Note:** Each top-level key in your initial state object gets a separate call to `React.useState`, and `state.put[key]()` is a proxy for the setter function returned from `useState`. So using this hook is fundamentally the same as calling `useState` directly for each value. What `useCleanState` provides is a way to unify those values and a convenient API for updating them.
57
57
 
58
- [Read the `useCleanState` docs]() for more details.
58
+ [Read the `useCleanState` docs](https://cleanjsweb.github.io/neat-react) for more details.
59
59
 
60
60
  ### Methods
61
61
  The `useMethods` hook lets you manage the closures that your component uses in a separate class, keeping the body of the component clean and easier to read. With `useMethods`, your functions are not recreated on every render. Yet, every method of your component is guaranteed to always have access to the latest props and state without the need for a dependencty array.
@@ -12,5 +12,9 @@ declare class MergedState<TState extends object> {
12
12
  constructor(initialState: TState);
13
13
  putMany: (newValues: Partial<TState>) => void;
14
14
  }
15
+ /**
16
+ * Similar to {@link useCleanState},
17
+ * but uses a single `useState` call for all keys.
18
+ */
15
19
  export declare const useMergedState: <TState extends object>(initialState: TState) => MergedState<TState> & TState;
16
20
  export {};
@@ -70,6 +70,10 @@ var MergedState = /** @class */ (function () {
70
70
  });
71
71
  return MergedState;
72
72
  }());
73
+ /**
74
+ * Similar to {@link useCleanState},
75
+ * but uses a single `useState` call for all keys.
76
+ */
73
77
  var useMergedState = function (initialState) {
74
78
  var cleanState = (0, react_1.useRef)((0, react_1.useMemo)(function () {
75
79
  return new MergedState(initialState);
@@ -1,6 +1,12 @@
1
+ /**
2
+ * @module ComponentMethods
3
+ */
1
4
  import type { TCleanState, TStateData } from './state';
2
5
  /**
3
- * Base class for a class that holds methods intended for use in a function component.
6
+ * @summary
7
+ * Base class for a class that holds methods for a function component.
8
+ *
9
+ * @remarks
4
10
  * These methods will have access to the components state and props via
5
11
  * `this.state` and `this.props` respectively.
6
12
  *
@@ -8,18 +14,22 @@ import type { TCleanState, TStateData } from './state';
8
14
  */
9
15
  export declare class ComponentMethods<TProps extends object = {}, TState extends TStateData | null = null> {
10
16
  readonly props: TProps;
11
- state: TState extends TStateData ? TCleanState<TState> : null;
17
+ readonly state: TState extends TStateData ? TCleanState<TState> : null;
18
+ _hmrPreserveKeys: Array<keyof this | (string & {})>;
19
+ _onHmrUpdate: <TInstance extends this>(oldInstance: TInstance) => void;
12
20
  }
13
21
  type UseMethods = {
14
22
  <Class extends typeof ComponentMethods<object, object>>(Methods: Class & Constructor<InstanceType<Class>>, props: InstanceType<Class>['props'], state: InstanceType<Class>['state']): InstanceType<Class>;
15
23
  <Class extends typeof ComponentMethods<object, null>>(Methods: Class & Constructor<InstanceType<Class>>, props: InstanceType<Class>['props'], state?: null): InstanceType<Class>;
16
- <Class extends typeof ComponentMethods<HardEmptyObject, null>>(Methods: Class & Constructor<InstanceType<Class>>): InstanceType<Class>;
24
+ <Class extends typeof ComponentMethods<NeverObject, null>>(Methods: Class & Constructor<InstanceType<Class>>): InstanceType<Class>;
17
25
  };
18
26
  /**
27
+ * @summary
19
28
  * Returns an instance of the provided class,
20
29
  * with the state and props arguments added as instance members.
21
30
  *
22
- * `state` must be an instance of `CleanState` created with {@link useCleanState}.
31
+ * @remarks
32
+ * `state` should be an instance of `CleanState` created with {@link useCleanState}.
23
33
  */
24
34
  declare const useMethods: UseMethods;
25
35
  export { useMethods };
@@ -29,7 +39,7 @@ export { useMethods };
29
39
 
30
40
  type t = keyof typeof a;
31
41
 
32
- class MyMethods extends ComponentMethods<WeakEmptyObject, null> {
42
+ class MyMethods extends ComponentMethods<EmptyObject, null> {
33
43
  // static getInitialState = () => ({});
34
44
  };
35
45
 
@@ -1,10 +1,27 @@
1
1
  "use strict";
2
+ /**
3
+ * @module ComponentMethods
4
+ */
5
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
6
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
7
+ if (ar || !(i in from)) {
8
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
9
+ ar[i] = from[i];
10
+ }
11
+ }
12
+ return to.concat(ar || Array.prototype.slice.call(from));
13
+ };
2
14
  Object.defineProperty(exports, "__esModule", { value: true });
3
15
  exports.useMethods = exports.ComponentMethods = void 0;
16
+ // JSDoc references
17
+ var helpers_1 = require("../helpers");
4
18
  // Values
5
19
  var react_1 = require("react");
6
20
  /**
7
- * Base class for a class that holds methods intended for use in a function component.
21
+ * @summary
22
+ * Base class for a class that holds methods for a function component.
23
+ *
24
+ * @remarks
8
25
  * These methods will have access to the components state and props via
9
26
  * `this.state` and `this.props` respectively.
10
27
  *
@@ -12,16 +29,19 @@ var react_1 = require("react");
12
29
  */
13
30
  var ComponentMethods = /** @class */ (function () {
14
31
  function ComponentMethods() {
32
+ this._hmrPreserveKeys = [];
15
33
  }
16
34
  return ComponentMethods;
17
35
  }());
18
36
  exports.ComponentMethods = ComponentMethods;
19
37
  ;
20
38
  /**
39
+ * @summary
21
40
  * Returns an instance of the provided class,
22
41
  * with the state and props arguments added as instance members.
23
42
  *
24
- * `state` must be an instance of `CleanState` created with {@link useCleanState}.
43
+ * @remarks
44
+ * `state` should be an instance of `CleanState` created with {@link useCleanState}.
25
45
  */
26
46
  var useMethods = function () {
27
47
  var args = [];
@@ -33,16 +53,59 @@ var useMethods = function () {
33
53
  // causing the instance to be unexpectedly recreated in the middle of the component's lifecycle.
34
54
  // But useRef and useState values appear to always be preserved whenever this happens.
35
55
  // So those two are the only cross-render-persistence methods we can consider safe.
36
- // @todo Provide a way for users to reflect updated methods code on the existing instance after HMR.
37
- var methods = (0, react_1.useRef)((0, react_1.useMemo)(function () {
38
- return new Methods();
39
- }, [])).current;
40
- /** A proxy variable to allow typechecking of the assignment to methods.props despite the need for "readonly" error suppression. */
41
- var _propsProxy_;
56
+ // In production, we only use the latestInstance the first time, and it's ignored every other time.
57
+ // This means changing the class at runtime will have no effect in production.
58
+ // latestInstance is only extracted into a separate variable for use in dev mode during HMR.
59
+ var latestInstance = (0, react_1.useMemo)(function () { return new Methods(); }, [Methods]);
60
+ var instanceRef = (0, react_1.useRef)(latestInstance);
61
+ if (process.env.NODE_ENV === 'development') {
62
+ var rerender_1 = (0, helpers_1.useRerender)();
63
+ (0, react_1.useEffect)(function () {
64
+ var _a;
65
+ if (instanceRef.current === latestInstance)
66
+ return;
67
+ console.log([
68
+ 'HMR-updated component class detected.',
69
+ 'Creating a new instance with the updated class.',
70
+ 'All stateful values will be copied over.\n\n',
71
+ 'Note that this mechanism only works in the `development` environment during HMR.',
72
+ 'In production, the class argument will be ignored after the first render.\n\n',
73
+ 'If this wasn\'t an HMR update, you should refactor your code to make sure',
74
+ 'all clean-react hooks receive the same class object on every render.'
75
+ ].join());
76
+ var oldInstance = instanceRef.current;
77
+ var hmrPreserveKeys = __spreadArray(__spreadArray([], ((_a = latestInstance._hmrPreserveKeys) !== null && _a !== void 0 ? _a : []), true), [
78
+ 'state', 'props', 'hooks',
79
+ ], false);
80
+ hmrPreserveKeys.forEach(function (_key) {
81
+ var key = _key;
82
+ // @ts-expect-error We're assigning to readonly properties. Also, Typescript doesn't know that the type of the left and right side will always match, due to the dynamic access.
83
+ latestInstance[key] = oldInstance[key];
84
+ });
85
+ latestInstance._onHmrUpdate(oldInstance);
86
+ Reflect.ownKeys(oldInstance).forEach(function (_key) {
87
+ var key = _key;
88
+ delete oldInstance[key];
89
+ });
90
+ Object.setPrototypeOf(oldInstance, latestInstance);
91
+ instanceRef.current = latestInstance;
92
+ rerender_1();
93
+ });
94
+ }
95
+ var methods = instanceRef.current;
96
+ /**
97
+ * A proxy variable to allow typechecking of the assignment
98
+ * to a readonly property,
99
+ * despite the need for "readonly" error suppression.
100
+ */
101
+ var _propsProxy;
102
+ /** @see {@link _propsProxy} */
103
+ var _stateProxy;
104
+ // @ts-expect-error
105
+ methods.props = (_propsProxy = props);
42
106
  // @ts-expect-error
43
- methods.props = (_propsProxy_ = props);
44
107
  if (state)
45
- methods.state = state;
108
+ methods.state = (_stateProxy = state);
46
109
  return methods;
47
110
  };
48
111
  exports.useMethods = useMethods;
@@ -52,7 +115,7 @@ exports.useMethods = useMethods;
52
115
 
53
116
  type t = keyof typeof a;
54
117
 
55
- class MyMethods extends ComponentMethods<WeakEmptyObject, null> {
118
+ class MyMethods extends ComponentMethods<EmptyObject, null> {
56
119
  // static getInitialState = () => ({});
57
120
  };
58
121
 
@@ -1,4 +1,5 @@
1
1
  import { ICleanStateClass, ICleanStateConstructor, PutState } from './class-types';
2
+ /** @internal */
2
3
  export declare class CleanStateBase<TState extends Record<string, any>> {
3
4
  readonly reservedKeys: string[];
4
5
  readonly valueKeys: string[];
@@ -11,4 +12,5 @@ export declare class CleanStateBase<TState extends Record<string, any>> {
11
12
  get initialState(): TState;
12
13
  readonly putMany: (newValues: Partial<TState>) => void;
13
14
  }
15
+ /** @internal */
14
16
  export declare const CleanState: ICleanStateConstructor & ICleanStateClass;
@@ -13,6 +13,7 @@ var __assign = (this && this.__assign) || function () {
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
14
  exports.CleanState = exports.CleanStateBase = void 0;
15
15
  var react_1 = require("react");
16
+ /** @internal */
16
17
  var CleanStateBase = /** @class */ (function () {
17
18
  function CleanStateBase(initialState) {
18
19
  var _this = this;
@@ -83,20 +84,16 @@ var CleanStateBase = /** @class */ (function () {
83
84
  var _a;
84
85
  // @todo Make state updates accessible immediately. Use state.staged to access the scheduled updates.
85
86
  var setter;
86
- // @todo Support SetStateAction callback signature in state.put(...);
87
87
  _a = retrieveState(_this.initialState[key]), _this._values_[key] = _a[0], setter = _a[1];
88
88
  _this._setters_[key] = (function (valueOrCallback) {
89
89
  // this._staged_[key] = value;
90
90
  setter(valueOrCallback);
91
91
  });
92
92
  });
93
- /* Object.entries<TUseStateArray<TState>>(stateAndSetters).forEach(([key, responseFromUseState]) => {
94
- [this._values_[key], this._setters_[key]] = responseFromUseState;
95
- }); */
96
- // return this;
97
93
  };
98
94
  return CleanStateBase;
99
95
  }());
100
96
  exports.CleanStateBase = CleanStateBase;
101
97
  ;
98
+ /** @internal */
102
99
  exports.CleanState = CleanStateBase;
@@ -1,12 +1,36 @@
1
1
  import { CleanStateBase } from './class';
2
+ /**
3
+ * Base type for an `initialState` object.
4
+ * It is a regular object type, with some reserved keys excluded.
5
+ *
6
+ * @_category Types
7
+ */
2
8
  export type TStateData = object & {
3
9
  [Key in keyof CleanStateBase<{}>]?: never;
4
10
  };
5
- /** @see https://github.com/cleanjsweb/neat-react#clean-state */
11
+ /**
12
+ * Describes a `CleanState` object instantiated with an `initialState`
13
+ * object of type `TState`.
14
+ *
15
+ * @typeParam TState - The type of your `initialState` object.
16
+ *
17
+ * @_category Types
18
+ */
6
19
  export type TCleanState<TState extends TStateData> = (CleanStateBase<TState> & Omit<TState, keyof CleanStateBase<{}>>);
20
+ /**
21
+ * Takes a `TCleanState` type and returns the `initialState` type
22
+ * associated with the provided `TCleanState`.
23
+ *
24
+ * This is useful to isolated the type of your actual state data without
25
+ * any of the reserved keys provided by the Clean State utility.
26
+ */
7
27
  export type ExtractCleanStateData<YourCleanState extends CleanStateBase<{}>> = Omit<YourCleanState, keyof CleanStateBase<{}>>;
8
28
  type StateInitFunction = (...args: any[]) => object;
9
29
  type StateInit = object | StateInitFunction;
10
30
  export type TInitialState<Initializer extends StateInit> = Initializer extends (...args: any[]) => (infer TState extends object) ? TState : Initializer;
31
+ /**
32
+ * @typeParam TInit - An initial state object, or a function that
33
+ * returns the initial state object.
34
+ */
11
35
  export type TUseCleanState = <TInit extends StateInit>(_initialState: TInit, ...props: TInit extends (...args: infer TProps extends any[]) => (infer TState extends object) ? TProps : []) => TCleanState<TInitialState<TInit>>;
12
36
  export {};
@@ -1,6 +1,12 @@
1
1
  import { TUseCleanState } from './hook-types';
2
2
  /**
3
- * Creates a state object, which includes the provided values, and helper methods for
4
- * updating those values and automatically rerendering your component's UI accordingly.
3
+ * @summary
4
+ * Creates a state object, which includes the provided values,
5
+ * as well as helper methods for updating those values and automatically
6
+ * rerendering your component's UI to reflect said updates.
7
+ *
8
+ * @remarks
9
+ * Uses {@link React.useState} under the hook, with a separate call
10
+ * to `useState` for each top-level key in the provided object.
5
11
  */
6
12
  export declare const useCleanState: TUseCleanState;
@@ -4,8 +4,14 @@ exports.useCleanState = void 0;
4
4
  var react_1 = require("react");
5
5
  var class_1 = require("./class");
6
6
  /**
7
- * Creates a state object, which includes the provided values, and helper methods for
8
- * updating those values and automatically rerendering your component's UI accordingly.
7
+ * @summary
8
+ * Creates a state object, which includes the provided values,
9
+ * as well as helper methods for updating those values and automatically
10
+ * rerendering your component's UI to reflect said updates.
11
+ *
12
+ * @remarks
13
+ * Uses {@link React.useState} under the hook, with a separate call
14
+ * to `useState` for each top-level key in the provided object.
9
15
  */
10
16
  var useCleanState = function (_initialState) {
11
17
  var props = [];
@@ -1,4 +1,8 @@
1
+ /**
2
+ * @module CleanState
3
+ */
1
4
  import '../../globals';
2
5
  export { CleanState } from './class';
3
6
  export { useCleanState } from './hooks';
4
7
  export type { TCleanState, TStateData, ExtractCleanStateData } from './hook-types';
8
+ export * as MergedState from '../../base/merged-state';
@@ -1,8 +1,35 @@
1
1
  "use strict";
2
+ /**
3
+ * @module CleanState
4
+ */
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || function (mod) {
22
+ if (mod && mod.__esModule) return mod;
23
+ var result = {};
24
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
25
+ __setModuleDefault(result, mod);
26
+ return result;
27
+ };
2
28
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useCleanState = exports.CleanState = void 0;
29
+ exports.MergedState = exports.useCleanState = exports.CleanState = void 0;
4
30
  require("../../globals");
5
31
  var class_1 = require("./class");
6
32
  Object.defineProperty(exports, "CleanState", { enumerable: true, get: function () { return class_1.CleanState; } });
7
33
  var hooks_1 = require("./hooks");
8
34
  Object.defineProperty(exports, "useCleanState", { enumerable: true, get: function () { return hooks_1.useCleanState; } });
35
+ exports.MergedState = __importStar(require("../../base/merged-state"));
@@ -1,14 +1,24 @@
1
1
  import type { Extractor } from './types/extractor';
2
2
  import { ComponentInstance } from '../instance';
3
+ import { TPropsBase } from '../logic';
3
4
  /**
4
- * A superset of {@link ComponentInstance} that allows defining your
5
+ * @summary
6
+ * A modern class component for React that is fully compatible with
7
+ * React Hooks and all of React's latest features.
8
+ *
9
+ * It is a superset of {@link ComponentInstance} that allows defining your
5
10
  * component's JSX template directly inside the class.
6
11
  *
12
+ * @remarks
13
+ * In essence, this is a class wrapper around an underlying function component.
14
+ * It acts as syntactic sugar, allowing you to create a regular function
15
+ * component, while writing in an object-oriented format.
16
+ *
7
17
  * This is designed to closely resemble the old {@link React.Component} class,
8
18
  * making it easier to migrate older class components to the newer hooks-based system
9
19
  * with little to no changes to their existing semantics/implementation.
10
20
  */
11
- export declare class ClassComponent<TProps extends object = WeakEmptyObject> extends ComponentInstance<TProps> {
21
+ export declare class ClassComponent<TProps extends TPropsBase = null> extends ComponentInstance<TProps> {
12
22
  /**
13
23
  * Analogous to {@link React.Component.render}. A function that returns
14
24
  * your component's JSX template.
@@ -81,7 +91,7 @@ export declare class ClassComponent<TProps extends object = WeakEmptyObject> ext
81
91
  static readonly FC: Extractor;
82
92
  }
83
93
  export { ClassComponent as Component };
84
- export { Use } from './use-component';
94
+ export { Use } from '../../helpers/use-component';
85
95
  /** /
86
96
  testing: {
87
97
  const a: object = {b: ''};
@@ -21,9 +21,18 @@ var instance_1 = require("../instance");
21
21
  var function_name_1 = require("./utils/function-name");
22
22
  var rerender_1 = require("../../helpers/rerender");
23
23
  /**
24
- * A superset of {@link ComponentInstance} that allows defining your
24
+ * @summary
25
+ * A modern class component for React that is fully compatible with
26
+ * React Hooks and all of React's latest features.
27
+ *
28
+ * It is a superset of {@link ComponentInstance} that allows defining your
25
29
  * component's JSX template directly inside the class.
26
30
  *
31
+ * @remarks
32
+ * In essence, this is a class wrapper around an underlying function component.
33
+ * It acts as syntactic sugar, allowing you to create a regular function
34
+ * component, while writing in an object-oriented format.
35
+ *
27
36
  * This is designed to closely resemble the old {@link React.Component} class,
28
37
  * making it easier to migrate older class components to the newer hooks-based system
29
38
  * with little to no changes to their existing semantics/implementation.
@@ -128,7 +137,7 @@ var ClassComponent = /** @class */ (function (_super) {
128
137
  }(instance_1.ComponentInstance));
129
138
  exports.ClassComponent = ClassComponent;
130
139
  exports.Component = ClassComponent;
131
- var use_component_1 = require("./use-component");
140
+ var use_component_1 = require("../../helpers/use-component");
132
141
  Object.defineProperty(exports, "Use", { enumerable: true, get: function () { return use_component_1.Use; } });
133
142
  /** /
134
143
  testing: {
@@ -1,8 +1,6 @@
1
1
  import type { UseInstance } from './types/hook';
2
- import { ComponentLogic } from '../../classy/logic';
2
+ import { ComponentLogic, type TPropsBase } from '../../classy/logic';
3
3
  type AsyncAllowedEffectCallback = () => Awaitable<IVoidFunction>;
4
- /** An empty function. It returns (void) without performing any operations. */
5
- export declare const noOp: () => void;
6
4
  /**
7
5
  * A superset of {@link ComponentLogic} that adds support for lifecycle methods.
8
6
  * This provides a declarative API for working with your React function component's lifecycle,
@@ -10,7 +8,7 @@ export declare const noOp: () => void;
10
8
  *
11
9
  * @see https://github.com/cleanjsweb/neat-react#lifecycle-useinstance
12
10
  */
13
- export declare class ComponentInstance<TProps extends object = NonPrimitive> extends ComponentLogic<TProps> {
11
+ export declare class ComponentInstance<TProps extends TPropsBase = null> extends ComponentLogic<TProps> {
14
12
  /**
15
13
  * Runs only _before_ first render,
16
14
  * i.e before the component instance is mounted.
@@ -60,11 +58,35 @@ export declare class ComponentInstance<TProps extends object = NonPrimitive> ext
60
58
  */
61
59
  cleanUp: IVoidFunction;
62
60
  }
61
+ /**
62
+ * @summary
63
+ * Enables full separation of concerns between a React components template
64
+ * and all of the logic that drives it.
65
+ *
66
+ * Returns an instance that fully represents a logical instance
67
+ * of a rendered React component, with the exception of the JSX template itself.
68
+ *
69
+ * This means the all of your components logic and lifecycle handlers
70
+ * can be externalized from the function component itself,
71
+ * and defined in a separate class.
72
+ *
73
+ * @remarks
74
+ * The provided class should be a subclass of {@link ComponentInstance}.
75
+ *
76
+ * @privateRemarks
77
+ * To ensure successful type checking, the second parameter must be written with spread syntax.
78
+ * Likely because of the `exactOptionalPropertyTypes` config option turned on,
79
+ * and `UseInstance` using an empty tuple in its rest parameter type, attempting to simply
80
+ * retrieve the second argument directly causes an error when that argument is passed on to `useLogic`.
81
+ * But directly working with the rest array bypasses the problem. Also note that the issue persists even when
82
+ * the second param is given `{}` as a default follow to account for the empty tuple case. TypeScript
83
+ * just wants us to use the rest parameter explicitly by force.
84
+ */
63
85
  export declare const useInstance: UseInstance;
64
86
  export {};
65
87
  /** /
66
88
  testing: {
67
- class A extends ComponentInstance<WeakEmptyObject> {
89
+ class A extends ComponentInstance<EmptyObject> {
68
90
  getInitialState = (p?: object) => ({putan: ''});
69
91
  // k = this.props.o
70
92
  am = this.state['_initialValues_'];
@@ -15,13 +15,11 @@ var __extends = (this && this.__extends) || (function () {
15
15
  };
16
16
  })();
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.useInstance = exports.ComponentInstance = exports.noOp = void 0;
18
+ exports.useInstance = exports.ComponentInstance = void 0;
19
19
  var react_1 = require("react");
20
20
  var logic_1 = require("../../classy/logic");
21
21
  var mount_callbacks_1 = require("./mount-callbacks");
22
- /** An empty function. It returns (void) without performing any operations. */
23
- var noOp = function () { };
24
- exports.noOp = noOp;
22
+ var helpers_1 = require("../../helpers");
25
23
  /**
26
24
  * A superset of {@link ComponentLogic} that adds support for lifecycle methods.
27
25
  * This provides a declarative API for working with your React function component's lifecycle,
@@ -54,7 +52,7 @@ var ComponentInstance = /** @class */ (function (_super) {
54
52
  *
55
53
  * Uses `useEffect()` under the hood.
56
54
  */
57
- _this.onMount = function () { return exports.noOp; };
55
+ _this.onMount = function () { return helpers_1.noOp; };
58
56
  /**
59
57
  * Runs _before_ every render cycle, including the first.
60
58
  * Useful for logic that is involved in determining what to render.
@@ -73,7 +71,7 @@ var ComponentInstance = /** @class */ (function (_super) {
73
71
  *
74
72
  * Returns a cleanup function.
75
73
  */
76
- _this.onRender = function () { return exports.noOp; };
74
+ _this.onRender = function () { return helpers_1.noOp; };
77
75
  /**
78
76
  * Runs when the component is unmounted.
79
77
  * It is called _after_ the cleanup function returned by onMount.
@@ -92,7 +90,22 @@ var ComponentInstance = /** @class */ (function (_super) {
92
90
  }(logic_1.ComponentLogic));
93
91
  exports.ComponentInstance = ComponentInstance;
94
92
  ;
95
- /*
93
+ /**
94
+ * @summary
95
+ * Enables full separation of concerns between a React components template
96
+ * and all of the logic that drives it.
97
+ *
98
+ * Returns an instance that fully represents a logical instance
99
+ * of a rendered React component, with the exception of the JSX template itself.
100
+ *
101
+ * This means the all of your components logic and lifecycle handlers
102
+ * can be externalized from the function component itself,
103
+ * and defined in a separate class.
104
+ *
105
+ * @remarks
106
+ * The provided class should be a subclass of {@link ComponentInstance}.
107
+ *
108
+ * @privateRemarks
96
109
  * To ensure successful type checking, the second parameter must be written with spread syntax.
97
110
  * Likely because of the `exactOptionalPropertyTypes` config option turned on,
98
111
  * and `UseInstance` using an empty tuple in its rest parameter type, attempting to simply
@@ -150,7 +163,7 @@ var useInstance = function () {
150
163
  exports.useInstance = useInstance;
151
164
  /** /
152
165
  testing: {
153
- class A extends ComponentInstance<WeakEmptyObject> {
166
+ class A extends ComponentInstance<EmptyObject> {
154
167
  getInitialState = (p?: object) => ({putan: ''});
155
168
  // k = this.props.o
156
169
  am = this.state['_initialValues_'];
@@ -1,4 +1,5 @@
1
1
  import { ComponentInstance } from '.';
2
2
  type UseMountCallbacks = <TInstance extends ComponentInstance>(instance: TInstance) => void;
3
+ /** @internal */
3
4
  export declare const useMountCallbacks: UseMountCallbacks;
4
5
  export {};
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useMountCallbacks = void 0;
4
4
  var react_1 = require("react");
5
5
  var mount_state_1 = require("../../helpers/mount-state");
6
+ /** @internal */
6
7
  var useMountCallbacks = function (instance) {
7
8
  var _a;
8
9
  var isMounted = (0, mount_state_1.useMountState)();
@@ -1,6 +1,7 @@
1
+ import { TPropsBase } from '../../../classy/logic';
1
2
  import { ComponentInstance } from '..';
2
- type UIClassParam = typeof ComponentInstance<any>;
3
- type UIProplessClassParam = typeof ComponentInstance<HardEmptyObject>;
3
+ type UIClassParam = typeof ComponentInstance<NonNullable<TPropsBase>>;
4
+ type UIProplessClassParam = typeof ComponentInstance<null>;
4
5
  export type UseInstance = {
5
6
  <Class extends UIProplessClassParam>(Methods: Class): InstanceType<Class>;
6
7
  <Class extends UIClassParam>(Methods: Class, props: InstanceType<Class>['props']): InstanceType<Class>;
@@ -1,7 +1,15 @@
1
1
  import type { TCleanState } from '../../base/state';
2
2
  import type { UseLogic } from './types/hook';
3
- export type HardEmpty = HardEmptyObject;
4
- export type WeakEmpty = WeakEmptyObject;
3
+ /**
4
+ * The base type for the props type argument.
5
+ * This is not the type the `props` property itself.
6
+ * It merely defines the type constraint for the type argument
7
+ * passed when extending any of the advanced external classes.
8
+ *
9
+ * It differs from the type of the actual props object
10
+ * in that it accepts null for components that don't take any props.
11
+ */
12
+ export type TPropsBase = NonPrimitive | null;
5
13
  /**
6
14
  * Base class for a class that holds methods intended for use in a function component,
7
15
  * as well as a static method for initializing state.
@@ -13,16 +21,22 @@ export type WeakEmpty = WeakEmptyObject;
13
21
  * React hooks within this class.
14
22
  *
15
23
  * Call the {@link useLogic} hook inside your function component to instantiate the class.
24
+ *
25
+ * @typeParam TProps - {@include ./types/tprops.md}
26
+ *
27
+ * @group ComponentLogic
28
+ * @_category External Classes
16
29
  */
17
- export declare class ComponentLogic<TProps extends object = NonPrimitive> {
30
+ export declare class ComponentLogic<TProps extends TPropsBase = null> {
18
31
  /**
19
32
  * A {@link TCleanState | `CleanState`} object.
20
33
  * Holds all of your component's state,
21
34
  * and methods for conveniently manipulating those values.
35
+ * Initialiazed with the object returned from your `getInitialState` method.
22
36
  */
23
37
  readonly state: TCleanState<ReturnType<this['getInitialState']>>;
24
- /** The props pass into your component at the time of rendering. */
25
- readonly props: TProps;
38
+ /** The props passed into your component at the time of rendering. */
39
+ readonly props: TProps extends null ? EmptyObject : TProps;
26
40
  /**
27
41
  * Values received from the hooks your component consumes.
28
42
  * This holds the latest copy of the object returned by
@@ -34,7 +48,7 @@ export declare class ComponentLogic<TProps extends object = NonPrimitive> {
34
48
  * It receives the initial `props` object and should return
35
49
  * an object with the initial values for your component's state.
36
50
  */
37
- getInitialState: (props?: TProps) => object;
51
+ getInitialState: (props?: this["props"]) => object;
38
52
  /**
39
53
  * Call React hooks from here. If your component needs
40
54
  * access to values return from the hooks you call,
@@ -44,7 +58,15 @@ export declare class ComponentLogic<TProps extends object = NonPrimitive> {
44
58
  * your component class.
45
59
  */
46
60
  useHooks: () => object | void;
61
+ _hmrPreserveKeys: Array<keyof this | (string & {})>;
62
+ _onHmrUpdate: <TInstance extends this>(oldInstance: TInstance) => void;
47
63
  }
64
+ /**
65
+ * Returns an instance of the provided class, which holds methods for your component and
66
+ * encapsulates hook calls with the special {@link ComponentLogic.useHooks | `useHooks`} method.
67
+ *
68
+ * The class argument must be a subclass of {@link ComponentLogic}.
69
+ */
48
70
  export declare const useLogic: UseLogic;
49
71
  /** /
50
72
  testing: {
@@ -1,4 +1,13 @@
1
1
  "use strict";
2
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
3
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
4
+ if (ar || !(i in from)) {
5
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
6
+ ar[i] = from[i];
7
+ }
8
+ }
9
+ return to.concat(ar || Array.prototype.slice.call(from));
10
+ };
2
11
  Object.defineProperty(exports, "__esModule", { value: true });
3
12
  exports.useLogic = exports.ComponentLogic = void 0;
4
13
  var react_1 = require("react");
@@ -14,6 +23,11 @@ var state_1 = require("../../base/state");
14
23
  * React hooks within this class.
15
24
  *
16
25
  * Call the {@link useLogic} hook inside your function component to instantiate the class.
26
+ *
27
+ * @typeParam TProps - {@include ./types/tprops.md}
28
+ *
29
+ * @group ComponentLogic
30
+ * @_category External Classes
17
31
  */
18
32
  var ComponentLogic = /** @class */ (function () {
19
33
  function ComponentLogic() {
@@ -32,11 +46,18 @@ var ComponentLogic = /** @class */ (function () {
32
46
  * your component class.
33
47
  */
34
48
  this.useHooks = function () { };
49
+ this._hmrPreserveKeys = [];
35
50
  }
36
51
  return ComponentLogic;
37
52
  }());
38
53
  exports.ComponentLogic = ComponentLogic;
39
54
  ;
55
+ /**
56
+ * Returns an instance of the provided class, which holds methods for your component and
57
+ * encapsulates hook calls with the special {@link ComponentLogic.useHooks | `useHooks`} method.
58
+ *
59
+ * The class argument must be a subclass of {@link ComponentLogic}.
60
+ */
40
61
  var useLogic = function () {
41
62
  var _a;
42
63
  var args = [];
@@ -44,21 +65,57 @@ var useLogic = function () {
44
65
  args[_i] = arguments[_i];
45
66
  }
46
67
  var Logic = args[0], _b = args[1], props = _b === void 0 ? {} : _b;
47
- var self = (0, react_1.useRef)((0, react_1.useMemo)(function () {
48
- return new Logic();
49
- }, [])).current;
50
- /** A proxy variable to allow typechecking of the assignment to `self.props` despite the need for "readonly" error suppression. */
51
- var _propsProxy_;
52
- /** A proxy variable to allow typechecking of the assignment to `self.state` despite the need for "readonly" error suppression. */
53
- var _stateProxy_;
54
- /** A proxy variable to allow typechecking of the assignment to `self.hooks` despite the need for "readonly" error suppression. */
55
- var _hooksProxy_;
68
+ // In production, we only use the latestInstance the first time, and it's ignored every other time.
69
+ // This means changing the class at runtime will have no effect in production.
70
+ // latestInstance is only extracted into a separate variable for use in dev mode during HMR.
71
+ var latestInstance = (0, react_1.useMemo)(function () { return new Logic(); }, [Logic]);
72
+ var instanceRef = (0, react_1.useRef)(latestInstance);
73
+ if (process.env.NODE_ENV === 'development') {
74
+ if (instanceRef.current !== latestInstance) {
75
+ console.log([
76
+ 'HMR-updated component class detected.',
77
+ 'Creating a new instance with the updated class.',
78
+ 'All stateful values will be copied over.\n\n',
79
+ 'Note that this mechanism only works in the `development` environment during HMR.',
80
+ 'In production, the class argument will be ignored after the first render.\n\n',
81
+ 'If this wasn\'t an HMR update, you should refactor your code to make sure',
82
+ 'all clean-react hooks receive the same class object on every render.'
83
+ ].join());
84
+ var oldInstance_1 = instanceRef.current;
85
+ var hmrPreserveKeys = __spreadArray(__spreadArray([], latestInstance._hmrPreserveKeys, true), [
86
+ 'state', 'props', 'hooks',
87
+ ], false);
88
+ hmrPreserveKeys.forEach(function (_key) {
89
+ var key = _key;
90
+ // @ts-expect-error We're assigning to readonly properties. Also, Typescript doesn't know that the type of the left and right side will always match, due to the dynamic access.
91
+ latestInstance[key] = oldInstance_1[key];
92
+ });
93
+ latestInstance._onHmrUpdate(oldInstance_1);
94
+ instanceRef.current = latestInstance;
95
+ Reflect.ownKeys(oldInstance_1).forEach(function (_key) {
96
+ var key = _key;
97
+ delete oldInstance_1[key];
98
+ });
99
+ Object.setPrototypeOf(oldInstance_1, latestInstance);
100
+ }
101
+ }
102
+ var self = instanceRef.current;
103
+ /**
104
+ * A proxy variable to allow typechecking of the assignment
105
+ * to a readonly property,
106
+ * despite the need for "readonly" error suppression.
107
+ */
108
+ var _propsProxy;
109
+ /** @see {@link _propsProxy} */
110
+ var _stateProxy;
111
+ /** @see {@link _propsProxy} */
112
+ var _hooksProxy;
56
113
  // @ts-expect-error
57
- self.props = (_propsProxy_ = props);
114
+ self.props = (_propsProxy = props);
58
115
  // @ts-expect-error
59
- self.state = (_stateProxy_ = (0, state_1.useCleanState)(self.getInitialState, props));
116
+ self.state = (_stateProxy = (0, state_1.useCleanState)(self.getInitialState, props));
60
117
  // @ts-expect-error
61
- self.hooks = (_hooksProxy_ = (_a = self.useHooks()) !== null && _a !== void 0 ? _a : {});
118
+ self.hooks = (_hooksProxy = (_a = self.useHooks()) !== null && _a !== void 0 ? _a : {});
62
119
  return self;
63
120
  };
64
121
  exports.useLogic = useLogic;
@@ -1,10 +1,9 @@
1
- import type { ComponentLogic } from '..';
1
+ import type { ComponentLogic, TPropsBase } from '..';
2
2
  /*************************************
3
3
  * # Hooks *
4
4
  **************************************/
5
- /** */
6
- type ULClassParam = typeof ComponentLogic<any>;
7
- type ULProplessClassParam = typeof ComponentLogic<HardEmptyObject>;
5
+ type ULClassParam = typeof ComponentLogic<NonNullable<TPropsBase>>;
6
+ type ULProplessClassParam = typeof ComponentLogic<null>;
8
7
  export type UseLogic = {
9
8
  <Class extends ULProplessClassParam>(Methods: Class): InstanceType<Class>;
10
9
  <Class extends ULClassParam>(Methods: Class, props: InstanceType<Class>['props']): InstanceType<Class>;
@@ -0,0 +1,3 @@
1
+ export { ComponentMethods } from '../../base/methods';
2
+ export { ComponentLogic } from '../../classy/logic';
3
+ export { ComponentInstance } from '../../classy/instance';
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ComponentInstance = exports.ComponentLogic = exports.ComponentMethods = void 0;
4
+ var methods_1 = require("../../base/methods");
5
+ Object.defineProperty(exports, "ComponentMethods", { enumerable: true, get: function () { return methods_1.ComponentMethods; } });
6
+ var logic_1 = require("../../classy/logic");
7
+ Object.defineProperty(exports, "ComponentLogic", { enumerable: true, get: function () { return logic_1.ComponentLogic; } });
8
+ var instance_1 = require("../../classy/instance");
9
+ Object.defineProperty(exports, "ComponentInstance", { enumerable: true, get: function () { return instance_1.ComponentInstance; } });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * API Reference
3
+ * @module API
4
+ */
5
+ export { useCleanState } from '../../base/state';
6
+ export { useMethods } from '../../base/methods';
7
+ export { useLogic } from '../../classy/logic';
8
+ export { useInstance } from '../../classy/instance';
9
+ export { ClassComponent } from '../../classy/class';
10
+ /** @namespace */
11
+ export * as BaseClasses from './base-classes';
12
+ export * as Helpers from '../../helpers';
13
+ export * as References from './references';
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ /**
3
+ * API Reference
4
+ * @module API
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || function (mod) {
23
+ if (mod && mod.__esModule) return mod;
24
+ var result = {};
25
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
26
+ __setModuleDefault(result, mod);
27
+ return result;
28
+ };
29
+ Object.defineProperty(exports, "__esModule", { value: true });
30
+ exports.References = exports.Helpers = exports.BaseClasses = exports.ClassComponent = exports.useInstance = exports.useLogic = exports.useMethods = exports.useCleanState = void 0;
31
+ var state_1 = require("../../base/state");
32
+ Object.defineProperty(exports, "useCleanState", { enumerable: true, get: function () { return state_1.useCleanState; } });
33
+ var methods_1 = require("../../base/methods");
34
+ Object.defineProperty(exports, "useMethods", { enumerable: true, get: function () { return methods_1.useMethods; } });
35
+ var logic_1 = require("../../classy/logic");
36
+ Object.defineProperty(exports, "useLogic", { enumerable: true, get: function () { return logic_1.useLogic; } });
37
+ var instance_1 = require("../../classy/instance");
38
+ Object.defineProperty(exports, "useInstance", { enumerable: true, get: function () { return instance_1.useInstance; } });
39
+ var class_1 = require("../../classy/class");
40
+ Object.defineProperty(exports, "ClassComponent", { enumerable: true, get: function () { return class_1.ClassComponent; } });
41
+ /** @namespace */
42
+ exports.BaseClasses = __importStar(require("./base-classes"));
43
+ exports.Helpers = __importStar(require("../../helpers"));
44
+ exports.References = __importStar(require("./references"));
@@ -0,0 +1,5 @@
1
+ export * as $CleanState from '../../base/state';
2
+ export * as $ComponentMethods from '../../base/methods';
3
+ export * as ComponentLogic from '../../classy/logic';
4
+ export * as ComponentInstance from '../../classy/instance';
5
+ export * as ClassComponent from '../../classy/class';
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.ClassComponent = exports.ComponentInstance = exports.ComponentLogic = exports.$ComponentMethods = exports.$CleanState = void 0;
27
+ exports.$CleanState = __importStar(require("../../base/state"));
28
+ exports.$ComponentMethods = __importStar(require("../../base/methods"));
29
+ exports.ComponentLogic = __importStar(require("../../classy/logic"));
30
+ exports.ComponentInstance = __importStar(require("../../classy/instance"));
31
+ exports.ClassComponent = __importStar(require("../../classy/class"));
@@ -8,7 +8,7 @@ declare const UniqueSecretSymbolKey: unique symbol;
8
8
  // '': '',
9
9
  }
10
10
 
11
- let TT: WeakEmptyObject = {};
11
+ let TT: EmptyObject = {};
12
12
  TT = tt;
13
13
  }/**/
14
14
  declare global {
@@ -46,17 +46,17 @@ declare global {
46
46
  * Having a single key allows the object to throw type errors
47
47
  * of the form:
48
48
  * ```
49
- * Type `A` has no properties in common with `WeakEmptyObject`.
49
+ * Type `A` has no properties in common with `EmptyObject`.
50
50
  * ```
51
51
  * This may provide a slightly stricter type checking than simply
52
52
  * using the non-nullish (`{}`) or non-primitive (`object`)
53
53
  * built-in types.
54
54
  *
55
- * Note: `WeakEmptyObject` is not assignable to `HardEmptyObject`
55
+ * Note: `EmptyObject` is not assignable to `NeverObject`
56
56
  * because it has a key whose value type includes `undefined`,
57
- * but `HardEmptyObject` keys can only have a type of `never`.
57
+ * but `NeverObject` keys can only have a type of `never`.
58
58
  */
59
- interface WeakEmptyObject {
59
+ interface EmptyObject {
60
60
  [UniqueSecretSymbolKey]?: never;
61
61
  }
62
62
  /**
@@ -65,7 +65,7 @@ declare global {
65
65
  * from ever being stored on the object. The object is therefore
66
66
  * guaranteed to always be empty.
67
67
  */
68
- interface HardEmptyObject {
68
+ interface NeverObject {
69
69
  [key: keyof any]: never;
70
70
  }
71
71
  type valueof<TObject> = TObject[keyof TObject];
@@ -77,6 +77,7 @@ declare global {
77
77
  }
78
78
  namespace NodeJS {
79
79
  interface ProcessEnv {
80
+ NODE_ENV: 'development' | 'production' | 'test';
80
81
  }
81
82
  }
82
83
  }
@@ -0,0 +1,18 @@
1
+ interface OoreInstanceForHMR {
2
+ hmrPreserveKeys?: Array<keyof this>;
3
+ hmrConstructorParams?: any[];
4
+ hmrWillUpdate: (newInstance: OoreInstanceForHMR) => void;
5
+ hmrDidUpdate: (oldInstance: OoreInstanceForHMR) => void;
6
+ }
7
+ interface OoreClassForHMR {
8
+ new (...params: any[]): OoreInstanceForHMR;
9
+ instances: InstanceType<OoreClassForHMR>[];
10
+ }
11
+ declare global {
12
+ interface Window {
13
+ handleOoreHmr: (Class: OoreClassForHMR, module: {
14
+ hot?: __WebpackModuleApi.Hot;
15
+ }, name?: string) => void;
16
+ }
17
+ }
18
+ export {};
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
3
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
4
+ if (ar || !(i in from)) {
5
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
6
+ ar[i] = from[i];
7
+ }
8
+ }
9
+ return to.concat(ar || Array.prototype.slice.call(from));
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ // window.__OBJECT_ORIENTED_REACT__ = window.__OBJECT_ORIENTED_REACT__ || {};
13
+ if (process.env.NODE_ENV === 'development' && typeof window !== 'undefined') {
14
+ window.handleOoreHmr = function (Class, module, name) {
15
+ var _a, _b;
16
+ var classKey = name || Class.name;
17
+ (_a = module.hot) === null || _a === void 0 ? void 0 : _a.dispose(function (data) {
18
+ data.ooreClasses[classKey] = { instances: Class.instances };
19
+ });
20
+ console.log('New module instances:', Class.instances.length);
21
+ var Classes = (_b = module.hot) === null || _b === void 0 ? void 0 : _b.data.ooreClasses;
22
+ if (!Classes[classKey]) {
23
+ console.warn([
24
+ "New component \"".concat(Class.name, "\" detected.\n\nIf this"),
25
+ 'component was renamed, oore will be unable to match it',
26
+ 'with the previous name, and a reload may be necessary.',
27
+ 'You can pass the name argument to the hmr handler',
28
+ 'to ensure a consistent identifier for your component',
29
+ 'class across renames and therefore bypass this issue.',
30
+ ].join(' '));
31
+ return;
32
+ }
33
+ var instances = Classes[classKey].instances;
34
+ instances.forEach(function (instance) {
35
+ var _a, _b, _c, _d;
36
+ var newInstance = new (Class.bind.apply(Class, __spreadArray([void 0], ((_a = instance.hmrConstructorParams) !== null && _a !== void 0 ? _a : []), false)))();
37
+ (_b = instance.hmrPreserveKeys) === null || _b === void 0 ? void 0 : _b.forEach(function (_key) {
38
+ var key = _key;
39
+ // @ts-expect-error Typescript doesn't realize that since both sides have the same key, the value type being assigned will always match the property type it's assigned to.
40
+ newInstance[key] = instance[key];
41
+ });
42
+ // instance.hotReplace(newInstance);
43
+ (_c = instance.hmrWillUpdate) === null || _c === void 0 ? void 0 : _c.call(instance, newInstance);
44
+ (_d = newInstance.hmrDidUpdate) === null || _d === void 0 ? void 0 : _d.call(newInstance, instance);
45
+ });
46
+ console.log('Updated module instances (max 3):', Class.instances.slice(0, 3));
47
+ };
48
+ }
@@ -1,2 +1,13 @@
1
+ /**
2
+ * <!-- @ mergeModuleWith API -->
3
+ * @module Helpers
4
+ */
1
5
  export * from './mount-state';
2
6
  export * from './rerender';
7
+ export * from './use-component';
8
+ export * from './type-guards';
9
+ /**
10
+ * An empty function.
11
+ * It returns (void) without performing any operations.
12
+ */
13
+ export declare const noOp: () => void;
@@ -1,4 +1,8 @@
1
1
  "use strict";
2
+ /**
3
+ * <!-- @ mergeModuleWith API -->
4
+ * @module Helpers
5
+ */
2
6
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
7
  if (k2 === undefined) k2 = k;
4
8
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -14,5 +18,15 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
18
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
19
  };
16
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.noOp = void 0;
17
22
  __exportStar(require("./mount-state"), exports);
18
23
  __exportStar(require("./rerender"), exports);
24
+ __exportStar(require("./use-component"), exports);
25
+ // export * from './hmr';
26
+ __exportStar(require("./type-guards"), exports);
27
+ /**
28
+ * An empty function.
29
+ * It returns (void) without performing any operations.
30
+ */
31
+ var noOp = function () { };
32
+ exports.noOp = noOp;
@@ -1 +1,5 @@
1
+ /**
2
+ * Returns a function that can be called to manually trigger
3
+ * a rerender of your component.
4
+ */
1
5
  export declare const useRerender: () => () => void;
@@ -3,6 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useRerender = void 0;
4
4
  var mount_state_1 = require("../helpers/mount-state");
5
5
  var react_1 = require("react");
6
+ /**
7
+ * Returns a function that can be called to manually trigger
8
+ * a rerender of your component.
9
+ */
6
10
  var useRerender = function () {
7
11
  var isMounted = (0, mount_state_1.useMountState)();
8
12
  // Skip the value, we don't need it. Grab just the setter function.
@@ -0,0 +1 @@
1
+ export declare const canIndex: (key: keyof any, targetObject: any) => key is keyof typeof targetObject;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.canIndex = void 0;
4
+ var canIndex = function (key, targetObject) {
5
+ return Reflect
6
+ .ownKeys(targetObject)
7
+ .includes(typeof key === 'number' ? "".concat(key) : key);
8
+ };
9
+ exports.canIndex = canIndex;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleanweb/react",
3
- "version": "2.1.0",
3
+ "version": "2.1.1-beta.1",
4
4
  "description": "A suite of helpers for writing cleaner React function components.",
5
5
  "engines": {
6
6
  "node": ">=18"
@@ -18,12 +18,15 @@
18
18
  "exports": {
19
19
  ".": "./build/classy/index.js",
20
20
  "./base": "./build/base/index.js",
21
- "./helpers": "./build/helpers/index.js"
21
+ "./helpers": "./build/helpers/index.js",
22
+ "./all": "./build/index.js"
22
23
  },
23
24
  "scripts": {
24
25
  "prebuild": "rimraf ./build",
25
26
  "build": "tsc && tsc-alias",
27
+ "serve-docs": "serve docs",
26
28
  "postbuild": "copyfiles tsconfig.json build",
29
+ "build:docs": "typedoc",
27
30
  "_": "",
28
31
  "prepublishOnly": "npm run build",
29
32
  "publish:patch": "npm version patch && npm publish",
@@ -61,6 +64,7 @@
61
64
  "@babel/preset-typescript": "^7.26.0",
62
65
  "@types/node": "20.14.10",
63
66
  "@types/react": "^16",
67
+ "@types/webpack-env": "^1.18.8",
64
68
  "babel-preset-react-app": "^10.0.1",
65
69
  "copyfiles": "^2.4.1",
66
70
  "eslint": "^9.15.0",
@@ -68,7 +72,12 @@
68
72
  "eslint-plugin-react": "^7.37.2",
69
73
  "globals": "^15.12.0",
70
74
  "rimraf": "^6.0.1",
75
+ "serve": "^14.2.4",
71
76
  "tsc-alias": "1.8.10",
77
+ "typedoc": "latest",
78
+ "typedoc-plugin-coverage": "^3.4.1",
79
+ "typedoc-plugin-markdown": "^4.4.1",
80
+ "typedoc-plugin-mdn-links": "^4.0.13",
72
81
  "typescript": "^5.6.2"
73
82
  },
74
83
  "peerDependencies": {