@webiny/react-composition 0.0.0-unstable.df7a8bb475 → 0.0.0-unstable.e2758ee1cf

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/Compose.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import React from "react";
1
2
  import type { DecoratableTypes } from "./Context.js";
2
3
  import type { ComposeWith } from "./types.js";
3
4
  export interface ComposeProps {
@@ -5,4 +6,4 @@ export interface ComposeProps {
5
6
  component?: DecoratableTypes;
6
7
  with: ComposeWith;
7
8
  }
8
- export declare const Compose: (props: ComposeProps) => null;
9
+ export declare const Compose: (props: ComposeProps) => React.JSX.Element | null;
package/Compose.js CHANGED
@@ -1,27 +1,68 @@
1
- import { useEffect } from "react";
2
- import { useComposition } from "./Context.js";
1
+ import React, { useEffect, useRef } from "react";
2
+ import { useCompositionStore } from "./Context.js";
3
3
  import { useCompositionScope } from "./CompositionScope.js";
4
4
  export const Compose = props => {
5
- const {
6
- composeComponent
7
- } = useComposition();
5
+ const store = useCompositionStore();
8
6
  const {
9
7
  scope,
10
8
  inherit
11
9
  } = useCompositionScope();
12
10
  const targetFn = props.function ?? props.component;
11
+ if (!targetFn) {
12
+ console.warn("You must provide a function or a component to compose with!", props);
13
+ return null;
14
+ }
15
+ if (typeof targetFn.original === "undefined") {
16
+ console.warn(`You must make your function "${targetFn.originalName ?? targetFn.name}" composable, by using the makeDecoratable() function!`);
17
+ return null;
18
+ }
19
+ const decorators = Array.isArray(props.with) ? props.with : [props.with];
20
+ const currentScope = scope[scope.length - 1] ?? "*";
21
+
22
+ // Register synchronously during render so decorators are available immediately.
23
+ // Pass silent=true to avoid notifying listeners mid-render (which would trigger
24
+ // setState in other components and cause React warnings).
25
+ store.register(targetFn.original, decorators, currentScope, inherit, true);
26
+ return /*#__PURE__*/React.createElement(ComposeEffects, {
27
+ store: store,
28
+ target: targetFn.original,
29
+ decorators: decorators,
30
+ scope: currentScope
31
+ });
32
+ };
33
+
34
+ /**
35
+ * Separate component for the effect to avoid re-running the cleanup on every render.
36
+ * This component handles cleanup on unmount and when props change.
37
+ */
38
+ function ComposeEffects({
39
+ store,
40
+ target,
41
+ decorators,
42
+ scope
43
+ }) {
44
+ const prevRef = useRef(null);
13
45
  useEffect(() => {
14
- if (!targetFn) {
15
- console.warn("You must provide a function or a component to compose with!", props);
16
- }
17
- if (typeof targetFn.original === "undefined") {
18
- console.warn(`You must make your function "${targetFn.originalName ?? targetFn.name}" composable, by using the makeDecoratable() function!`);
19
- return;
46
+ const prev = prevRef.current;
47
+
48
+ // On prop change: unregister old decorators.
49
+ if (prev && (prev.decorators !== decorators || prev.scope !== scope)) {
50
+ store.unregister(target, prev.decorators, prev.scope);
51
+ // Re-register new ones (they were already registered during render,
52
+ // but the idempotency check handles this).
53
+ store.register(target, decorators, scope);
20
54
  }
21
- const decorators = Array.isArray(props.with) ? props.with : [props.with];
22
- return composeComponent(targetFn.original, decorators, scope[scope.length - 1], inherit);
23
- }, [props.with]);
55
+ prevRef.current = {
56
+ decorators,
57
+ scope
58
+ };
59
+
60
+ // Cleanup on unmount.
61
+ return () => {
62
+ store.unregister(target, decorators, scope);
63
+ };
64
+ }, [store, target, decorators, scope]);
24
65
  return null;
25
- };
66
+ }
26
67
 
27
68
  //# sourceMappingURL=Compose.js.map
