@cleanweb/react 1.0.11 → 1.1.1-beta.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -26,7 +26,8 @@ var MergedState = /** @class */ (function () {
26
26
  this.valueKeys = [];
27
27
  this._initialValues_ = __assign({}, initialState);
28
28
  this._values_ = __assign({}, initialState);
29
- Object.keys(initialState).forEach(function (key) {
29
+ Object.keys(initialState).forEach(function (_key) {
30
+ var key = _key;
30
31
  if (_this.reservedKeys.includes(key)) {
31
32
  throw new Error("The name \"".concat(key, "\" is reserved by CleanState and cannot be used to index state variables. Please use a different key."));
32
33
  }
@@ -3,7 +3,6 @@ export declare class ComponentMethods<TState extends object, TProps extends obje
3
3
  state: TCleanState<TState>;
4
4
  props: TProps;
5
5
  }
6
- type ComponentMethodsConstructor = typeof ComponentMethods<any, any>;
7
- type UseMethods = <MethodsClass extends ComponentMethodsConstructor>(Methods: MethodsClass, state: InstanceType<MethodsClass>['state'], props: InstanceType<MethodsClass>['props']) => InstanceType<MethodsClass>;
6
+ type UseMethods = <TMethods extends ComponentMethods<any, any>>(Methods: Constructor<TMethods>, state: TMethods['state'], props: TMethods['props']) => TMethods;
8
7
  export declare const useMethods: UseMethods;
9
8
  export {};
@@ -14,10 +14,10 @@ var useMethods = function (Methods, state, props) {
14
14
  // causing the instance to be unexpectedly recreated in the middle of the components lifecycle.
15
15
  // But useRef and useState values appear to always be preserved whenever this happens.
16
16
  // So those two are the only cross-render-persistence methods we can consider safe.
17
- var methods = (0, react_1.useMemo)(function () {
17
+ var methods = (0, react_1.useRef)((0, react_1.useMemo)(function () {
18
18
  // See useLogic implementation for a discussion of this type assertion.
19
19
  return new Methods();
20
- }, []);
20
+ }, [])).current;
21
21
  methods.state = state;
22
22
  methods.props = props;
23
23
  return methods;
@@ -1,4 +1,12 @@
1
- declare class CleanStateBase<TState extends object> {
1
+ /**
2
+ * Returns a value that is false before the component has been mounted,
3
+ * then true during all subsequent rerenders.
4
+ */
5
+ export declare const useMountState: () => boolean;
6
+ type PutState<TState extends object> = {
7
+ [Key in keyof TState]: React.Dispatch<React.SetStateAction<TState[Key]>>;
8
+ };
9
+ declare class CleanStateBase<TState extends Record<string, any>> {
2
10
  reservedKeys: string[];
3
11
  valueKeys: string[];
4
12
  private _values_;
@@ -6,20 +14,16 @@ declare class CleanStateBase<TState extends object> {
6
14
  private _setters_;
7
15
  constructor(initialState: TState);
8
16
  static update: <TState_1 extends object>(this: CleanStateBase<TState_1>) => void;
9
- get put(): { [Key in keyof TState]: (value: TState[Key]) => void; };
17
+ get put(): PutState<TState>;
10
18
  get initialState(): TState;
11
19
  putMany: (newValues: Partial<TState>) => void;
12
20
  }
13
21
  type TCleanStateInstance<TState extends object> = TState & CleanStateBase<TState>;
14
22
  export type TCleanState<TState extends object> = TCleanStateInstance<TState>;
15
- type TFunctionType = (...args: any) => object;
16
- type TInitialState<StateParamType> = StateParamType extends TFunctionType ? ReturnType<StateParamType> : StateParamType;
17
- type StateInitParameters<StateInitializer> = StateInitializer extends TFunctionType ? Parameters<StateInitializer> : [];
18
- type UseCleanState = <StateInitializer extends TFunctionType | object>(_initialState: StateInitializer, ...props: StateInitParameters<StateInitializer>) => TCleanStateInstance<TInitialState<StateInitializer>>;
19
- export declare const useCleanState: UseCleanState;
20
- /**
21
- * Returns a value that is false before the component has been mounted,
22
- * then true during all subsequent rerenders.
23
- */
24
- export declare const useMountState: () => boolean;
23
+ export type TState<YourCleanState extends CleanStateBase<{}>> = Omit<YourCleanState, keyof CleanStateBase<{}>>;
24
+ type StateInitFunction = (...args: any[]) => object;
25
+ type StateInit = object | StateInitFunction;
26
+ type TInitialState<Initializer extends StateInit> = Initializer extends (...args: any[]) => (infer TState extends object) ? TState : Initializer;
27
+ type TUseCleanState = <TInit extends StateInit>(_initialState: TInit, ...props: TInit extends (...args: infer TProps extends any[]) => (infer TState extends object) ? TProps : []) => TCleanStateInstance<TInitialState<TInit>>;
28
+ export declare const useCleanState: TUseCleanState;
25
29
  export {};
@@ -11,8 +11,26 @@ var __assign = (this && this.__assign) || function () {
11
11
  return __assign.apply(this, arguments);
12
12
  };
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
- exports.useMountState = exports.useCleanState = void 0;
14
+ exports.useCleanState = exports.useMountState = void 0;
15
15
  var react_1 = require("react");
16
+ /**
17
+ * Returns a value that is false before the component has been mounted,
18
+ * then true during all subsequent rerenders.
19
+ */
20
+ var useMountState = function () {
21
+ /**
22
+ * This must not be a stateful value. It should not be the cause of a rerender.
23
+ * It merely provides information about the render count,
24
+ * without influencing that count itself.
25
+ * So `mounted` should never be set with `useState`.
26
+ */
27
+ var mounted = (0, react_1.useRef)(false);
28
+ (0, react_1.useEffect)(function () {
29
+ mounted.current = true;
30
+ }, []);
31
+ return mounted.current;
32
+ };
33
+ exports.useMountState = useMountState;
16
34
  var CleanStateBase = /** @class */ (function () {
17
35
  function CleanStateBase(initialState) {
18
36
  var _this = this;
@@ -81,7 +99,14 @@ var CleanStateBase = /** @class */ (function () {
81
99
  var retrieveState = react_1.useState;
82
100
  this.valueKeys.forEach(function (key) {
83
101
  var _a;
84
- _a = retrieveState(_this.initialState[key]), _this._values_[key] = _a[0], _this._setters_[key] = _a[1];
102
+ // @todo Make state updates accessible immediately. Use state.staged to access the scheduled updates.
103
+ var setter;
104
+ // @todo Support SetStateAction callback signature in state.put(...);
105
+ _a = retrieveState(_this.initialState[key]), _this._values_[key] = _a[0], setter = _a[1];
106
+ _this._setters_[key] = (function (valueOrCallback) {
107
+ // this._staged_[key] = value;
108
+ setter(valueOrCallback);
109
+ });
85
110
  });
86
111
  /* Object.entries<TUseStateArray<TState>>(stateAndSetters).forEach(([key, responseFromUseState]) => {
87
112
  [this._values_[key], this._setters_[key]] = responseFromUseState;
@@ -97,27 +122,32 @@ var useCleanState = function (_initialState) {
97
122
  for (var _i = 1; _i < arguments.length; _i++) {
98
123
  props[_i - 1] = arguments[_i];
99
124
  }
100
- var initialState = typeof _initialState === 'function' ? (0, react_1.useMemo)(function () { return _initialState.apply(void 0, props); }, []) : _initialState;
101
- var cleanState = (0, react_1.useMemo)(function () { return new CleanState(initialState); }, []);
125
+ var mounted = (0, exports.useMountState)();
126
+ var initialState = typeof _initialState === 'function'
127
+ ? (0, react_1.useMemo)(function () { return _initialState.apply(void 0, props); }, [])
128
+ : _initialState;
129
+ ;
130
+ var freshInstance = {};
131
+ if (!mounted)
132
+ freshInstance = new CleanState(initialState);
133
+ if (!freshInstance.put)
134
+ throw new Error('useCleanState failed to initialized a state instance.');
135
+ var cleanState = (0, react_1.useRef)(freshInstance).current;
102
136
  CleanState.update.call(cleanState);
103
137
  return cleanState;
104
138
  };
105
139
  exports.useCleanState = useCleanState;
106
- /**
107
- * Returns a value that is false before the component has been mounted,
108
- * then true during all subsequent rerenders.
109
- */
110
- var useMountState = function () {
111
- /**
112
- * This must not be a stateful value. It should not be the cause of a rerender.
113
- * It merely provides information about the render count,
114
- * without influencing that count itself.
115
- * So `mounted` should never be set with `useState`.
116
- */
117
- var mounted = (0, react_1.useRef)(false);
118
- (0, react_1.useEffect)(function () {
119
- mounted.current = true;
120
- }, []);
121
- return mounted.current;
122
- };
123
- exports.useMountState = useMountState;
140
+ // Should be valid.
141
+ // useCleanState((a: number) => ({b: a.toString(), q: 1}), 6);
142
+ // useCleanState((a: boolean) => ({b: a.toString()}), true);
143
+ // useCleanState((a: number, c?: string) => ({ b: `${a}` }), 6);
144
+ // useCleanState((a: number, c?: string) => ({ b: `${a}` }), 6, 'word');
145
+ // useCleanState((a: number, c: string) => ({ b: a + c, f: true }), 6, 'text');
146
+ // useCleanState({ d: 5000 });
147
+ // Should fail.
148
+ // useCleanState((a: number) => ({b: a.toString(), q: 1}), 6, false);
149
+ // useCleanState((a: boolean) => ({b: a.toString()}));
150
+ // useCleanState((a: number, c?: string) => ({ b: `${a}` }), '6');
151
+ // useCleanState((a: number, c?: string) => ({ b: `${a}` }));
152
+ // useCleanState((a: number, c: string) => ({ b: a + c, f: true }), 6, 7);
153
+ // useCleanState({ d: 5000 }, true);
@@ -1,13 +1,12 @@
1
- import type { ReactElement } from 'react';
2
- import type { ComponentInstanceConstructor } from './instance';
1
+ import type { VoidFunctionComponent } from 'react';
2
+ import type { TComponentClass } from './logic';
3
3
  import { ComponentInstance } from './instance';
4
- type Obj = Record<string, any>;
5
- type IComponentConstructor = ComponentInstanceConstructor<any, any, any> & typeof ClassComponent<any, any, any>;
6
- export declare class ClassComponent<TState extends Obj, TProps extends Obj, THooks extends Obj> extends ComponentInstance<TState, TProps, THooks> {
7
- Render: () => ReactElement<any, any> | null;
8
- static FC: <IComponentType extends IComponentConstructor>(this: IComponentType, _Component?: IComponentType) => (props: InstanceType<IComponentType>["props"]) => ReactElement<any, any> | null;
4
+ type Extractor = <TComponent extends ClassComponent<object, object, object>>(this: TComponentClass<TComponent, typeof ClassComponent>, _Component?: TComponentClass<TComponent, typeof ClassComponent>) => VoidFunctionComponent;
5
+ export declare class ClassComponent<TState extends object = EmptyObject, TProps extends object = EmptyObject, THooks extends object = EmptyObject> extends ComponentInstance<TState, TProps, THooks> {
6
+ Render: VoidFunctionComponent<{}>;
7
+ static renderAs: 'component' | 'template';
8
+ static FC: Extractor;
9
9
  }
10
- type AnyFunction = (...args: any) => any;
11
10
  interface HookWrapperProps<THookFunction extends AnyFunction> {
12
11
  hook: THookFunction;
13
12
  argumentsList: Parameters<THookFunction>;
@@ -16,6 +16,7 @@ var __extends = (this && this.__extends) || (function () {
16
16
  })();
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.Use = exports.ClassComponent = void 0;
19
+ var jsx_runtime_1 = require("react/jsx-runtime");
19
20
  var react_1 = require("react");
20
21
  var instance_1 = require("./instance");
21
22
  /** Provide more useful stack traces for otherwise non-specific function names. */
@@ -31,37 +32,56 @@ var setFunctionName = function (func, newName) {
31
32
  console.warn(error);
32
33
  }
33
34
  };
35
+ // eslint-enable no-use-before-define
34
36
  var ClassComponent = /** @class */ (function (_super) {
35
37
  __extends(ClassComponent, _super);
36
38
  function ClassComponent() {
37
39
  return _super !== null && _super.apply(this, arguments) || this;
38
40
  }
41
+ ClassComponent.renderAs = 'component';
39
42
  ClassComponent.FC = function FC(_Component) {
40
- var Component = _Component || this;
43
+ var Component = _Component !== null && _Component !== void 0 ? _Component : this;
41
44
  var isClassComponentType = Component.prototype instanceof ClassComponent;
42
45
  if (!Component.getInitialState || !isClassComponentType)
43
46
  throw new Error('Attempted to initialize ClassComponent with invalid Class type. Either pass a class that extends ClassComponent to FC (e.g `export FC(MyComponent);`), or ensure it is called as a method on a ClassComponent constructor type (e.g `export MyComponent.FC()`).');
44
- var Wrapper = function (props) {
47
+ var Wrapper = function (props, context) {
45
48
  var Render = (0, instance_1.useInstance)(Component, props).Render;
46
49
  // Add calling component name to Render function name in stack traces.
47
- (0, react_1.useMemo)(function () { return setFunctionName(Render, "".concat(Component.name, " > Render")); }, [Render]);
50
+ (0, react_1.useMemo)(function () { return setFunctionName(Render, "".concat(Component.name, ".Render")); }, [Render]);
48
51
  /**
49
- * It may be impossible to set state within the body of Render,
52
+ * Normally a component can update it's own state in the "before-render" stage to
53
+ * skip DOM updates and trigger and immediate rerun of the rendering with the new state.
54
+ *
55
+ * It may be impossible to do this within the body of Render, if we call it as JSX here,
50
56
  * since technically, the Wrapper component owns the state and not the Render component.
51
- * Consider using this as a function call instead of JSX to avoid that.
52
- */
53
- // if (instance.renderAs === 'component') return <Render />;
54
- return Render();
57
+ * Using it as JSX establishes a component boundary, and React will throw an error if we try to set
58
+ * state in the "before-render" stage of `Render`, since it will be attempting to update it's parent's
59
+ * state (i.e `Wrapper` component) rather than it's own state.
60
+ *
61
+ * Consider using this as a function call instead of JSX to avoid that. This way, we avoid
62
+ * establishing a component boundary between `Wrapper` and `Render`.
63
+ *
64
+ * Although, since beforeRender() is called earlier from a hook, this is probably
65
+ * a non-issue. It will only force users to move their logic into `beforeRender` instead
66
+ * of doing it directly in `Render`. This might mean cleaner Render functions,
67
+ * so there's probably no real value lost if we keep the component boundary.
68
+ **/
69
+ if (Component.renderAs === 'template')
70
+ return Render({}, context);
71
+ // With the existence of useContext(),
72
+ // what exactly does the context argument to FunctionComponent represent?
73
+ // Do we need to find a way to pass that context value to <Render /> here?
74
+ return (0, jsx_runtime_1.jsx)(Render, {});
55
75
  };
56
76
  // Include calling component name in wrapper function name on stack traces.
57
- setFunctionName(Wrapper, "".concat(Component.name, " > ").concat(Wrapper.name));
77
+ setFunctionName(Wrapper, "".concat(Component.name, " < Wrapper")); // ${Wrapper.name}
58
78
  return Wrapper;
59
79
  };
60
80
  return ClassComponent;
61
81
  }(instance_1.ComponentInstance));
62
82
  exports.ClassComponent = ClassComponent;
63
- var Use = function (_a) {
64
- var useGenericHook = _a.hook, argumentsList = _a.argumentsList, onUpdate = _a.onUpdate;
83
+ var Use = function (params) {
84
+ var useGenericHook = params.hook, argumentsList = params.argumentsList, onUpdate = params.onUpdate;
65
85
  var output = useGenericHook.apply(void 0, argumentsList);
66
86
  (0, react_1.useEffect)(function () {
67
87
  onUpdate(output);
@@ -1,9 +1,8 @@
1
- import type { ComponentLogicConstructor } from './logic';
1
+ import type { TComponentClass } from './logic';
2
2
  import { ComponentLogic } from './logic';
3
- type Obj = Record<string, any>;
4
3
  type AsyncAllowedEffectCallback = () => Awaitable<IVoidFunction>;
5
4
  export declare const noOp: () => void;
6
- export declare class ComponentInstance<TState extends Obj = {}, TProps extends Obj = {}, THooks extends Obj = {}> extends ComponentLogic<TState, TProps, THooks> {
5
+ export declare class ComponentInstance<TState extends object = EmptyObject, TProps extends object = EmptyObject, THooks extends object = EmptyObject> extends ComponentLogic<TState, TProps, THooks> {
7
6
  /**
8
7
  * Runs only _before_ first render, i.e before the component instance is mounted.
9
8
  * Useful for logic that is involved in determining what to render.
@@ -48,10 +47,17 @@ export declare class ComponentInstance<TState extends Obj = {}, TProps extends O
48
47
  */
49
48
  cleanUp: IVoidFunction;
50
49
  }
51
- type ComponentClassBaseType<TState extends Obj = {}, TProps extends Obj = {}, THooks extends Obj = {}> = ComponentLogicConstructor<TState, TProps, THooks> & Constructor<ComponentInstance<TState, TProps, THooks>>;
52
- export interface ComponentInstanceConstructor<TState extends Obj = {}, TProps extends Obj = {}, THooks extends Obj = {}> extends ComponentClassBaseType<TState, TProps, THooks> {
53
- }
54
- type UseInstance = <TClass extends ComponentInstanceConstructor>(Class: TClass, props: InstanceType<TClass>['props']) => InstanceType<TClass>;
55
- export declare const useMountCallbacks: <TInstance extends ComponentInstance<any, any, any>>(instance: TInstance) => void;
50
+ type UseInstance = <TClass extends ComponentInstance<object, object, object>>(Class: TComponentClass<TClass>, ...props: valueof<TClass['props']> extends never ? ([] | [EmptyObject] | [TClass['props']]) : [TClass['props']]) => TClass;
51
+ /**
52
+ * To ensure successful type checking, the second parameter must be written with spread syntax.
53
+ * Likely because of the `exactOptionalPropertyTypes` config option turned on,
54
+ * and `UseInstance` using an empty tuple in its rest parameter type, attempting to simply
55
+ * retrieve the second argument directly causes an error when that argument is passed on to `useLogic`.
56
+ * But directly working with the rest array bypasses the problem. Also note that the issue persists even when
57
+ * the second param is given `{}` as a default follow to account for the empty tuple case. TypeScript
58
+ * just wants us to use the rest parameter explicitly by force.
59
+ */
56
60
  export declare const useInstance: UseInstance;
61
+ type UseMountCallbacks = <TInstance extends ComponentInstance<any, any, any>>(instance: TInstance) => void;
62
+ export declare const useMountCallbacks: UseMountCallbacks;
57
63
  export {};
@@ -14,8 +14,17 @@ var __extends = (this && this.__extends) || (function () {
14
14
  d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
15
15
  };
16
16
  })();
17
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
18
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
19
+ if (ar || !(i in from)) {
20
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
21
+ ar[i] = from[i];
22
+ }
23
+ }
24
+ return to.concat(ar || Array.prototype.slice.call(from));
25
+ };
17
26
  Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.useInstance = exports.useMountCallbacks = exports.ComponentInstance = exports.noOp = void 0;
27
+ exports.useMountCallbacks = exports.useInstance = exports.ComponentInstance = exports.noOp = void 0;
19
28
  var react_1 = require("react");
20
29
  var state_1 = require("../base/state");
21
30
  var logic_1 = require("./logic");
@@ -74,6 +83,56 @@ var ComponentInstance = /** @class */ (function (_super) {
74
83
  }(logic_1.ComponentLogic));
75
84
  exports.ComponentInstance = ComponentInstance;
76
85
  ;
86
+ /**
87
+ * To ensure successful type checking, the second parameter must be written with spread syntax.
88
+ * Likely because of the `exactOptionalPropertyTypes` config option turned on,
89
+ * and `UseInstance` using an empty tuple in its rest parameter type, attempting to simply
90
+ * retrieve the second argument directly causes an error when that argument is passed on to `useLogic`.
91
+ * But directly working with the rest array bypasses the problem. Also note that the issue persists even when
92
+ * the second param is given `{}` as a default follow to account for the empty tuple case. TypeScript
93
+ * just wants us to use the rest parameter explicitly by force.
94
+ */
95
+ var useInstance = function (Component) {
96
+ var _a;
97
+ var args = [];
98
+ for (var _i = 1; _i < arguments.length; _i++) {
99
+ args[_i - 1] = arguments[_i];
100
+ }
101
+ // useHooks.
102
+ var instance = logic_1.useLogic.apply(void 0, __spreadArray([Component], args, false)); // Must spread rest parameter, rather than passing a single `props` argument directly.
103
+ /**
104
+ * Argument of type '
105
+ * [
106
+ (valueof<TClass["props"]> extends never
107
+ ? [] | [CEmptyObject]
108
+ : [ TClass["props"] ]
109
+ )[0]
110
+ ]
111
+ ' is not assignable to parameter of type '
112
+ valueof<TClass["props"]> extends never
113
+ ? [] | [CEmptyObject] // | [undefined]
114
+ : [ TClass["props"] ]
115
+ '
116
+ */
117
+ // beforeMount, onMount, cleanUp.
118
+ // eslint-disable-next-line no-use-before-define
119
+ (0, exports.useMountCallbacks)(instance);
120
+ // beforeRender.
121
+ (_a = instance.beforeRender) === null || _a === void 0 ? void 0 : _a.call(instance);
122
+ // onRender.
123
+ (0, react_1.useEffect)(function () {
124
+ var _a;
125
+ var cleanupAfterRerender = (_a = instance.onRender) === null || _a === void 0 ? void 0 : _a.call(instance);
126
+ return function () {
127
+ if (typeof cleanupAfterRerender === 'function')
128
+ cleanupAfterRerender();
129
+ else
130
+ cleanupAfterRerender === null || cleanupAfterRerender === void 0 ? void 0 : cleanupAfterRerender.then(function (cleanUp) { return cleanUp === null || cleanUp === void 0 ? void 0 : cleanUp(); });
131
+ };
132
+ });
133
+ return instance;
134
+ };
135
+ exports.useInstance = useInstance;
77
136
  var useMountCallbacks = function (instance) {
78
137
  var _a;
79
138
  var mounted = (0, state_1.useMountState)();
@@ -99,25 +158,3 @@ var useMountCallbacks = function (instance) {
99
158
  }, []);
100
159
  };
101
160
  exports.useMountCallbacks = useMountCallbacks;
102
- var useInstance = function (Component, props) {
103
- var _a;
104
- // useHooks.
105
- var instance = (0, logic_1.useLogic)(Component, props);
106
- // beforeMount, onMount, cleanUp.
107
- (0, exports.useMountCallbacks)(instance);
108
- // beforeRender.
109
- (_a = instance.beforeRender) === null || _a === void 0 ? void 0 : _a.call(instance);
110
- // onRender.
111
- (0, react_1.useEffect)(function () {
112
- var _a;
113
- var cleanupAfterRerender = (_a = instance.onRender) === null || _a === void 0 ? void 0 : _a.call(instance);
114
- return function () {
115
- if (typeof cleanupAfterRerender === 'function')
116
- cleanupAfterRerender();
117
- else
118
- cleanupAfterRerender === null || cleanupAfterRerender === void 0 ? void 0 : cleanupAfterRerender.then(function (cleanUp) { return cleanUp === null || cleanUp === void 0 ? void 0 : cleanUp(); });
119
- };
120
- });
121
- return instance;
122
- };
123
- exports.useInstance = useInstance;
@@ -1,13 +1,22 @@
1
- import type { TCleanState } from '../base/state';
2
- export declare class ComponentLogic<TState extends object, TProps extends object, THooks extends object> {
1
+ import type { TCleanState, TState } from '../base/state';
2
+ export declare class ComponentLogic<TState extends object = EmptyObject, TProps extends object = EmptyObject, THooks extends object = EmptyObject> {
3
3
  state: TCleanState<TState>;
4
4
  props: TProps;
5
5
  hooks: THooks;
6
+ static getInitialState: IComponentClass['getInitialState'];
6
7
  useHooks?: () => THooks;
7
8
  }
8
- export interface ComponentLogicConstructor<TState extends object, TProps extends object, THooks extends object> extends Constructor<ComponentLogic<TState, TProps, THooks>> {
9
- getInitialState: (props?: TProps) => TState;
9
+ type CnstPrm = ConstructorParameters<typeof ComponentLogic>;
10
+ export interface IComponentClass<Instance extends ComponentLogic = ComponentLogic> {
11
+ new (...params: CnstPrm): Instance;
12
+ getInitialState: (props?: Instance['props']) => TState<Instance['state']>;
10
13
  }
11
- type UseLogic = <LogicClass extends ComponentLogicConstructor<{}, object, any>>(Methods: LogicClass, props?: InstanceType<LogicClass>['props']) => InstanceType<LogicClass>;
14
+ export type ComponentClassStatics<Instance extends ComponentLogic<object, object, object>> = {
15
+ getInitialState: (props?: Instance['props']) => TState<Instance['state']>;
16
+ };
17
+ export type TComponentClass<Instance extends ComponentLogic<object, object, object>, Statics extends ComponentClassStatics<Instance> = ComponentClassStatics<Instance>, Params extends CnstPrm = CnstPrm> = Statics & Constructor<Instance, Params>;
18
+ export interface IEmpty extends EmptyObject {
19
+ }
20
+ type UseLogic = <CLogic extends ComponentLogic<object, object, object>>(Methods: TComponentClass<CLogic>, ...props: valueof<CLogic['props']> extends never ? ([] | [EmptyObject] | [CLogic['props']]) : [CLogic['props']]) => CLogic;
12
21
  export declare const useLogic: UseLogic;
13
22
  export {};
@@ -1,4 +1,20 @@
1
1
  "use strict";
2
+ var __extends = (this && this.__extends) || (function () {
3
+ var extendStatics = function (d, b) {
4
+ extendStatics = Object.setPrototypeOf ||
5
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
6
+ function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
7
+ return extendStatics(d, b);
8
+ };
9
+ return function (d, b) {
10
+ if (typeof b !== "function" && b !== null)
11
+ throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
12
+ extendStatics(d, b);
13
+ function __() { this.constructor = d; }
14
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
15
+ };
16
+ })();
17
+ var _a;
2
18
  Object.defineProperty(exports, "__esModule", { value: true });
3
19
  exports.useLogic = exports.ComponentLogic = void 0;
4
20
  var react_1 = require("react");
@@ -10,22 +26,42 @@ var ComponentLogic = /** @class */ (function () {
10
26
  }());
11
27
  exports.ComponentLogic = ComponentLogic;
12
28
  ;
29
+ testing: {
30
+ var A = (_a = /** @class */ (function (_super) {
31
+ __extends(C, _super);
32
+ function C() {
33
+ return _super !== null && _super.apply(this, arguments) || this;
34
+ }
35
+ return C;
36
+ }(ComponentLogic)),
37
+ _a.getInitialState = function () { return ({}); },
38
+ _a);
39
+ A.getInitialState();
40
+ }
41
+ ;
13
42
  var useLogic = function (Methods, props) {
14
- var _a;
43
+ var _b;
15
44
  if (props === void 0) { props = {}; }
16
45
  var state = (0, state_1.useCleanState)(Methods.getInitialState, props);
17
- // There's apparently a bug? with Typescript that pegs the return type of "new Methods()" to "ComponentLogic<{}, {}, {}>",
18
- // completely ignoring the type specified for Methods in the function's type definition.
19
- // `new Methods()` should return whatever the InstanceType of TClass is, as that is the type explicitly specified for Methods.
20
- // Ignoring the specified type to gin up something else and then complain about it is quite weird.
21
- // Regardless, even when `extends ComponentLogicConstructor<TState, TProps, THooks>` is specified using generics instead of a set type,
22
- // the issue persists. Which is absurd since this should ensure that InstanceType<Class> should exactly match ComponentLogic<TState, TProps, THooks>
23
- var methods = (0, react_1.useMemo)(function () {
46
+ var methods = (0, react_1.useRef)((0, react_1.useMemo)(function () {
24
47
  return new Methods();
25
- }, []);
48
+ }, [])).current;
26
49
  methods.state = state;
27
50
  methods.props = props;
28
- methods.hooks = ((_a = methods.useHooks) === null || _a === void 0 ? void 0 : _a.call(methods)) || {};
51
+ methods.hooks = ((_b = methods.useHooks) === null || _b === void 0 ? void 0 : _b.call(methods)) || {};
29
52
  return methods;
30
53
  };
31
54
  exports.useLogic = useLogic;
55
+ testing: {
56
+ var a = { b: '' };
57
+ var MyComponentLogic = /** @class */ (function (_super) {
58
+ __extends(MyComponentLogic, _super);
59
+ function MyComponentLogic() {
60
+ return _super !== null && _super.apply(this, arguments) || this;
61
+ }
62
+ MyComponentLogic.getInitialState = function () { return ({}); };
63
+ return MyComponentLogic;
64
+ }(ComponentLogic));
65
+ ;
66
+ (0, exports.useLogic)(MyComponentLogic);
67
+ }
@@ -1,56 +1,67 @@
1
-
2
- type Optional<
3
- BaseType,
4
- AllowNull extends boolean = true
5
- > = (
6
- AllowNull extends true
7
- ? BaseType | undefined | null
8
- : BaseType | undefined
9
- )
10
-
11
- type Awaitable<Type> = Type | Promise<Type>;
12
-
13
- type Constructor<
14
- TInstance extends any = any,
15
- TParams extends any[] = never[]
16
- > = new (...args: TParams) => TInstance
17
-
18
-
19
1
  /**
2
+ * @file
3
+ * This file is an "Ambient declarations file". The types defined here are available globally.
4
+ * More info here: https://stackoverflow.com/a/73389225/985454
5
+ *
6
+ * Don't use `import` and `export` in this file directly! It breaks ambience.
7
+ * To import external types in an ambient declarations file (this file) use the following:
8
+ *
20
9
  * @example
21
- * ```js
22
- * const getNumber: AsyncFunction<number> = async () => {
23
- * return 5;
10
+ * declare type React = typeof import('react');
11
+ *
12
+ * To contribute ambient declarations from any file, even non-ambient ones, use this:
13
+ *
14
+ * @example
15
+ * declare global {
16
+ * interface Window {
17
+ * ethereum: any
18
+ * }
24
19
  * }
25
- * ```
26
- */
27
- declare type AsyncFunction<
28
- TReturnValue extends any = void,
29
- Params extends any[] = never[]
30
- > = (...params: Params) => Promise<TReturnValue>
31
-
32
- /**
33
- * A function that takes no arguments and returns nothing.
34
- * Pass a type argument to set whether `async` and/or `sync` functions are allowed.
35
- */
36
- declare interface IVoidFunction<AsyncType extends 'async' | 'sync' | 'both' = 'both'> {
37
- (): AsyncType extends 'async' ? Promise<void>
38
- : AsyncType extends 'sync' ? void
39
- : Promise<void> | void
40
- }
41
-
42
- declare type FunctionType = (...args: any) => any;
43
-
44
-
45
- declare interface Window {
46
- }
47
-
48
- declare namespace JSX {
49
- interface IntrinsicElements {
50
- }
51
- }
52
-
53
- declare namespace NodeJS {
54
- interface ProcessEnv {
55
- }
20
+ **/
21
+ /** */
22
+ declare global {
23
+ type Optional<BaseType, AllowNull extends boolean = true> = (AllowNull extends true ? BaseType | undefined | null : BaseType | undefined);
24
+ type Awaitable<Type> = Type | Promise<Type>;
25
+ type Constructor<TInstance extends any = any, TParams extends any[] = never[]> = new (...args: TParams) => TInstance;
26
+ /**
27
+ * @example
28
+ * ```js
29
+ * const getNumber: AsyncFunction<number> = async () => {
30
+ * return 5;
31
+ * }
32
+ * ```
33
+ */
34
+ type AsyncFunction<TReturnValue extends any = void, Params extends any[] = never[]> = (...params: Params) => Promise<TReturnValue>;
35
+ /**
36
+ * A function that takes no arguments and returns nothing.
37
+ * Pass a type argument to set whether `async` and/or `sync` functions are allowed.
38
+ */
39
+ interface IVoidFunction<AsyncType extends 'async' | 'sync' | 'both' = 'both'> {
40
+ (): AsyncType extends 'async' ? Promise<void> : AsyncType extends 'sync' ? void : Promise<void> | void;
41
+ }
42
+ type AnyFunction = (...args: any) => any;
43
+ type FunctionType = AnyFunction;
44
+ type TFunction = AnyFunction;
45
+ interface Window {
46
+ }
47
+ namespace JSX {
48
+ interface IntrinsicElements {
49
+ }
50
+ }
51
+ namespace NodeJS {
52
+ interface ProcessEnv {
53
+ }
54
+ }
55
+ type __FromPrivateHelpers = typeof import('./globals.private');
56
+ type TEmptyObject1 = {
57
+ ''?: never;
58
+ };
59
+ type TEmptyObject2 = Record<symbol, never>;
60
+ type EmptyObject = __FromPrivateHelpers['EmptyObject'];
61
+ type EmptyObject2 = __FromPrivateHelpers['EmptyObject2'];
62
+ type EmptyObject3 = __FromPrivateHelpers['EmptyObject3'];
63
+ type valueof<TObject> = TObject[keyof TObject];
64
+ interface T extends __FromPrivateHelpers {
65
+ }
56
66
  }
67
+ export {};
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ /**
3
+ * @file
4
+ * This file is an "Ambient declarations file". The types defined here are available globally.
5
+ * More info here: https://stackoverflow.com/a/73389225/985454
6
+ *
7
+ * Don't use `import` and `export` in this file directly! It breaks ambience.
8
+ * To import external types in an ambient declarations file (this file) use the following:
9
+ *
10
+ * @example
11
+ * declare type React = typeof import('react');
12
+ *
13
+ * To contribute ambient declarations from any file, even non-ambient ones, use this:
14
+ *
15
+ * @example
16
+ * declare global {
17
+ * interface Window {
18
+ * ethereum: any
19
+ * }
20
+ * }
21
+ **/
22
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,48 @@
1
+ /**
2
+ * @file
3
+ * This file is created specifically to augment the declarations in
4
+ * [globals.d.ts]({@link ./globals.d.ts}).
5
+ *
6
+ * **You should not import this file directly.**
7
+ */
8
+ /** */
9
+ declare const UniqueSecretSymbolKey: unique symbol;
10
+ declare class CEmptyObject {
11
+ [key: keyof any]: never;
12
+ }
13
+ declare class CEmptyObject2 {
14
+ [UniqueSecretSymbolKey]?: never;
15
+ }
16
+ declare class CEmptyObject3 {
17
+ /**
18
+ * It appears keys of the base `symbol` type are excluded from
19
+ * excess property checks. This is likely a bug in TypeScript.
20
+ * Even the "has no properties in common" error disappears if the
21
+ * value being placed into a variable has a key typed as `symbol`.
22
+ * This only applies to the base `symbol` type. Specifc `'unique symbol'`
23
+ * types are unaffected.
24
+ *
25
+ * @example
26
+ * // Consider the following object:
27
+ * const myUniqueSymbol = Symbol('lkjhgfc');
28
+ * let myObj = { [myUniqueSymbol]?: 'a string value' };
29
+ *
30
+ * // We can attempt to reassign `myObj` with the expectation that TS will
31
+ * // warn if any key other than `myUniqueSymbol` is used in the new object.
32
+ * // But this breaks in one specific scenario.
33
+ *
34
+ * // No excess property check when this is used as a key.
35
+ * // Error "no properties in common" also suppressed when this is used as a key.
36
+ * const differentBasicSymbol = Symbol('qwertiop[') as symbol;
37
+ * myObj = { [differentBasicSymbol]: 5 };
38
+ *
39
+ * // Errors emitted as expected when this is used as a key.
40
+ * const differentUniqueSymbol = Symbol('zxcvbnm');
41
+ * myObj = { [differentUniqueSymbol]: 5 };
42
+ */
43
+ [key: symbol]: never;
44
+ }
45
+ export declare const EmptyObject: CEmptyObject;
46
+ export declare const EmptyObject2: CEmptyObject2;
47
+ export declare const EmptyObject3: CEmptyObject3;
48
+ export {};
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ /**
3
+ * @file
4
+ * This file is created specifically to augment the declarations in
5
+ * [globals.d.ts]({@link ./globals.d.ts}).
6
+ *
7
+ * **You should not import this file directly.**
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.EmptyObject3 = exports.EmptyObject2 = exports.EmptyObject = void 0;
11
+ /** */
12
+ var UniqueSecretSymbolKey = Symbol('asdfghjkliuytrewqaxcvb,nb');
13
+ var CEmptyObject = /** @class */ (function () {
14
+ function CEmptyObject() {
15
+ }
16
+ return CEmptyObject;
17
+ }());
18
+ var CEmptyObject2 = /** @class */ (function () {
19
+ function CEmptyObject2() {
20
+ }
21
+ return CEmptyObject2;
22
+ }());
23
+ var CEmptyObject3 = /** @class */ (function () {
24
+ function CEmptyObject3() {
25
+ }
26
+ return CEmptyObject3;
27
+ }());
28
+ exports.EmptyObject = new CEmptyObject();
29
+ exports.EmptyObject2 = new CEmptyObject2();
30
+ exports.EmptyObject3 = new CEmptyObject3();
31
+ testing: {
32
+ var mySymbol = Symbol('asdfgh');
33
+ var tt = {
34
+ // [mySymbol]: '' as never,
35
+ // [UniqueSecretSymbolKey]: '',
36
+ // '': '',
37
+ };
38
+ var TT = new CEmptyObject();
39
+ TT = tt;
40
+ }
@@ -28,10 +28,13 @@
28
28
  "resolveJsonModule": true,
29
29
  "isolatedModules": true,
30
30
  "jsx": "react-jsx",
31
- "strictNullChecks": true
31
+ "strictNullChecks": true,
32
+ "noImplicitAny": true,
33
+ "noUncheckedIndexedAccess": true,
34
+ "strictBindCallApply": true,
35
+ // "exactOptionalPropertyTypes": true
32
36
  },
33
37
  "include": [
34
- "next-env.d.ts",
35
38
  "**/*.ts",
36
39
  "**/*.tsx"
37
40
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleanweb/react",
3
- "version": "1.0.11",
3
+ "version": "1.1.1-beta.0",
4
4
  "description": "A suite of helpers for writing cleaner React function components.",
5
5
  "engines": {
6
6
  "node": ">=18"
@@ -34,8 +34,17 @@
34
34
  "_": "",
35
35
  "prepublishOnly": "npm run build",
36
36
  "publish:patch": "npm version patch && npm publish",
37
+ "publish:minor": "npm version minor && npm publish",
38
+ "publish:major": "npm version major && npm publish",
39
+ "__": "/// Increment beta number for the current patch version. ///",
40
+ "publish:beta:current": "npm version prerelease --preid beta && npm publish --tag beta",
41
+ "___": "/// Create a beta.0 for a new patch/minor/major version ///",
42
+ "publish:beta:new-patch": "npm version prepatch --preid beta && npm publish --tag beta",
43
+ "publish:beta:new-minor": "npm version preminor --preid beta && npm publish --tag beta",
44
+ "publish:beta:new-major": "npm version premajor --preid beta && npm publish --tag beta",
45
+ "____": "",
37
46
  "//postpublish": "cd ./mirror-pkg && npm publish && cd ..",
38
- "__": "",
47
+ "______": "",
39
48
  "test": "echo \"No tests ATM\""
40
49
  },
41
50
  "keywords": [
@@ -55,9 +64,16 @@
55
64
  },
56
65
  "license": "MIT",
57
66
  "devDependencies": {
67
+ "@babel/eslint-parser": "^7.25.9",
68
+ "@babel/preset-typescript": "^7.26.0",
58
69
  "@types/node": "20.14.10",
59
70
  "@types/react": "^16",
71
+ "babel-preset-react-app": "^10.0.1",
60
72
  "copyfiles": "^2.4.1",
73
+ "eslint": "^9.15.0",
74
+ "eslint-plugin-jsdoc": "^50.5.0",
75
+ "eslint-plugin-react": "^7.37.2",
76
+ "globals": "^15.12.0",
61
77
  "rimraf": "^6.0.1",
62
78
  "tsc-alias": "1.8.10",
63
79
  "typescript": "^5.6.2"