@cleanweb/oore 1.2.2-alpha.2 → 1.2.2-alpha.5

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.
@@ -1,5 +1,24 @@
1
+ interface ICountRef {
2
+ current: number;
3
+ }
4
+ interface IRefresherReturn {
5
+ /** The last render count just before the rerender was triggered. */
6
+ previousCount: number;
7
+ /** A {@link useRef | RefObject} whose `current` property always has the latest render count. */
8
+ latestCountRef: ICountRef;
9
+ }
10
+ interface IRefresher {
11
+ (): Promise<IRefresherReturn>;
12
+ }
13
+ interface IUseRender {
14
+ (): IRefresher & {
15
+ /** The number of times this instance of the component has been (re)rendered. */
16
+ currentCount: number;
17
+ };
18
+ }
1
19
  /**
2
20
  * Returns a function that can be called to manually trigger
3
21
  * a rerender of your component.
4
22
  */
5
- export declare const useRerender: () => () => Promise<unknown>;
23
+ export declare const useRerender: IUseRender;
24
+ export {};
@@ -1,37 +1,42 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useRerender = void 0;
4
- var mount_state_1 = require("../helpers/mount-state");
5
4
  var react_1 = require("react");
5
+ var mount_state_1 = require("../helpers/mount-state");
6
+ ;
6
7
  /**
7
8
  * Returns a function that can be called to manually trigger
8
9
  * a rerender of your component.
9
10
  */
10
11
  var useRerender = function () {
11
12
  var isMounted = (0, mount_state_1.useMountState)();
12
- var _a = (0, react_1.useState)(Date.now()), key = _a[0], forceRerender = _a[1];
13
- var rerender = (0, react_1.useCallback)(function () { return new Promise(function (resolve, reject) {
13
+ var renderCount = (0, react_1.useRef)(0);
14
+ var _a = (0, react_1.useState)(renderCount.current), forceRerender = _a[1];
15
+ renderCount.current++;
16
+ var rerender = (0, react_1.useCallback)(function () {
17
+ var resolve;
18
+ var promise = new Promise(function (_r) { return resolve = _r; });
14
19
  var execute = function () {
15
- var key = Date.now();
16
- forceRerender(key);
17
- resolve(key);
20
+ forceRerender(renderCount.current);
21
+ resolve({
22
+ previousCount: renderCount.current,
23
+ latestCountRef: renderCount,
24
+ });
18
25
  };
19
- if (isMounted()) {
26
+ if (isMounted())
20
27
  execute();
21
- return;
28
+ else {
29
+ setTimeout(function () {
30
+ if (isMounted())
31
+ execute();
32
+ else
33
+ console.log('Cannot rerender an unmounted component.');
34
+ }, 1000);
22
35
  }
23
- setTimeout(function () {
24
- if (isMounted()) {
25
- execute();
26
- return;
27
- }
28
- else {
29
- console.log('Cannot rerender an unmounted component.');
30
- }
31
- }, 1000);
32
- }); }, [forceRerender]);
33
- // @ts-expect-error
34
- rerender.key = key;
35
- return rerender;
36
+ return promise;
37
+ }, [forceRerender, renderCount]);
38
+ var refresher = function () { return rerender(); };
39
+ refresher.currentCount = renderCount.current;
40
+ return refresher;
36
41
  };
37
42
  exports.useRerender = useRerender;
@@ -5,5 +5,15 @@ interface IGetSlotName {
5
5
  (TargetComponent: PotentialSlotComponent, child?: ReactElement): string | undefined;
6
6
  }
7
7
  export declare const getComponentSlotName: IGetSlotName;
8
+ /**
9
+ * Groups `children` prop into predefined slots.
10
+ *
11
+ * @returns A {@link TUseSlotsResult} array,
12
+ * which includes a `slotNodes` object that maps the keys from
13
+ * the predefined {@link slotComponents} object to the corresponding
14
+ * React node(s) that were rendered for that slot.
15
+ *
16
+ * @see {@link SlotComponent} for more on how to use the returned slot nodes.
17
+ */
8
18
  export declare const useSlots: IUseSlots;
9
19
  export type { SlotNamedComponent, SlottedComponent, TSlotsRecord, PotentialSlotComponent, } from './types';
@@ -1,4 +1,27 @@
1
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
+ };
2
25
  var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
3
26
  if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
4
27
  if (ar || !(i in from)) {
@@ -8,13 +31,10 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
8
31
  }
9
32
  return to.concat(ar || Array.prototype.slice.call(from));
10
33
  };
11
- var __importDefault = (this && this.__importDefault) || function (mod) {
12
- return (mod && mod.__esModule) ? mod : { "default": mod };
13
- };
14
34
  Object.defineProperty(exports, "__esModule", { value: true });
15
35
  exports.useSlots = exports.getComponentSlotName = exports.isElementChild = void 0;