package/Compose.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"names":["useEffect","useComposition","useCompositionScope","Compose","props","composeComponent","scope","inherit","targetFn","function","component","console","warn","original","originalName","name","decorators","Array","isArray","with","length"],"sources":["Compose.tsx"],"sourcesContent":["import { useEffect } from \"react\";\nimport type { DecoratableTypes } from \"./Context.js\";\nimport { useComposition } from \"./Context.js\";\nimport { useCompositionScope } from \"~/CompositionScope.js\";\nimport type { ComposeWith, Decoratable, Enumerable } from \"./types.js\";\n\nexport interface ComposeProps {\n function?: DecoratableTypes;\n component?: DecoratableTypes;\n with: ComposeWith;\n}\n\nexport const Compose = (props: ComposeProps) => {\n const { composeComponent } = useComposition();\n const { scope, inherit } = useCompositionScope();\n\n const targetFn = (props.function ?? props.component) as Decoratable;\n\n useEffect(() => {\n if (!targetFn) {\n console.warn(\"You must provide a function or a component to compose with!\", props);\n }\n if (typeof targetFn.original === \"undefined\") {\n console.warn(\n `You must make your function \"${\n targetFn.originalName ?? targetFn.name\n }\" composable, by using the makeDecoratable() function!`\n );\n\n return;\n }\n\n const decorators = Array.isArray(props.with) ? props.with : [props.with];\n return composeComponent(\n targetFn.original,\n decorators as Enumerable<ComposeWith>,\n scope[scope.length - 1],\n inherit\n );\n }, [props.with]);\n\n return null;\n};\n"],"mappings":"AAAA,SAASA,SAAS,QAAQ,OAAO;AAEjC,SAASC,cAAc;AACvB,SAASC,mBAAmB;AAS5B,OAAO,MAAMC,OAAO,GAAIC,KAAmB,IAAK;EAC5C,MAAM;IAAEC;EAAiB,CAAC,GAAGJ,cAAc,CAAC,CAAC;EAC7C,MAAM;IAAEK,KAAK;IAAEC;EAAQ,CAAC,GAAGL,mBAAmB,CAAC,CAAC;EAEhD,MAAMM,QAAQ,GAAIJ,KAAK,CAACK,QAAQ,IAAIL,KAAK,CAACM,SAAyB;EAEnEV,SAAS,CAAC,MAAM;IACZ,IAAI,CAACQ,QAAQ,EAAE;MACXG,OAAO,CAACC,IAAI,CAAC,6DAA6D,EAAER,KAAK,CAAC;IACtF;IACA,IAAI,OAAOI,QAAQ,CAACK,QAAQ,KAAK,WAAW,EAAE;MAC1CF,OAAO,CAACC,IAAI,CACR,gCACIJ,QAAQ,CAACM,YAAY,IAAIN,QAAQ,CAACO,IAAI,wDAE9C,CAAC;MAED;IACJ;IAEA,MAAMC,UAAU,GAAGC,KAAK,CAACC,OAAO,CAACd,KAAK,CAACe,IAAI,CAAC,GAAGf,KAAK,CAACe,IAAI,GAAG,CAACf,KAAK,CAACe,IAAI,CAAC;IACxE,OAAOd,gBAAgB,CACnBG,QAAQ,CAACK,QAAQ,EACjBG,UAAU,EACVV,KAAK,CAACA,KAAK,CAACc,MAAM,GAAG,CAAC,CAAC,EACvBb,OACJ,CAAC;EACL,CAAC,EAAE,CAACH,KAAK,CAACe,IAAI,CAAC,CAAC;EAEhB,OAAO,IAAI;AACf,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["React","useEffect","useRef","useCompositionStore","useCompositionScope","Compose","props","store","scope","inherit","targetFn","function","component","console","warn","original","originalName","name","decorators","Array","isArray","with","currentScope","length","register","createElement","ComposeEffects","target","prevRef","prev","current","unregister"],"sources":["Compose.tsx"],"sourcesContent":["import React, { useEffect, useRef } from \"react\";\nimport type { DecoratableTypes } from \"./Context.js\";\nimport { useCompositionStore } from \"./Context.js\";\nimport { useCompositionScope } from \"~/CompositionScope.js\";\nimport type {\n ComposeWith,\n Decoratable,\n Decorator,\n GenericComponent,\n GenericHook\n} from \"./types.js\";\n\nexport interface ComposeProps {\n function?: DecoratableTypes;\n component?: DecoratableTypes;\n with: ComposeWith;\n}\n\nexport const Compose = (props: ComposeProps) => {\n const store = useCompositionStore();\n const { scope, inherit } = useCompositionScope();\n\n const targetFn = (props.function ?? props.component) as Decoratable;\n\n if (!targetFn) {\n console.warn(\"You must provide a function or a component to compose with!\", props);\n return null;\n }\n\n if (typeof targetFn.original === \"undefined\") {\n console.warn(\n `You must make your function \"${\n targetFn.originalName ?? targetFn.name\n }\" composable, by using the makeDecoratable() function!`\n );\n return null;\n }\n\n const decorators = (Array.isArray(props.with) ? props.with : [props.with]) as Decorator<\n GenericComponent | GenericHook\n >[];\n const currentScope = scope[scope.length - 1] ?? \"*\";\n\n // Register synchronously during render so decorators are available immediately.\n // Pass silent=true to avoid notifying listeners mid-render (which would trigger\n // setState in other components and cause React warnings).\n store.register(targetFn.original, decorators, currentScope, inherit, true);\n\n return (\n <ComposeEffects\n store={store}\n target={targetFn.original}\n decorators={decorators}\n scope={currentScope}\n />\n );\n};\n\n/**\n * Separate component for the effect to avoid re-running the cleanup on every render.\n * This component handles cleanup on unmount and when props change.\n */\nfunction ComposeEffects({\n store,\n target,\n decorators,\n scope\n}: {\n store: ReturnType<typeof useCompositionStore>;\n target: any;\n decorators: Decorator<GenericComponent | GenericHook>[];\n scope: string;\n}) {\n const prevRef = useRef<{\n decorators: Decorator<GenericComponent | GenericHook>[];\n scope: string;\n } | null>(null);\n\n useEffect(() => {\n const prev = prevRef.current;\n\n // On prop change: unregister old decorators.\n if (prev && (prev.decorators !== decorators || prev.scope !== scope)) {\n store.unregister(target, prev.decorators, prev.scope);\n // Re-register new ones (they were already registered during render,\n // but the idempotency check handles this).\n store.register(target, decorators, scope);\n }\n\n prevRef.current = { decorators, scope };\n\n // Cleanup on unmount.\n return () => {\n store.unregister(target, decorators, scope);\n };\n }, [store, target, decorators, scope]);\n\n return null;\n}\n"],"mappings":"AAAA,OAAOA,KAAK,IAAIC,SAAS,EAAEC,MAAM,QAAQ,OAAO;AAEhD,SAASC,mBAAmB;AAC5B,SAASC,mBAAmB;AAe5B,OAAO,MAAMC,OAAO,GAAIC,KAAmB,IAAK;EAC5C,MAAMC,KAAK,GAAGJ,mBAAmB,CAAC,CAAC;EACnC,MAAM;IAAEK,KAAK;IAAEC;EAAQ,CAAC,GAAGL,mBAAmB,CAAC,CAAC;EAEhD,MAAMM,QAAQ,GAAIJ,KAAK,CAACK,QAAQ,IAAIL,KAAK,CAACM,SAAyB;EAEnE,IAAI,CAACF,QAAQ,EAAE;IACXG,OAAO,CAACC,IAAI,CAAC,6DAA6D,EAAER,KAAK,CAAC;IAClF,OAAO,IAAI;EACf;EAEA,IAAI,OAAOI,QAAQ,CAACK,QAAQ,KAAK,WAAW,EAAE;IAC1CF,OAAO,CAACC,IAAI,CACR,gCACIJ,QAAQ,CAACM,YAAY,IAAIN,QAAQ,CAACO,IAAI,wDAE9C,CAAC;IACD,OAAO,IAAI;EACf;EAEA,MAAMC,UAAU,GAAIC,KAAK,CAACC,OAAO,CAACd,KAAK,CAACe,IAAI,CAAC,GAAGf,KAAK,CAACe,IAAI,GAAG,CAACf,KAAK,CAACe,IAAI,CAErE;EACH,MAAMC,YAAY,GAAGd,KAAK,CAACA,KAAK,CAACe,MAAM,GAAG,CAAC,CAAC,IAAI,GAAG;;EAEnD;EACA;EACA;EACAhB,KAAK,CAACiB,QAAQ,CAACd,QAAQ,CAACK,QAAQ,EAAEG,UAAU,EAAEI,YAAY,EAAEb,OAAO,EAAE,IAAI,CAAC;EAE1E,oBACIT,KAAA,CAAAyB,aAAA,CAACC,cAAc;IACXnB,KAAK,EAAEA,KAAM;IACboB,MAAM,EAAEjB,QAAQ,CAACK,QAAS;IAC1BG,UAAU,EAAEA,UAAW;IACvBV,KAAK,EAAEc;EAAa,CACvB,CAAC;AAEV,CAAC;;AAED;AACA;AACA;AACA;AACA,SAASI,cAAcA,CAAC;EACpBnB,KAAK;EACLoB,MAAM;EACNT,UAAU;EACVV;AAMJ,CAAC,EAAE;EACC,MAAMoB,OAAO,GAAG1B,MAAM,CAGZ,IAAI,CAAC;EAEfD,SAAS,CAAC,MAAM;IACZ,MAAM4B,IAAI,GAAGD,OAAO,CAACE,OAAO;;IAE5B;IACA,IAAID,IAAI,KAAKA,IAAI,CAACX,UAAU,KAAKA,UAAU,IAAIW,IAAI,CAACrB,KAAK,KAAKA,KAAK,CAAC,EAAE;MAClED,KAAK,CAACwB,UAAU,CAACJ,MAAM,EAAEE,IAAI,CAACX,UAAU,EAAEW,IAAI,CAACrB,KAAK,CAAC;MACrD;MACA;MACAD,KAAK,CAACiB,QAAQ,CAACG,MAAM,EAAET,UAAU,EAAEV,KAAK,CAAC;IAC7C;IAEAoB,OAAO,CAACE,OAAO,GAAG;MAAEZ,UAAU;MAAEV;IAAM,CAAC;;IAEvC;IACA,OAAO,MAAM;MACTD,KAAK,CAACwB,UAAU,CAACJ,MAAM,EAAET,UAAU,EAAEV,KAAK,CAAC;IAC/C,CAAC;EACL,CAAC,EAAE,CAACD,KAAK,EAAEoB,MAAM,EAAET,UAAU,EAAEV,KAAK,CAAC,CAAC;EAEtC,OAAO,IAAI;AACf","ignoreList":[]}
package/Context.d.ts CHANGED
@@ -1,38 +1,15 @@
1
1
  import type { ComponentType } from "react";
2
2
  import React from "react";
3
- import type { ComposedFunction, ComposeWith, Decoratable, DecoratableComponent, DecoratableHook, Decorator, Enumerable, GenericComponent, GenericHook } from "./types.js";
3
+ import { CompositionStore } from "./domain/CompositionStore.js";
4
+ import type { ComposeWith, Decoratable, DecoratableComponent, DecoratableHook, Decorator, Enumerable, GenericComponent, GenericHook } from "./types.js";
4
5
  export declare function compose<T>(...fns: Decorator<T>[]): (decoratee: T) => T;
