@cleanweb/oore 1.0.6 → 1.1.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/base/merged-state.d.ts +1 -1
- package/build/base/merged-state.js +1 -1
- package/build/base/state/class.d.ts +54 -1
- package/build/base/state/class.js +53 -1
- package/build/base/state/index.d.ts +1 -0
- package/build/classy/logic/index.d.ts +1 -1
- package/build/helpers/errors.d.ts +10 -0
- package/build/helpers/errors.js +21 -0
- package/build/slots/hook.d.ts +6 -0
- package/build/slots/hook.js +83 -0
- package/build/slots/index.d.ts +1 -0
- package/build/slots/index.js +17 -0
- package/build/slots/types.d.ts +31 -0
- package/build/slots/types.js +4 -0
- package/package.json +2 -6
|
@@ -14,7 +14,7 @@ declare class MergedState<TState extends object> {
|
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
16
|
* Similar to {@link useCleanState},
|
|
17
|
-
* but uses a single `useState` call for the entire object.
|
|
17
|
+
* but uses a single `useState` call for the entire state object.
|
|
18
18
|
*/
|
|
19
19
|
export declare const useMergedState: <TState extends object>(initialState: TState) => MergedState<TState> & TState;
|
|
20
20
|
export {};
|
|
@@ -72,7 +72,7 @@ var MergedState = /** @class */ (function () {
|
|
|
72
72
|
}());
|
|
73
73
|
/**
|
|
74
74
|
* Similar to {@link useCleanState},
|
|
75
|
-
* but uses a single `useState` call for the entire object.
|
|
75
|
+
* but uses a single `useState` call for the entire state object.
|
|
76
76
|
*/
|
|
77
77
|
var useMergedState = function (initialState) {
|
|
78
78
|
var cleanState = (0, react_1.useRef)((0, react_1.useMemo)(function () {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ICleanStateClass, ICleanStateConstructor, PutManyPayload, PutState } from './class-types';
|
|
1
|
+
import type { ICleanStateClass, ICleanStateConstructor, PutManyPayload, PutState } from './class-types';
|
|
2
2
|
/** @internal */
|
|
3
3
|
export declare class CleanStateBase<TState extends Record<string, any>> {
|
|
4
4
|
readonly reservedKeys: string[];
|
|
@@ -10,6 +10,59 @@ export declare class CleanStateBase<TState extends Record<string, any>> {
|
|
|
10
10
|
static update: <TState_1 extends object>(this: CleanStateBase<TState_1>) => void;
|
|
11
11
|
get put(): PutState<TState>;
|
|
12
12
|
get initialState(): TState;
|
|
13
|
+
/**
|
|
14
|
+
* Accepts an object to be merged into the current state object,
|
|
15
|
+
* updating the values of specified keys to their specified values.
|
|
16
|
+
*
|
|
17
|
+
* All specified keys must be present on the initial state object.
|
|
18
|
+
* This is a requirement of React hooks, as the number of calls to
|
|
19
|
+
* useState must be the same throughout a component's lifetime.
|
|
20
|
+
*
|
|
21
|
+
* To dynamically add new keys to your state object, use a nested object,
|
|
22
|
+
* or use the {@link useMergedState | `useMergedState`} hook also exported by this library.
|
|
23
|
+
*
|
|
24
|
+
* When called from inside a component class, the type of the object passed
|
|
25
|
+
* must be explicitly specified with a type assertion.
|
|
26
|
+
* This is because the component classes use the dynamic `this` type to define
|
|
27
|
+
* the type of their state property.
|
|
28
|
+
*
|
|
29
|
+
* Use the `StateFragment` type, also aliased as `SF`, to define this type assertion.
|
|
30
|
+
* The assertion is not needed when calling putMany from outside the class.
|
|
31
|
+
*
|
|
32
|
+
* @example <caption>Using `putMany`</caption>
|
|
33
|
+
* import type { SF } from '@cleanweb/oore/base';
|
|
34
|
+
* import { ComponentLogic } from '@cleanweb/oore';
|
|
35
|
+
*
|
|
36
|
+
* class InputCL extends ComponentLogic<TProps> {
|
|
37
|
+
* getInitialState(props: TProps): TState => ({
|
|
38
|
+
* disabled: true,
|
|
39
|
+
* });
|
|
40
|
+
*
|
|
41
|
+
* enableInput = () => {
|
|
42
|
+
* this.state.putMany({
|
|
43
|
+
* disabled: false,
|
|
44
|
+
* } as SF<this>); // Type assertion required inside class.
|
|
45
|
+
* };
|
|
46
|
+
* }
|
|
47
|
+
*
|
|
48
|
+
* export const Input = (props: TProps) => {
|
|
49
|
+
* const self = useLogic(InputCL, props);
|
|
50
|
+
*
|
|
51
|
+
* return <>
|
|
52
|
+
* {
|
|
53
|
+
* // Other elements...
|
|
54
|
+
* }
|
|
55
|
+
* <button className="cta"
|
|
56
|
+
* onClick={() => {
|
|
57
|
+
* self.state.putMany({
|
|
58
|
+
* disabled: true
|
|
59
|
+
* }); // No type assertion needed outside the class.
|
|
60
|
+
* }}>
|
|
61
|
+
* Submit
|
|
62
|
+
* </button>
|
|
63
|
+
* </>;
|
|
64
|
+
* };
|
|
65
|
+
*/
|
|
13
66
|
readonly putMany: (newValues: PutManyPayload<TState>) => void;
|
|
14
67
|
}
|
|
15
68
|
/** @internal */
|
|
@@ -19,7 +19,59 @@ var CleanStateBase = /** @class */ (function () {
|
|
|
19
19
|
var _this = this;
|
|
20
20
|
this._values_ = {};
|
|
21
21
|
this._setters_ = {};
|
|
22
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Accepts an object to be merged into the current state object,
|
|
24
|
+
* updating the values of specified keys to their specified values.
|
|
25
|
+
*
|
|
26
|
+
* All specified keys must be present on the initial state object.
|
|
27
|
+
* This is a requirement of React hooks, as the number of calls to
|
|
28
|
+
* useState must be the same throughout a component's lifetime.
|
|
29
|
+
*
|
|
30
|
+
* To dynamically add new keys to your state object, use a nested object,
|
|
31
|
+
* or use the {@link useMergedState | `useMergedState`} hook also exported by this library.
|
|
32
|
+
*
|
|
33
|
+
* When called from inside a component class, the type of the object passed
|
|
34
|
+
* must be explicitly specified with a type assertion.
|
|
35
|
+
* This is because the component classes use the dynamic `this` type to define
|
|
36
|
+
* the type of their state property.
|
|
37
|
+
*
|
|
38
|
+
* Use the `StateFragment` type, also aliased as `SF`, to define this type assertion.
|
|
39
|
+
* The assertion is not needed when calling putMany from outside the class.
|
|
40
|
+
*
|
|
41
|
+
* @example <caption>Using `putMany`</caption>
|
|
42
|
+
* import type { SF } from '@cleanweb/oore/base';
|
|
43
|
+
* import { ComponentLogic } from '@cleanweb/oore';
|
|
44
|
+
*
|
|
45
|
+
* class InputCL extends ComponentLogic<TProps> {
|
|
46
|
+
* getInitialState(props: TProps): TState => ({
|
|
47
|
+
* disabled: true,
|
|
48
|
+
* });
|
|
49
|
+
*
|
|
50
|
+
* enableInput = () => {
|
|
51
|
+
* this.state.putMany({
|
|
52
|
+
* disabled: false,
|
|
53
|
+
* } as SF<this>); // Type assertion required inside class.
|
|
54
|
+
* };
|
|
55
|
+
* }
|
|
56
|
+
*
|
|
57
|
+
* export const Input = (props: TProps) => {
|
|
58
|
+
* const self = useLogic(InputCL, props);
|
|
59
|
+
*
|
|
60
|
+
* return <>
|
|
61
|
+
* {
|
|
62
|
+
* // Other elements...
|
|
63
|
+
* }
|
|
64
|
+
* <button className="cta"
|
|
65
|
+
* onClick={() => {
|
|
66
|
+
* self.state.putMany({
|
|
67
|
+
* disabled: true
|
|
68
|
+
* }); // No type assertion needed outside the class.
|
|
69
|
+
* }}>
|
|
70
|
+
* Submit
|
|
71
|
+
* </button>
|
|
72
|
+
* </>;
|
|
73
|
+
* };
|
|
74
|
+
*/
|
|
23
75
|
this.putMany = function (newValues) {
|
|
24
76
|
Object.entries(newValues).forEach(function (entry) {
|
|
25
77
|
var _a = entry, key = _a[0], value = _a[1];
|
|
@@ -5,4 +5,5 @@ import '../../globals';
|
|
|
5
5
|
export { CleanState } from './class';
|
|
6
6
|
export { useCleanState } from './hooks';
|
|
7
7
|
export type { TCleanState, TStateData, ExtractCleanStateData } from './hook-types';
|
|
8
|
+
export type { SF, StateFragment } from './class-types';
|
|
8
9
|
export * as MergedState from '../../base/merged-state';
|
|
@@ -52,7 +52,7 @@ export declare class ComponentLogic<TProps extends TPropsBase = null> {
|
|
|
52
52
|
* It receives the initial `props` object and should return
|
|
53
53
|
* an object with the initial values for your component's state.
|
|
54
54
|
*/
|
|
55
|
-
getInitialState: (props
|
|
55
|
+
getInitialState: (props: TProps extends null ? undefined : TProps) => object;
|
|
56
56
|
/**
|
|
57
57
|
* Call React hooks from here. If your component needs
|
|
58
58
|
* access to values return from the hooks you call,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Throw an error with the provided message
|
|
3
|
+
* only when `NODE_ENV` is *not* 'production'.
|
|
4
|
+
*
|
|
5
|
+
* In production, this falls back to just a console warning.
|
|
6
|
+
*
|
|
7
|
+
* Useful for enforcing certain conditions in development
|
|
8
|
+
* while failing more gracefully in production.
|
|
9
|
+
*/
|
|
10
|
+
export declare const throwDevError: (message: string) => void;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.throwDevError = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Throw an error with the provided message
|
|
6
|
+
* only when `NODE_ENV` is *not* 'production'.
|
|
7
|
+
*
|
|
8
|
+
* In production, this falls back to just a console warning.
|
|
9
|
+
*
|
|
10
|
+
* Useful for enforcing certain conditions in development
|
|
11
|
+
* while failing more gracefully in production.
|
|
12
|
+
*/
|
|
13
|
+
var throwDevError = function (message) {
|
|
14
|
+
if (process.env.NODE_ENV === 'production') {
|
|
15
|
+
console.warn(message);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
throw new Error(message);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
exports.throwDevError = throwDevError;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from 'react';
|
|
2
|
+
import type { IUseSlots, PotentialSlotComponent } from './types';
|
|
3
|
+
export declare const isElementChild: (child: ReactNode) => child is ReactElement<any, any>;
|
|
4
|
+
export declare const getComponentSlotName: (TargetComponent: PotentialSlotComponent) => string | number | symbol | undefined;
|
|
5
|
+
export declare const useSlots: IUseSlots;
|
|
6
|
+
export type { SlotNamedComponent, SlottedComponent, TSlotsRecord, PotentialSlotComponent, } from './types';
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.useSlots = exports.getComponentSlotName = exports.isElementChild = void 0;
|
|
7
|
+
var errors_1 = require("../helpers/errors");
|
|
8
|
+
var react_1 = __importDefault(require("react"));
|
|
9
|
+
var isElementChild = function (child) {
|
|
10
|
+
if (child && typeof child === 'object' && 'type' in child) {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
return false;
|
|
14
|
+
};
|
|
15
|
+
exports.isElementChild = isElementChild;
|
|
16
|
+
var getComponentSlotName = function (TargetComponent) {
|
|
17
|
+
if (typeof TargetComponent === 'string') {
|
|
18
|
+
return TargetComponent;
|
|
19
|
+
}
|
|
20
|
+
else if ('slotName' in TargetComponent) {
|
|
21
|
+
return TargetComponent.slotName;
|
|
22
|
+
}
|
|
23
|
+
else if ('displayName' in TargetComponent) {
|
|
24
|
+
return TargetComponent.displayName;
|
|
25
|
+
}
|
|
26
|
+
else
|
|
27
|
+
return undefined;
|
|
28
|
+
};
|
|
29
|
+
exports.getComponentSlotName = getComponentSlotName;
|
|
30
|
+
var useSlots = function (children, slotComponents) {
|
|
31
|
+
var useMemo = react_1.default.useMemo;
|
|
32
|
+
console.log({ slotComponents: slotComponents });
|
|
33
|
+
var slotsAliasLookup = useMemo(function () {
|
|
34
|
+
var entries = Object.entries(slotComponents);
|
|
35
|
+
var aliasLookup = {};
|
|
36
|
+
entries.forEach(function (_a) {
|
|
37
|
+
var alias = _a[0], RegisteredSlotComponent = _a[1];
|
|
38
|
+
var slotName = (0, exports.getComponentSlotName)(RegisteredSlotComponent);
|
|
39
|
+
if (!slotName) {
|
|
40
|
+
(0, errors_1.throwDevError)("A registered slot component did not have a slot name. All components registered as slots must either be a string tag-name or a React component with either \"slotName\" or \"displayName\". The affected component was: ".concat(RegisteredSlotComponent));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
return aliasLookup[slotName] = alias;
|
|
44
|
+
});
|
|
45
|
+
return aliasLookup;
|
|
46
|
+
}, [slotComponents]);
|
|
47
|
+
console.log({ slotsAliasLookup: slotsAliasLookup });
|
|
48
|
+
var result = useMemo(function () {
|
|
49
|
+
var slotNodes = {};
|
|
50
|
+
var unmatchedChildren = [];
|
|
51
|
+
var invalidChildren = [];
|
|
52
|
+
react_1.default.Children.forEach(children, function (child) {
|
|
53
|
+
if (!child) {
|
|
54
|
+
invalidChildren.push(child);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (!react_1.default.isValidElement(child)) {
|
|
58
|
+
console.warn("Invalid node found in JSX children while parsing slots. Got: \"".concat(child, "\"."));
|
|
59
|
+
invalidChildren.push(child);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
;
|
|
63
|
+
if (!(0, exports.isElementChild)(child)) {
|
|
64
|
+
unmatchedChildren.push(child);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
var slotAlias = (function () {
|
|
68
|
+
var ChildComponent = child.type;
|
|
69
|
+
var slotName = (0, exports.getComponentSlotName)(ChildComponent);
|
|
70
|
+
return slotName ? slotsAliasLookup[slotName] : null;
|
|
71
|
+
})();
|
|
72
|
+
console.log('match:', { key: slotAlias });
|
|
73
|
+
if (slotAlias)
|
|
74
|
+
slotNodes[slotAlias] = child;
|
|
75
|
+
else
|
|
76
|
+
unmatchedChildren.push(child);
|
|
77
|
+
});
|
|
78
|
+
console.log({ unmatchedChildren: unmatchedChildren, invalidChildren: invalidChildren });
|
|
79
|
+
return [slotNodes, unmatchedChildren, invalidChildren];
|
|
80
|
+
}, [children]);
|
|
81
|
+
return result;
|
|
82
|
+
};
|
|
83
|
+
exports.useSlots = useSlots;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './hook';
|
|
@@ -0,0 +1,17 @@
|
|
|
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./hook"), exports);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode, JSXElementConstructor, FunctionComponent } from 'react';
|
|
2
|
+
export type TComponent = JSXElementConstructor<any>;
|
|
3
|
+
export type TSlotName = keyof any;
|
|
4
|
+
export type TSlotAlias = keyof any;
|
|
5
|
+
export type TSlotsRecord<TKey extends TSlotAlias = TSlotAlias> = {
|
|
6
|
+
[Key in TKey]: (string | SlotComponent);
|
|
7
|
+
};
|
|
8
|
+
export interface DisplayNamedComponent<TProps extends any = any> extends FunctionComponent<TProps> {
|
|
9
|
+
displayName: string;
|
|
10
|
+
}
|
|
11
|
+
export interface SlotNamedComponent<TProps extends any = any> {
|
|
12
|
+
(props: TProps): ReactNode;
|
|
13
|
+
slotName: TSlotName;
|
|
14
|
+
}
|
|
15
|
+
export type SlotComponent<Component extends TComponent = TComponent> = SlotNamedComponent | DisplayNamedComponent<Component>;
|
|
16
|
+
export interface SlottedComponent<TProps = any, TSlotsRecordArg extends TSlotsRecord = TSlotsRecord> {
|
|
17
|
+
(props: TProps): ReactNode;
|
|
18
|
+
Slots: TSlotsRecordArg;
|
|
19
|
+
}
|
|
20
|
+
export type TSlotNodes<TSlotAliasArg extends TSlotAlias> = {
|
|
21
|
+
[Key in TSlotAliasArg]?: ReactElement<any>;
|
|
22
|
+
};
|
|
23
|
+
export type TUseSlotsResult<TSlotAliasArg extends TSlotAlias = TSlotAlias> = Readonly<[
|
|
24
|
+
slots: TSlotNodes<TSlotAliasArg>,
|
|
25
|
+
unmatchedChildren: ReactNode[],
|
|
26
|
+
invalidChildren: any[]
|
|
27
|
+
]>;
|
|
28
|
+
export interface IUseSlots {
|
|
29
|
+
<TSlotAliasArg extends TSlotAlias = TSlotAlias>(children: ReactNode, slotComponents: TSlotsRecord<TSlotAliasArg>): TUseSlotsResult<TSlotAliasArg>;
|
|
30
|
+
}
|
|
31
|
+
export type PotentialSlotComponent = string | SlotComponent | TComponent;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cleanweb/oore",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.1.0-alpha.0",
|
|
4
4
|
"description": "A library of helpers for writing cleaner React function components with object-oriented patterns.",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20"
|
|
@@ -10,15 +10,11 @@
|
|
|
10
10
|
".npmrc"
|
|
11
11
|
],
|
|
12
12
|
"main": "build/index.js",
|
|
13
|
-
"//exports/.": {
|
|
14
|
-
"require": "./src/index.cjs",
|
|
15
|
-
"import": "./src/index.mjs"
|
|
16
|
-
},
|
|
17
|
-
"//type": "module",
|
|
18
13
|
"exports": {
|
|
19
14
|
".": "./build/classy/index.js",
|
|
20
15
|
"./base": "./build/base/index.js",
|
|
21
16
|
"./helpers": "./build/helpers/index.js",
|
|
17
|
+
"./slots": "./build/slots/index.js",
|
|
22
18
|
"./all": "./build/index.js"
|
|
23
19
|
},
|
|
24
20
|
"scripts": {
|