16
36
  var errors_1 = require("../helpers/errors");
17
- var react_1 = __importDefault(require("react"));
37
+ var react_1 = __importStar(require("react"));
18
38
  var isElementChild = function (child) {
19
39
  if (child && typeof child === 'object' && 'type' in child) {
20
40
  return true;
@@ -45,9 +65,18 @@ var getComponentSlotName = function (TargetComponent, child) {
45
65
  return undefined;
46
66
  };
47
67
  exports.getComponentSlotName = getComponentSlotName;
68
+ /**
69
+ * Groups `children` prop into predefined slots.
70
+ *
71
+ * @returns A {@link TUseSlotsResult} array,
72
+ * which includes a `slotNodes` object that maps the keys from
73
+ * the predefined {@link slotComponents} object to the corresponding
74
+ * React node(s) that were rendered for that slot.
75
+ *
76
+ * @see {@link SlotComponent} for more on how to use the returned slot nodes.
77
+ */
48
78
  var useSlots = function (children, slotComponents, requiredSlotAliases) {
49
- var useMemo = react_1.default.useMemo;
50
- var slotsAliasLookup = useMemo(function () {
79
+ var slotsAliasLookup = (0, react_1.useMemo)(function () {
51
80
  var entries = Object.entries(slotComponents);
52
81
  var aliasLookup = {};
53
82
  entries.forEach(function (_a) {
@@ -61,7 +90,7 @@ var useSlots = function (children, slotComponents, requiredSlotAliases) {
61
90
  });
62
91
  return aliasLookup;
63
92
  }, [slotComponents]);
64
- var result = useMemo(function () {
93
+ var result = (0, react_1.useMemo)(function () {
65
94
  var slotNodes = {};
66
95
  var unmatchedChildren = [];
67
96
  var invalidChildren = [];
@@ -91,14 +120,11 @@ var useSlots = function (children, slotComponents, requiredSlotAliases) {
91
120
  }
92
121
  }
93
122
  if (slotAlias) {
94
- if (!slotNodes[slotAlias]) {
95
- slotNodes[slotAlias] = child;
96
- }
97
- else if (Array.isArray(slotNodes[slotAlias])) {
123
+ if (slotNodes[slotAlias]) {
98
124
  slotNodes[slotAlias].push(child);
99
125
  }
100
126
  else {
101
- slotNodes[slotAlias] = [slotNodes[slotAlias], child];
127
+ slotNodes[slotAlias] = [child];
102
128
  }
103
129
  }
104
130
  else
@@ -2,6 +2,12 @@ import type { ReactElement, ReactNode, JSXElementConstructor } from 'react';
2
2
  export type TComponent = JSXElementConstructor<any>;
3
3
  export type TSlotName = keyof any;
4
4
  export type TSlotAlias = keyof any;
5
+ /**
6
+ * A map of slot aliases to actual {@link SlotComponent}s.
7
+ *
8
+ * The `useSlots` hook will create a corresponding key for
9
+ * each alias in this record to hold any `ReactNode`s rendered for that slot.
10
+ */
5
11
  export type TSlotsRecord<TKey extends TSlotAlias = TSlotAlias> = {
6
12
  [Key in TKey]: string | SlotComponent;
7
13
  };
@@ -11,21 +17,119 @@ export type DisplayNamedComponent<TComponentArg extends TComponent = TComponent,
11
17
  export type SlotNamedComponent<TComponentArg extends TComponent = TComponent, TSlotNameArg extends TSlotName = TSlotName> = TComponentArg & {
12
18
  slotName: TSlotNameArg;
13
19
  };
20
+ /**
21
+ * A child component used to insert content into a specific slot in the parent component.
22
+ * This can either be a string, or a React component with a `slotName` property.
23
+ * If `slotName` is missing `displayName` will be used as a fallback.
24
+ *
25
+ * ### Strings
26
+ * For strings, they are treated by React a native tags. This lets you use custom strings
27
+ * as if you are rendering a custom Web Component, or simply handle specific HTML tags in a specific way.
28
+ *
29
+ * If using a custom string, you will want to handle it in the parent component by simply forwarding the slot's
30
+ * props to an actual element of your choice.
31
+ *
32
+ * So consumers may pass the following as `children`.
33
+ * ```jsx
34
+ * <option-slot>Click</option-slot>
35
+ * ```
36
+ * and you can map that to
37
+ * ```jsx
38
+ * <button
39
+ * {...slotNodes.Option.props}
40
+ * type="button"
41
+ * />
42
+ * ```
43
+ *
44
+ * ### React Components
45
+ * For slot components that are actual React components, the parent can simply
46
+ * render the slot node directly.
47
+ *
48
+ * So consumers may pass the following as `children`.
49
+ * ```jsx
50
+ * <Parent.slots.Content>
51
+ * Some awesome content.
52
+ * </Parent.slots.Content>
53
+ * ```
54
+ * and you can map that to
55
+ * ```jsx
56
+ * return <>
57
+ * <h1>Title</h1>
58
+ * {slotNodes.Content}
59
+ * <footer>Powered by oore</footer>
60
+ * </>
61
+ * ```
62
+ *
63
+ * This means `Parent.slots.Content` must be defined as a proper React component
64
+ * and is solely responsible for doing something with the `"Some awesome content."`
65
+ * which was passed into it as children.
66
+ *
67
+ * This is unlike the string version where the parent component
68
+ * extracts the props passed to the slot and handles
69
+ * what the slot actually renders.
70
+ */
14
71
  export type SlotComponent<TComponentArg extends TComponent = TComponent> = (SlotNamedComponent<TComponentArg> | DisplayNamedComponent<TComponentArg>) & {
15
72
  isRequiredSlot?: boolean;
16
73
  };
74
+ /**
75
+ * A parent component which accepts content that can be grouped into predefined slots.
76
+ * By convention, it should have a `slots` property which is a {@link TSlotsRecord}.
77
+ *
78
+ * This allows consumers access the predefined slot components
79
+ * directly from the parent component itself,
80
+ * through an alias that is easy to remember.
81
+ */
17
82
  export type SlottedComponent<TComponentArg extends TComponent = TComponent, TSlotAliasArg extends TSlotAlias = TSlotAlias, TSlotsRecordArg extends TSlotsRecord<TSlotAliasArg> = TSlotsRecord<TSlotAliasArg>> = TComponentArg & {
18
- Slots: TSlotsRecordArg;
83
+ [Key in 'Slots' | 'slots']: TSlotsRecordArg;
19
84
  };
85
+ /**
86
+ * A record of slot aliases mapped to the corresponding `ReactNode`(s)
87
+ * to be rendered for that slot.
88
+ */
20
89
  export type TSlotNodes<TSlotAliasArg extends TSlotAlias> = {
21
- [Key in TSlotAliasArg]?: ReactElement<any> | Array<ReactElement<any>>;
90
+ [Key in TSlotAliasArg]?: Array<ReactElement<any>>;
22
91
  };
23
92
  export type TUseSlotsResult<TSlotAliasArg extends TSlotAlias = TSlotAlias> = Readonly<[
24
- slots: TSlotNodes<TSlotAliasArg>,
93
+ /**
94
+ * A record of slot aliases to their corresponding React nodes.
95
+ * Each alias maps to an array of one or more React nodes that were passed
96
+ * as children for that slot.
97
+ *
98
+ * If a slot was not rendered in `children`, it's alias will be `undefined` in this object.
99
+ */
100
+ slotNodes: TSlotNodes<TSlotAliasArg>,
101
+ /**
102
+ * Valid React nodes passed as children which did not match any of the
103
+ * predefined slots.
104
+ */
25
105
  unmatchedChildren: ReactNode[],
106
+ /**
107
+ * Items included in `children` which are not valid React nodes.
108
+ */
26
109
  invalidChildren: any[]
27
110
  ]>;
28
111
  export interface IUseSlots {
29
- <TSlotAliasArg extends TSlotAlias = TSlotAlias>(children: ReactNode, slotComponents: TSlotsRecord<TSlotAliasArg>, requiredSlotAliases?: TSlotAliasArg[]): TUseSlotsResult<TSlotAliasArg>;
112
+ <TSlotAliasArg extends TSlotAlias = TSlotAlias>(
113
+ /**
114
+ * Your component's `children` prop.
115
+ * The nodes it contains will be categorized and
116
+ * grouped according to the predefined {@link slotComponents}.
117
+ */
118
+ children: ReactNode,
119
+ /**
120
+ * A map of slot aliases to their corresponding {@link SlotComponent}.
121
+ * React nodes passed as children will be compared with the components
122
+ * in this record to generate the categorized `slotNodes` object.
123
+ */
124
+ slotComponents: TSlotsRecord<TSlotAliasArg>,
125
+ /**
126
+ * An array of aliases to be treated as required. If a slot's
127
+ * alias is in this array, the slot is treated as required.
128
+ *
129
+ * If no ReactNode is found in {@link children | `children`}
130
+ * for a required slot, an error will be thrown in development.
131
+ * However in production this is softened to just a console warning.
132
+ */
133
+ requiredSlotAliases?: TSlotAliasArg[]): TUseSlotsResult<TSlotAliasArg>;
30
134
  }
31
135
  export type PotentialSlotComponent = string | SlotComponent | TComponent;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleanweb/oore",
3
- "version": "1.2.2-alpha.2",
3
+ "version": "1.2.2-alpha.5",
4
4
  "description": "A library of helpers for writing cleaner React function components with object-oriented patterns.",
5
5
  "engines": {
6
6
  "node": ">=20"