5
- interface ComposedComponent {
6
- /**
7
- * Ready to use React component.
8
- */
9
- component: GenericHook | GenericComponent;
10
- /**
11
- * HOCs used to compose the original component.
12
- */
13
- hocs: Decorator<GenericComponent | GenericHook>[];
14
- /**
15
- * Component composition can be scoped.
16
- */
17
- scope?: string;
18
- }
19
6
  /**
20
7
  * @deprecated Use `Decorator` instead.
21
8
  */
22
9
  export interface HigherOrderComponent<TProps = any, TOutput = TProps> {
23
10
  (Component: GenericComponent<TProps>): GenericComponent<TOutput>;
24
11
  }
25
- type ComposedComponents = Map<ComponentType<unknown>, ComposedComponent>;
26
- type ComponentScopes = Map<string, ComposedComponents>;
27
12
  export type DecoratableTypes = DecoratableComponent | DecoratableHook;
28
- interface CompositionContextGetComponentCallable {
29
- (component: ComponentType<unknown>, scope: string[]): ComposedFunction | GenericComponent | undefined;
30
- }
31
- interface CompositionContextValue {
32
- components: ComponentScopes;
33
- getComponent: CompositionContextGetComponentCallable;
34
- composeComponent(component: ComponentType<unknown>, hocs: Enumerable<ComposeWith>, scope?: string, inherit?: boolean): void;
35
- }
36
13
  export type DecoratorsTuple = [Decoratable, Decorator<any>[]];
37
14
  export type DecoratorsCollection = Array<DecoratorsTuple>;
38
15
  interface CompositionProviderProps {
@@ -40,7 +17,13 @@ interface CompositionProviderProps {
40
17
  children: React.ReactNode;
41
18
  }
42
19
  export declare const CompositionProvider: ({ decorators, children }: CompositionProviderProps) => React.JSX.Element;
20
+ export declare function useCompositionStore(): CompositionStore;
21
+ export declare function useOptionalCompositionStore(): CompositionStore | undefined;
43
22
  export declare function useComponent<T>(baseFunction: T): T;
23
+ interface CompositionContextValue {
24
+ composeComponent(component: ComponentType<unknown>, hocs: Enumerable<ComposeWith>, scope?: string, inherit?: boolean): () => void;
25
+ getComponent(component: ComponentType<unknown>, scope: string[]): GenericComponent | GenericHook | undefined;
26
+ }
44
27
  /**
45
28
  * This hook will throw an error if composition context doesn't exist.
46
29
  */
package/Context.js CHANGED
@@ -1,5 +1,6 @@
1
- import React, { createContext, useCallback, useContext, useMemo, useState } from "react";
1
+ import React, { createContext, useContext, useRef, useSyncExternalStore } from "react";
2
2
  import { useCompositionScope } from "./CompositionScope.js";
3
+ import { CompositionStore } from "./domain/CompositionStore.js";
3
4
  export function compose(...fns) {
4
5
  return decoratee => {
5
6
  return fns.reduceRight((decoratee, decorator) => decorator(decoratee), decoratee);
@@ -10,111 +11,81 @@ export function compose(...fns) {
10
11
  * @deprecated Use `Decorator` instead.
11
12
  */
12
13
 
13
- const CompositionContext = /*#__PURE__*/createContext(undefined);
14
- const composeComponents = (components, decorators, scope = "*", inherit = false) => {
15
- const scopeMap = components.get(scope) || new Map();
16
- for (const [component, newHocs] of decorators) {
17
- const recipe = scopeMap.get(component) || {
18
- component: null,
19
- hocs: []
20
- };
21
- const existingHocs = [...(recipe.hocs || [])];
22
- if (inherit && scope !== "*") {
23
- const globalScope = components.get("*") || new Map();
24
- const globalRecipe = globalScope.get(component) || {
25
- component: null,
26
- hocs: []
27
- };
28
- existingHocs.unshift(...globalRecipe.hocs);
29
- }
30
- const finalHocs = [...existingHocs, ...newHocs];
31
- scopeMap.set(component, {
32
- component: compose(...[...finalHocs].reverse())(component),
33
- hocs: finalHocs
34
- });
35
- components.set(scope, scopeMap);
36
- }
37
- return components;
38
- };
14
+ const CompositionStoreContext = /*#__PURE__*/createContext(undefined);
39
15
  export const CompositionProvider = ({
40
16
  decorators = [],
41
17
  children
42
18
  }) => {
43
- const [components, setComponents] = useState(() => {
44
- return composeComponents(new Map(), decorators.map(tuple => {
45
- return [tuple[0].original, tuple[1]];
46
- }));
47
- });
48
- const composeComponent = useCallback((component, hocs, scope = "*", inherit = false) => {
49
- setComponents(prevComponents => {
50
- return composeComponents(new Map(prevComponents), [[component, hocs]], scope, inherit);
51
- });
52
-
53
- // Return a function that will remove the added HOCs.
54
- return () => {
55
- setComponents(prevComponents => {
56
- const components = new Map(prevComponents);
57
- const scopeMap = components.get(scope) || new Map();
58
- const recipe = scopeMap.get(component) || {
59
- component: null,
60
- hocs: []
61
- };
62
- const newHOCs = [...recipe.hocs].filter(hoc => !hocs.includes(hoc));
63
- const NewComponent = compose(...[...newHOCs].reverse())(component);
64
- scopeMap.set(component, {
65
- component: NewComponent,
66
- hocs: newHOCs
67
- });
68
- components.set(scope, scopeMap);
69
- return components;
70
- });
71
- };
72
- }, [setComponents]);
73
- const getComponent = useCallback((Component, scope = []) => {
74
- const scopesToResolve = ["*", ...scope].reverse();
75
- for (const scope of scopesToResolve) {
76
- const scopeMap = components.get(scope) || new Map();
77
- const composedComponent = scopeMap.get(Component);
78
- if (composedComponent) {
79
- return composedComponent.component;
80
- }
19
+ const storeRef = useRef(null);
20
+ if (storeRef.current === null) {
21
+ const store = new CompositionStore();
22
+ // Pre-register decorators from props.
23
+ for (const [decoratable, hocs] of decorators) {
24
+ store.register(decoratable.original, hocs);
81
25
  }
82
- return undefined;
83
- }, [components]);
84
- const context = useMemo(() => ({
85
- getComponent,
86
- composeComponent,
87
- components
88
- }), [components, composeComponent]);
89
- return /*#__PURE__*/React.createElement(CompositionContext.Provider, {
90
- value: context
26
+ storeRef.current = store;
27
+ }
28
+ return /*#__PURE__*/React.createElement(CompositionStoreContext.Provider, {
29
+ value: storeRef.current
91
30
  }, children);
92
31
  };
32
+ export function useCompositionStore() {
33
+ const store = useContext(CompositionStoreContext);
34
+ if (!store) {
35
+ throw new Error(`You're missing a <CompositionProvider> higher up in your component hierarchy!`);
36
+ }
37
+ return store;
38
+ }
39
+ export function useOptionalCompositionStore() {
40
+ return useContext(CompositionStoreContext);
41
+ }
93
42
  export function useComponent(baseFunction) {
94
- const context = useOptionalComposition();
43
+ const store = useOptionalCompositionStore();
95
44
  const scope = useCompositionScope();
96
- if (!context) {
45
+
46
+ // Subscribe to store changes so we re-render when compositions change.
47
+ useSyncExternalStore(store ? store.subscribe : noopSubscribe, store ? store.getSnapshot : noopGetSnapshot);
48
+ if (!store) {
97
49
  return baseFunction;
98
50
  }
99
- return context.getComponent(baseFunction, scope.scope) || baseFunction;
51
+ return store.getComponent(baseFunction, scope.scope) || baseFunction;
100
52
  }
53
+ const noopSubscribe = () => () => {};
54
+ const noopGetSnapshot = () => 0;
55
+
56
+ // Legacy compatibility — kept for any external consumers.
101
57
 
102
58
  /**
103
59
  * This hook will throw an error if composition context doesn't exist.
104
60
  */
105
61
  export function useComposition() {
106
- const context = useContext(CompositionContext);
107
- if (!context) {
108
- throw new Error(`You're missing a <CompositionProvider> higher up in your component hierarchy!`);
109
- }
110
- return context;
62
+ const store = useCompositionStore();
63
+ return {
64
+ composeComponent: (component, hocs, scope = "*", inherit = false) => {
65
+ return store.register(component, hocs, scope, inherit);
66
+ },
67
+ getComponent: (component, scope) => {
68
+ return store.getComponent(component, scope);
69
+ }
70
+ };
111
71
  }
112
72
 
113
73
  /**
114
74
  * This hook will not throw an error if composition context doesn't exist.
115
75
  */
116
76
  export function useOptionalComposition() {
117
- return useContext(CompositionContext);
77
+ const store = useOptionalCompositionStore();
78
+ if (!store) {
79
+ return undefined;
80
+ }
81
+ return {
82
+ composeComponent: (component, hocs, scope = "*", inherit = false) => {
83
+ return store.register(component, hocs, scope, inherit);
84
+ },
85
+ getComponent: (component, scope) => {
86
+ return store.getComponent(component, scope);
87
+ }
88
+ };
118
89
  }
119
90
 
120
91
  //# sourceMappingURL=Context.js.map
package/Context.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"names":["React","createContext","useCallback","useContext","useMemo","useState","useCompositionScope","compose","fns","decoratee","reduceRight","decorator","CompositionContext","undefined","composeComponents","components","decorators","scope","inherit","scopeMap","get","Map","component","newHocs","recipe","hocs","existingHocs","globalScope","globalRecipe","unshift","finalHocs","set","reverse","CompositionProvider","children","setComponents","map","tuple","original","composeComponent","prevComponents","newHOCs","filter","hoc","includes","NewComponent","getComponent","Component","scopesToResolve","composedComponent","context","createElement","Provider","value","useComponent","baseFunction","useOptionalComposition","useComposition","Error"],"sources":["Context.tsx"],"sourcesContent":["import type { ComponentType } from \"react\";\nimport React, { createContext, useCallback, useContext, useMemo, useState } from \"react\";\nimport { useCompositionScope } from \"~/CompositionScope.js\";\nimport type {\n ComposedFunction,\n ComposeWith,\n Decoratable,\n DecoratableComponent,\n DecoratableHook,\n Decorator,\n Enumerable,\n GenericComponent,\n GenericHook\n} from \"~/types.js\";\n\nexport function compose<T>(...fns: Decorator<T>[]) {\n return (decoratee: T): T => {\n return fns.reduceRight((decoratee, decorator) => decorator(decoratee), decoratee) as T;\n };\n}\n\ninterface ComposedComponent {\n /**\n * Ready to use React component.\n */\n component: GenericHook | GenericComponent;\n /**\n * HOCs used to compose the original component.\n */\n hocs: Decorator<GenericComponent | GenericHook>[];\n /**\n * Component composition can be scoped.\n */\n scope?: string;\n}\n\n/**\n * @deprecated Use `Decorator` instead.\n */\nexport interface HigherOrderComponent<TProps = any, TOutput = TProps> {\n (Component: GenericComponent<TProps>): GenericComponent<TOutput>;\n}\n\ntype ComposedComponents = Map<ComponentType<unknown>, ComposedComponent>;\ntype ComponentScopes = Map<string, ComposedComponents>;\n\nexport type DecoratableTypes = DecoratableComponent | DecoratableHook;\n\ninterface CompositionContextGetComponentCallable {\n (\n component: ComponentType<unknown>,\n scope: string[]\n ): ComposedFunction | GenericComponent | undefined;\n}\n\ninterface CompositionContextValue {\n components: ComponentScopes;\n getComponent: CompositionContextGetComponentCallable;\n composeComponent(\n component: ComponentType<unknown>,\n hocs: Enumerable<ComposeWith>,\n scope?: string,\n inherit?: boolean\n ): void;\n}\n\nconst CompositionContext = createContext<CompositionContextValue | undefined>(undefined);\n\nexport type DecoratorsTuple = [Decoratable, Decorator<any>[]];\nexport type DecoratorsCollection = Array<DecoratorsTuple>;\n\ninterface CompositionProviderProps {\n decorators?: DecoratorsCollection;\n children: React.ReactNode;\n}\n\nconst composeComponents = (\n components: ComponentScopes,\n decorators: Array<[GenericComponent | GenericHook, Decorator<any>[]]>,\n scope = \"*\",\n inherit = false\n) => {\n const scopeMap: ComposedComponents = components.get(scope) || new Map();\n for (const [component, newHocs] of decorators) {\n const recipe = scopeMap.get(component) || { component: null, hocs: [] };\n\n const existingHocs = [...(recipe.hocs || [])];\n if (inherit && scope !== \"*\") {\n const globalScope = components.get(\"*\") || new Map();\n const globalRecipe = globalScope.get(component) || { component: null, hocs: [] };\n existingHocs.unshift(...globalRecipe.hocs);\n }\n\n const finalHocs = [...existingHocs, ...newHocs] as Decorator<\n GenericHook | GenericComponent\n >[];\n\n scopeMap.set(component, {\n component: compose(...[...finalHocs].reverse())(component),\n hocs: finalHocs\n });\n\n components.set(scope, scopeMap);\n }\n\n return components;\n};\n\nexport const CompositionProvider = ({ decorators = [], children }: CompositionProviderProps) => {\n const [components, setComponents] = useState<ComponentScopes>(() => {\n return composeComponents(\n new Map(),\n decorators.map(tuple => {\n return [tuple[0].original, tuple[1]];\n })\n );\n });\n\n const composeComponent = useCallback(\n (\n component: GenericComponent | GenericHook,\n hocs: HigherOrderComponent<any, any>[],\n scope: string | undefined = \"*\",\n inherit = false\n ) => {\n setComponents(prevComponents => {\n return composeComponents(\n new Map(prevComponents),\n [[component, hocs]],\n scope,\n inherit\n );\n });\n\n // Return a function that will remove the added HOCs.\n return () => {\n setComponents(prevComponents => {\n const components = new Map(prevComponents);\n const scopeMap: ComposedComponents = components.get(scope) || new Map();\n const recipe = scopeMap.get(component) || {\n component: null,\n hocs: []\n };\n\n const newHOCs = [...recipe.hocs].filter(hoc => !hocs.includes(hoc));\n const NewComponent = compose(...[...newHOCs].reverse())(component);\n\n scopeMap.set(component, {\n component: NewComponent,\n hocs: newHOCs\n });\n\n components.set(scope, scopeMap);\n return components;\n });\n };\n },\n [setComponents]\n );\n\n const getComponent = useCallback<CompositionContextGetComponentCallable>(\n (Component, scope = []) => {\n const scopesToResolve = [\"*\", ...scope].reverse();\n for (const scope of scopesToResolve) {\n const scopeMap: ComposedComponents = components.get(scope) || new Map();\n const composedComponent = scopeMap.get(Component);\n if (composedComponent) {\n return composedComponent.component;\n }\n }\n\n return undefined;\n },\n [components]\n );\n\n const context: CompositionContextValue = useMemo(\n () => ({\n getComponent,\n composeComponent,\n components\n }),\n [components, composeComponent]\n );\n\n return <CompositionContext.Provider value={context}>{children}</CompositionContext.Provider>;\n};\n\nexport function useComponent<T>(baseFunction: T) {\n const context = useOptionalComposition();\n const scope = useCompositionScope();\n\n if (!context) {\n return baseFunction;\n }\n\n return (context.getComponent(baseFunction as any, scope.scope) || baseFunction) as T;\n}\n\n/**\n * This hook will throw an error if composition context doesn't exist.\n */\nexport function useComposition() {\n const context = useContext(CompositionContext);\n if (!context) {\n throw new Error(\n `You're missing a <CompositionProvider> higher up in your component hierarchy!`\n );\n }\n\n return context;\n}\n\n/**\n * This hook will not throw an error if composition context doesn't exist.\n */\nexport function useOptionalComposition() {\n return useContext(CompositionContext);\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["React","createContext","useContext","useRef","useSyncExternalStore","useCompositionScope","CompositionStore","compose","fns","decoratee","reduceRight","decorator","CompositionStoreContext","undefined","CompositionProvider","decorators","children","storeRef","current","store","decoratable","hocs","register","original","createElement","Provider","value","useCompositionStore","Error","useOptionalCompositionStore","useComponent","baseFunction","scope","subscribe","noopSubscribe","getSnapshot","noopGetSnapshot","getComponent","useComposition","composeComponent","component","inherit","useOptionalComposition"],"sources":["Context.tsx"],"sourcesContent":["import type { ComponentType } from \"react\";\nimport React, { createContext, useContext, useRef, useSyncExternalStore } from \"react\";\nimport { useCompositionScope } from \"~/CompositionScope.js\";\nimport { CompositionStore } from \"~/domain/CompositionStore.js\";\nimport type {\n ComposeWith,\n Decoratable,\n DecoratableComponent,\n DecoratableHook,\n Decorator,\n Enumerable,\n GenericComponent,\n GenericHook\n} from \"~/types.js\";\n\nexport function compose<T>(...fns: Decorator<T>[]) {\n return (decoratee: T): T => {\n return fns.reduceRight((decoratee, decorator) => decorator(decoratee), decoratee) as T;\n };\n}\n\n/**\n * @deprecated Use `Decorator` instead.\n */\nexport interface HigherOrderComponent<TProps = any, TOutput = TProps> {\n (Component: GenericComponent<TProps>): GenericComponent<TOutput>;\n}\n\nexport type DecoratableTypes = DecoratableComponent | DecoratableHook;\n\nconst CompositionStoreContext = createContext<CompositionStore | undefined>(undefined);\n\nexport type DecoratorsTuple = [Decoratable, Decorator<any>[]];\nexport type DecoratorsCollection = Array<DecoratorsTuple>;\n\ninterface CompositionProviderProps {\n decorators?: DecoratorsCollection;\n children: React.ReactNode;\n}\n\nexport const CompositionProvider = ({ decorators = [], children }: CompositionProviderProps) => {\n const storeRef = useRef<CompositionStore | null>(null);\n if (storeRef.current === null) {\n const store = new CompositionStore();\n // Pre-register decorators from props.\n for (const [decoratable, hocs] of decorators) {\n store.register(decoratable.original as ComponentType<unknown>, hocs);\n }\n storeRef.current = store;\n }\n\n return (\n <CompositionStoreContext.Provider value={storeRef.current}>\n {children}\n </CompositionStoreContext.Provider>\n );\n};\n\nexport function useCompositionStore(): CompositionStore {\n const store = useContext(CompositionStoreContext);\n if (!store) {\n throw new Error(\n `You're missing a <CompositionProvider> higher up in your component hierarchy!`\n );\n }\n return store;\n}\n\nexport function useOptionalCompositionStore(): CompositionStore | undefined {\n return useContext(CompositionStoreContext);\n}\n\nexport function useComponent<T>(baseFunction: T) {\n const store = useOptionalCompositionStore();\n const scope = useCompositionScope();\n\n // Subscribe to store changes so we re-render when compositions change.\n useSyncExternalStore(\n store ? store.subscribe : noopSubscribe,\n store ? store.getSnapshot : noopGetSnapshot\n );\n\n if (!store) {\n return baseFunction;\n }\n\n return (store.getComponent(baseFunction as any, scope.scope) || baseFunction) as T;\n}\n\nconst noopSubscribe = () => () => {};\nconst noopGetSnapshot = () => 0;\n\n// Legacy compatibility — kept for any external consumers.\n\ninterface CompositionContextValue {\n composeComponent(\n component: ComponentType<unknown>,\n hocs: Enumerable<ComposeWith>,\n scope?: string,\n inherit?: boolean\n ): () => void;\n getComponent(\n component: ComponentType<unknown>,\n scope: string[]\n ): GenericComponent | GenericHook | undefined;\n}\n\n/**\n * This hook will throw an error if composition context doesn't exist.\n */\nexport function useComposition(): CompositionContextValue {\n const store = useCompositionStore();\n\n return {\n composeComponent: (component, hocs, scope = \"*\", inherit = false) => {\n return store.register(component, hocs as any[], scope, inherit);\n },\n getComponent: (component, scope) => {\n return store.getComponent(component, scope);\n }\n };\n}\n\n/**\n * This hook will not throw an error if composition context doesn't exist.\n */\nexport function useOptionalComposition(): CompositionContextValue | undefined {\n const store = useOptionalCompositionStore();\n if (!store) {\n return undefined;\n }\n\n return {\n composeComponent: (component, hocs, scope = \"*\", inherit = false) => {\n return store.register(component, hocs as any[], scope, inherit);\n },\n getComponent: (component, scope) => {\n return store.getComponent(component, scope);\n }\n };\n}\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,15 @@
1
+ import type { ComponentType } from "react";
2
+ import type { Decorator, GenericComponent, GenericHook } from "../types.js";
3
+ type Listener = () => void;
4
+ export declare class CompositionStore {
5
+ private scopes;
6
+ private version;
7
+ private listeners;
8
+ register(component: ComponentType<unknown>, hocs: Decorator<GenericComponent | GenericHook>[], scope?: string, inherit?: boolean, silent?: boolean): () => void;
9
+ unregister(component: ComponentType<unknown>, hocs: Decorator<GenericComponent | GenericHook>[], scope?: string): void;
10
+ getComponent(component: ComponentType<unknown>, scope?: string[]): GenericComponent | GenericHook | undefined;
11
+ subscribe: (listener: Listener) => (() => void);
12
+ getSnapshot: () => number;
13
+ private notifyListeners;
14
+ }
15
+ export {};
@@ -0,0 +1,102 @@
1
+ import { compose } from "../Context.js";
2
+ export class CompositionStore {
3
+ scopes = new Map();
4
+ version = 0;
5
+ listeners = new Set();
6
+ register(component, hocs, scope = "*", inherit = false, silent = false) {
7
+ const scopeMap = this.scopes.get(scope) || new Map();
8
+ const recipe = scopeMap.get(component) || {
9
+ component: component,
10
+ hocs: []
11
+ };
12
+
13
+ // Idempotent: skip if all HOCs are already registered (handles StrictMode double-render).
14
+ const newHocs = hocs.filter(hoc => !recipe.hocs.includes(hoc));
15
+ if (newHocs.length === 0) {
16
+ return () => this.unregister(component, hocs, scope);
17
+ }
18
+ const existingHocs = [...recipe.hocs];
19
+ if (inherit && scope !== "*") {
20
+ const globalScope = this.scopes.get("*") || new Map();
21
+ const globalRecipe = globalScope.get(component) || {
22
+ component: component,
23
+ hocs: []
24
+ };
25
+ // Only prepend global HOCs that aren't already present.
26
+ const globalHocsToAdd = globalRecipe.hocs.filter(hoc => !existingHocs.includes(hoc));
27
+ existingHocs.unshift(...globalHocsToAdd);
28
+ }
29
+ const finalHocs = [...existingHocs, ...newHocs];
30
+ scopeMap.set(component, {
31
+ component: compose(...[...finalHocs].reverse())(component),
32
+ hocs: finalHocs
33
+ });
34
+ this.scopes.set(scope, scopeMap);
35
+
36
+ // Bump the version so useSyncExternalStore sees a new snapshot.
37
+ this.version++;
38
+
39
+ // When called during render (silent=true), don't notify listeners —
40
+ // that would trigger setState in other components mid-render.
41
+ // Components that render after this point will see the updated snapshot.
42
+ if (!silent) {
43
+ this.notifyListeners();
44
+ }
45
+ return () => this.unregister(component, hocs, scope);
46
+ }
47
+ unregister(component, hocs, scope = "*") {
48
+ const scopeMap = this.scopes.get(scope);
49
+ if (!scopeMap) {
50
+ return;
51
+ }
52
+ const recipe = scopeMap.get(component);
53
+ if (!recipe) {
54
+ return;
55
+ }
56
+ const newHocs = recipe.hocs.filter(hoc => !hocs.includes(hoc));
57
+ if (newHocs.length === recipe.hocs.length) {
58
+ // Nothing was removed.
59
+ return;
60
+ }
61
+ if (newHocs.length === 0) {
62
+ scopeMap.delete(component);
63
+ } else {
64
+ scopeMap.set(component, {
65
+ component: compose(...[...newHocs].reverse())(component),
66
+ hocs: newHocs
67
+ });
68
+ }
69
+ this.version++;
70
+ this.notifyListeners();
71
+ }
72
+ getComponent(component, scope = []) {
73
+ const scopesToResolve = ["*", ...scope].reverse();
74
+ for (const s of scopesToResolve) {
75
+ const scopeMap = this.scopes.get(s);
76
+ if (!scopeMap) {
77
+ continue;
78
+ }
79
+ const composed = scopeMap.get(component);
80
+ if (composed) {
81
+ return composed.component;
82
+ }
83
+ }
84
+ return undefined;
85
+ }
86
+ subscribe = listener => {
87
+ this.listeners.add(listener);
88
+ return () => {
89
+ this.listeners.delete(listener);
90
+ };
91
+ };
92
+ getSnapshot = () => {
93
+ return this.version;
94
+ };
95
+ notifyListeners() {
96
+ for (const listener of this.listeners) {
97
+ listener();
98
+ }
99
+ }
100
+ }
101
+
102
+ //# sourceMappingURL=CompositionStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["compose","CompositionStore","scopes","Map","version","listeners","Set","register","component","hocs","scope","inherit","silent","scopeMap","get","recipe","newHocs","filter","hoc","includes","length","unregister","existingHocs","globalScope","globalRecipe","globalHocsToAdd","unshift","finalHocs","set","reverse","notifyListeners","delete","getComponent","scopesToResolve","s","composed","undefined","subscribe","listener","add","getSnapshot"],"sources":["CompositionStore.ts"],"sourcesContent":["import type { ComponentType } from \"react\";\nimport { compose } from \"~/Context.js\";\nimport type { Decorator, GenericComponent, GenericHook } from \"~/types.js\";\n\ninterface ComposedComponent {\n component: GenericHook | GenericComponent;\n hocs: Decorator<GenericComponent | GenericHook>[];\n}\n\ntype ComposedComponents = Map<ComponentType<unknown>, ComposedComponent>;\ntype ComponentScopes = Map<string, ComposedComponents>;\n\ntype Listener = () => void;\n\nexport class CompositionStore {\n private scopes: ComponentScopes = new Map();\n private version = 0;\n private listeners = new Set<Listener>();\n\n register(\n component: ComponentType<unknown>,\n hocs: Decorator<GenericComponent | GenericHook>[],\n scope = \"*\",\n inherit = false,\n silent = false\n ): () => void {\n const scopeMap: ComposedComponents = this.scopes.get(scope) || new Map();\n const recipe = scopeMap.get(component) || {\n component: component as any,\n hocs: [] as Decorator<GenericComponent | GenericHook>[]\n };\n\n // Idempotent: skip if all HOCs are already registered (handles StrictMode double-render).\n const newHocs = hocs.filter(hoc => !recipe.hocs.includes(hoc));\n if (newHocs.length === 0) {\n return () => this.unregister(component, hocs, scope);\n }\n\n const existingHocs = [...recipe.hocs];\n if (inherit && scope !== \"*\") {\n const globalScope = this.scopes.get(\"*\") || new Map();\n const globalRecipe = globalScope.get(component) || {\n component: component as any,\n hocs: [] as Decorator<GenericComponent | GenericHook>[]\n };\n // Only prepend global HOCs that aren't already present.\n const globalHocsToAdd = globalRecipe.hocs.filter(\n (hoc: Decorator<GenericComponent | GenericHook>) => !existingHocs.includes(hoc)\n );\n existingHocs.unshift(...globalHocsToAdd);\n }\n\n const finalHocs = [...existingHocs, ...newHocs];\n\n scopeMap.set(component, {\n component: compose(...[...finalHocs].reverse())(component as any),\n hocs: finalHocs\n });\n\n this.scopes.set(scope, scopeMap);\n\n // Bump the version so useSyncExternalStore sees a new snapshot.\n this.version++;\n\n // When called during render (silent=true), don't notify listeners —\n // that would trigger setState in other components mid-render.\n // Components that render after this point will see the updated snapshot.\n if (!silent) {\n this.notifyListeners();\n }\n\n return () => this.unregister(component, hocs, scope);\n }\n\n unregister(\n component: ComponentType<unknown>,\n hocs: Decorator<GenericComponent | GenericHook>[],\n scope = \"*\"\n ): void {\n const scopeMap = this.scopes.get(scope);\n if (!scopeMap) {\n return;\n }\n\n const recipe = scopeMap.get(component);\n if (!recipe) {\n return;\n }\n\n const newHocs = recipe.hocs.filter(hoc => !hocs.includes(hoc));\n if (newHocs.length === recipe.hocs.length) {\n // Nothing was removed.\n return;\n }\n\n if (newHocs.length === 0) {\n scopeMap.delete(component);\n } else {\n scopeMap.set(component, {\n component: compose(...[...newHocs].reverse())(component as any),\n hocs: newHocs\n });\n }\n\n this.version++;\n this.notifyListeners();\n }\n\n getComponent(\n component: ComponentType<unknown>,\n scope: string[] = []\n ): GenericComponent | GenericHook | undefined {\n const scopesToResolve = [\"*\", ...scope].reverse();\n for (const s of scopesToResolve) {\n const scopeMap = this.scopes.get(s);\n if (!scopeMap) {\n continue;\n }\n const composed = scopeMap.get(component);\n if (composed) {\n return composed.component;\n }\n }\n return undefined;\n }\n\n subscribe = (listener: Listener): (() => void) => {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n };\n\n getSnapshot = (): number => {\n return this.version;\n };\n\n private notifyListeners(): void {\n for (const listener of this.listeners) {\n listener();\n }\n }\n}\n"],"mappings":"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","ignoreList":[]}
package/index.d.ts CHANGED
@@ -5,4 +5,5 @@ export * from "./makeDecoratable.js";
5
5
  export * from "./createDecorator.js";
6
6
  export * from "./decorators.js";
7
7
  export * from "./CompositionScope.js";
8
+ export { CompositionStore } from "./domain/CompositionStore.js";
8
9
  export type * from "./types.js";
package/index.js CHANGED
@@ -5,5 +5,6 @@ export * from "./makeDecoratable.js";
5
5
  export * from "./createDecorator.js";
6
6
  export * from "./decorators.js";
7
7
  export * from "./CompositionScope.js";
8
+ export { CompositionStore } from "./domain/CompositionStore.js";
8
9
 
9
10
  //# sourceMappingURL=index.js.map
package/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["index.ts"],"sourcesContent":["export * from \"./Context.js\";\nexport * from \"./Compose.js\";\nexport * from \"./makeComposable.js\";\nexport * from \"./makeDecoratable.js\";\nexport * from \"./createDecorator.js\";\nexport * from \"./decorators.js\";\nexport * from \"./CompositionScope.js\";\nexport type * from \"./types.js\";\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA","ignoreList":[]}
1
+ {"version":3,"names":["CompositionStore"],"sources":["index.ts"],"sourcesContent":["export * from \"./Context.js\";\nexport * from \"./Compose.js\";\nexport * from \"./makeComposable.js\";\nexport * from \"./makeDecoratable.js\";\nexport * from \"./createDecorator.js\";\nexport * from \"./decorators.js\";\nexport * from \"./CompositionScope.js\";\nexport { CompositionStore } from \"./domain/CompositionStore.js\";\nexport type * from \"./types.js\";\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASA,gBAAgB","ignoreList":[]}
@@ -1,6 +1,41 @@
1
1
  import React, { createContext, useContext, useMemo } from "react";
2
2
  import { useComponent } from "./Context.js";
3
3
  import { withDecoratorFactory, withHookDecoratorFactory } from "./decorators.js";
4
+ class DecoratableErrorBoundary extends React.Component {
5
+ constructor(props) {
6
+ super(props);
7
+ this.state = {
8
+ hasError: false,
9
+ error: undefined
10
+ };
11
+ }
12
+ static getDerivedStateFromError(error) {
13
+ return {
14
+ hasError: true,
15
+ error
16
+ };
17
+ }
18
+ componentDidCatch(error, errorInfo) {
19
+ console.groupCollapsed(`%cCOMPONENT ERROR%c: "${this.props.name}" failed to render.`, "color:red", "color:default");
20
+ console.error(error, errorInfo);
21
+ console.groupEnd();
22
+ }
23
+ render() {
24
+ if (this.state.hasError) {
25
+ return /*#__PURE__*/React.createElement("div", {
26
+ style: {
27
+ padding: "8px 12px",
28
+ border: "1px solid #e53e3e",
29
+ borderRadius: 4,
30
+ background: "#fff5f5",
31
+ color: "#c53030",
32
+ fontSize: 13
33
+ }
34
+ }, /*#__PURE__*/React.createElement("strong", null, this.props.name), ": ", this.state.error?.message);
35
+ }
36
+ return this.props.children;
37
+ }
38
+ }
4
39
  const ComposableContext = /*#__PURE__*/createContext([]);
5
40
  ComposableContext.displayName = "ComposableContext";
6
41
  function useComposableParents() {
@@ -18,7 +53,9 @@ function makeDecoratableComponent(name, Component = nullRenderer) {
18
53
  const context = useMemo(() => [...parents, name], [parents, name]);
19
54
  return /*#__PURE__*/React.createElement(ComposableContext.Provider, {
20
55
  value: context
21
- }, /*#__PURE__*/React.createElement(ComposedComponent, props, props.children));
56
+ }, /*#__PURE__*/React.createElement(DecoratableErrorBoundary, {
57
+ name: name
58
+ }, /*#__PURE__*/React.createElement(ComposedComponent, props, props.children)));
22
59
  };
23
60
  const staticProps = {
24
61
  original: Component,
@@ -1 +1 @@
1
- {"version":3,"names":["React","createContext","useContext","useMemo","useComponent","withDecoratorFactory","withHookDecoratorFactory","ComposableContext","displayName","useComposableParents","context","nullRenderer","makeDecoratableComponent","name","Component","Decoratable","props","parents","ComposedComponent","createElement","Provider","value","children","staticProps","original","originalName","Object","assign","makeDecoratableHook","hook","decoratableHook","params","composedHook","createVoidComponent","makeDecoratable","hookOrName","component","memo"],"sources":["makeDecoratable.tsx"],"sourcesContent":["import React, { createContext, useContext, useMemo } from \"react\";\nimport { useComponent } from \"./Context.js\";\nimport type {\n DecoratableComponent,\n DecoratableHook,\n GenericComponent,\n GenericHook\n} from \"~/types.js\";\nimport { withDecoratorFactory, withHookDecoratorFactory } from \"~/decorators.js\";\n\nconst ComposableContext = createContext<string[]>([]);\nComposableContext.displayName = \"ComposableContext\";\n\nfunction useComposableParents() {\n const context = useContext(ComposableContext);\n if (!context) {\n return [];\n }\n\n return context;\n}\n\nconst nullRenderer = () => null;\n\nfunction makeDecoratableComponent<T extends GenericComponent>(\n name: string,\n Component: T = nullRenderer as unknown as T\n) {\n const Decoratable = (props: React.ComponentProps<T>): JSX.Element | null => {\n const parents = useComposableParents();\n const ComposedComponent = useComponent(Component) as GenericComponent<\n React.ComponentProps<T>\n >;\n\n const context = useMemo(() => [...parents, name], [parents, name]);\n\n return (\n <ComposableContext.Provider value={context}>\n <ComposedComponent {...props}>{props.children}</ComposedComponent>\n </ComposableContext.Provider>\n );\n };\n\n const staticProps = {\n original: Component,\n originalName: name,\n displayName: `Decoratable<${name}>`\n };\n\n return withDecoratorFactory()(\n Object.assign(Decoratable, staticProps) as DecoratableComponent<\n typeof Component & typeof staticProps\n >\n );\n}\n\nexport function makeDecoratableHook<T extends GenericHook>(hook: T) {\n const decoratableHook = (params: Parameters<T>) => {\n const composedHook = useComponent(hook);\n\n return composedHook(params) as DecoratableHook<T>;\n };\n\n decoratableHook.original = hook;\n\n return withHookDecoratorFactory()(decoratableHook as DecoratableHook<T>);\n}\n\nexport function createVoidComponent<T>() {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n return (props: T): JSX.Element | null => {\n return null;\n };\n}\n\nexport function makeDecoratable<T extends GenericHook>(\n hook: T\n): ReturnType<typeof makeDecoratableHook<T>>;\nexport function makeDecoratable<T extends GenericComponent>(\n name: string,\n Component: T\n): ReturnType<typeof makeDecoratableComponent<T>>;\nexport function makeDecoratable(hookOrName: any, Component?: any) {\n if (Component) {\n const component = makeDecoratableComponent(hookOrName, React.memo(Component));\n component.original.displayName = hookOrName;\n return component;\n }\n\n return makeDecoratableHook(hookOrName);\n}\n"],"mappings":"AAAA,OAAOA,KAAK,IAAIC,aAAa,EAAEC,UAAU,EAAEC,OAAO,QAAQ,OAAO;AACjE,SAASC,YAAY;AAOrB,SAASC,oBAAoB,EAAEC,wBAAwB;AAEvD,MAAMC,iBAAiB,gBAAGN,aAAa,CAAW,EAAE,CAAC;AACrDM,iBAAiB,CAACC,WAAW,GAAG,mBAAmB;AAEnD,SAASC,oBAAoBA,CAAA,EAAG;EAC5B,MAAMC,OAAO,GAAGR,UAAU,CAACK,iBAAiB,CAAC;EAC7C,IAAI,CAACG,OAAO,EAAE;IACV,OAAO,EAAE;EACb;EAEA,OAAOA,OAAO;AAClB;AAEA,MAAMC,YAAY,GAAGA,CAAA,KAAM,IAAI;AAE/B,SAASC,wBAAwBA,CAC7BC,IAAY,EACZC,SAAY,GAAGH,YAA4B,EAC7C;EACE,MAAMI,WAAW,GAAIC,KAA8B,IAAyB;IACxE,MAAMC,OAAO,GAAGR,oBAAoB,CAAC,CAAC;IACtC,MAAMS,iBAAiB,GAAGd,YAAY,CAACU,SAAS,CAE/C;IAED,MAAMJ,OAAO,GAAGP,OAAO,CAAC,MAAM,CAAC,GAAGc,OAAO,EAAEJ,IAAI,CAAC,EAAE,CAACI,OAAO,EAAEJ,IAAI,CAAC,CAAC;IAElE,oBACIb,KAAA,CAAAmB,aAAA,CAACZ,iBAAiB,CAACa,QAAQ;MAACC,KAAK,EAAEX;IAAQ,gBACvCV,KAAA,CAAAmB,aAAA,CAACD,iBAAiB,EAAKF,KAAK,EAAGA,KAAK,CAACM,QAA4B,CACzC,CAAC;EAErC,CAAC;EAED,MAAMC,WAAW,GAAG;IAChBC,QAAQ,EAAEV,SAAS;IACnBW,YAAY,EAAEZ,IAAI;IAClBL,WAAW,EAAE,eAAeK,IAAI;EACpC,CAAC;EAED,OAAOR,oBAAoB,CAAC,CAAC,CACzBqB,MAAM,CAACC,MAAM,CAACZ,WAAW,EAAEQ,WAAW,CAG1C,CAAC;AACL;AAEA,OAAO,SAASK,mBAAmBA,CAAwBC,IAAO,EAAE;EAChE,MAAMC,eAAe,GAAIC,MAAqB,IAAK;IAC/C,MAAMC,YAAY,GAAG5B,YAAY,CAACyB,IAAI,CAAC;IAEvC,OAAOG,YAAY,CAACD,MAAM,CAAC;EAC/B,CAAC;EAEDD,eAAe,CAACN,QAAQ,GAAGK,IAAI;EAE/B,OAAOvB,wBAAwB,CAAC,CAAC,CAACwB,eAAqC,CAAC;AAC5E;AAEA,OAAO,SAASG,mBAAmBA,CAAA,EAAM;EACrC;EACA,OAAQjB,KAAQ,IAAyB;IACrC,OAAO,IAAI;EACf,CAAC;AACL;AASA,OAAO,SAASkB,eAAeA,CAACC,UAAe,EAAErB,SAAe,EAAE;EAC9D,IAAIA,SAAS,EAAE;IACX,MAAMsB,SAAS,GAAGxB,wBAAwB,CAACuB,UAAU,eAAEnC,KAAK,CAACqC,IAAI,CAACvB,SAAS,CAAC,CAAC;IAC7EsB,SAAS,CAACZ,QAAQ,CAAChB,WAAW,GAAG2B,UAAU;IAC3C,OAAOC,SAAS;EACpB;EAEA,OAAOR,mBAAmB,CAACO,UAAU,CAAC;AAC1C","ignoreList":[]}
1
+ {"version":3,"names":["React","createContext","useContext","useMemo","useComponent","withDecoratorFactory","withHookDecoratorFactory","DecoratableErrorBoundary","Component","constructor","props","state","hasError","error","undefined","getDerivedStateFromError","componentDidCatch","errorInfo","console","groupCollapsed","name","groupEnd","render","createElement","style","padding","border","borderRadius","background","color","fontSize","message","children","ComposableContext","displayName","useComposableParents","context","nullRenderer","makeDecoratableComponent","Decoratable","parents","ComposedComponent","Provider","value","staticProps","original","originalName","Object","assign","makeDecoratableHook","hook","decoratableHook","params","composedHook","createVoidComponent","makeDecoratable","hookOrName","component","memo"],"sources":["makeDecoratable.tsx"],"sourcesContent":["import React, { createContext, useContext, useMemo } from \"react\";\nimport type { ErrorInfo } from \"react\";\nimport { useComponent } from \"./Context.js\";\nimport type {\n DecoratableComponent,\n DecoratableHook,\n GenericComponent,\n GenericHook\n} from \"~/types.js\";\nimport { withDecoratorFactory, withHookDecoratorFactory } from \"~/decorators.js\";\n\ninterface ErrorBoundaryProps {\n name: string;\n children: React.ReactNode;\n}\n\ninterface ErrorBoundaryState {\n hasError: boolean;\n error: Error | undefined;\n}\n\nclass DecoratableErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {\n constructor(props: ErrorBoundaryProps) {\n super(props);\n this.state = { hasError: false, error: undefined };\n }\n\n static getDerivedStateFromError(error: Error): ErrorBoundaryState {\n return { hasError: true, error };\n }\n\n override componentDidCatch(error: Error, errorInfo: ErrorInfo) {\n console.groupCollapsed(\n `%cCOMPONENT ERROR%c: \"${this.props.name}\" failed to render.`,\n \"color:red\",\n \"color:default\"\n );\n console.error(error, errorInfo);\n console.groupEnd();\n }\n\n override render() {\n if (this.state.hasError) {\n return (\n <div\n style={{\n padding: \"8px 12px\",\n border: \"1px solid #e53e3e\",\n borderRadius: 4,\n background: \"#fff5f5\",\n color: \"#c53030\",\n fontSize: 13\n }}\n >\n <strong>{this.props.name}</strong>: {this.state.error?.message}\n </div>\n );\n }\n return this.props.children;\n }\n}\n\nconst ComposableContext = createContext<string[]>([]);\nComposableContext.displayName = \"ComposableContext\";\n\nfunction useComposableParents() {\n const context = useContext(ComposableContext);\n if (!context) {\n return [];\n }\n\n return context;\n}\n\nconst nullRenderer = () => null;\n\nfunction makeDecoratableComponent<T extends GenericComponent>(\n name: string,\n Component: T = nullRenderer as unknown as T\n) {\n const Decoratable = (props: React.ComponentProps<T>): JSX.Element | null => {\n const parents = useComposableParents();\n const ComposedComponent = useComponent(Component) as GenericComponent<\n React.ComponentProps<T>\n >;\n\n const context = useMemo(() => [...parents, name], [parents, name]);\n\n return (\n <ComposableContext.Provider value={context}>\n <DecoratableErrorBoundary name={name}>\n <ComposedComponent {...props}>{props.children}</ComposedComponent>\n </DecoratableErrorBoundary>\n </ComposableContext.Provider>\n );\n };\n\n const staticProps = {\n original: Component,\n originalName: name,\n displayName: `Decoratable<${name}>`\n };\n\n return withDecoratorFactory()(\n Object.assign(Decoratable, staticProps) as DecoratableComponent<\n typeof Component & typeof staticProps\n >\n );\n}\n\nexport function makeDecoratableHook<T extends GenericHook>(hook: T) {\n const decoratableHook = (params: Parameters<T>) => {\n const composedHook = useComponent(hook);\n\n return composedHook(params) as DecoratableHook<T>;\n };\n\n decoratableHook.original = hook;\n\n return withHookDecoratorFactory()(decoratableHook as DecoratableHook<T>);\n}\n\nexport function createVoidComponent<T>() {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n return (props: T): JSX.Element | null => {\n return null;\n };\n}\n\nexport function makeDecoratable<T extends GenericHook>(\n hook: T\n): ReturnType<typeof makeDecoratableHook<T>>;\nexport function makeDecoratable<T extends GenericComponent>(\n name: string,\n Component: T\n): ReturnType<typeof makeDecoratableComponent<T>>;\nexport function makeDecoratable(hookOrName: any, Component?: any) {\n if (Component) {\n const component = makeDecoratableComponent(hookOrName, React.memo(Component));\n component.original.displayName = hookOrName;\n return component;\n }\n\n return makeDecoratableHook(hookOrName);\n}\n"],"mappings":"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","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webiny/react-composition",
3
- "version": "0.0.0-unstable.df7a8bb475",
3
+ "version": "0.0.0-unstable.e2758ee1cf",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -21,13 +21,13 @@
21
21
  },
22
22
  "devDependencies": {
23
23
  "@testing-library/react": "15.0.7",
24
- "@webiny/build-tools": "0.0.0-unstable.df7a8bb475",
24
+ "@webiny/build-tools": "0.0.0-unstable.e2758ee1cf",
25
25
  "typescript": "5.9.3",
26
- "vitest": "3.2.4"
26
+ "vitest": "4.0.18"
27
27
  },
28
28
  "publishConfig": {
29
29
  "access": "public",
30
30
  "directory": "dist"
31
31
  },
32
- "gitHead": "df7a8bb4755a1da047f0af8c56bdb649cc81bf7d"
32
+ "gitHead": "e2758ee1cfa3b9a7152e9bb995a90ccabd33266f"
33
33
  }