@elementor/locations 4.3.0-986 → 4.3.0-988
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/dist/index.js +40 -10
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +41 -11
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/create-location.tsx +13 -4
- package/src/create-replaceable-location.tsx +16 -4
- package/src/injections.tsx +36 -3
package/dist/index.js
CHANGED
|
@@ -75,11 +75,31 @@ var flushInjectionsFns = [];
|
|
|
75
75
|
function flushAllInjections() {
|
|
76
76
|
flushInjectionsFns.forEach((flush) => flush());
|
|
77
77
|
}
|
|
78
|
+
function createSubscription() {
|
|
79
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
80
|
+
return {
|
|
81
|
+
subscribe: (listener) => {
|
|
82
|
+
listeners.add(listener);
|
|
83
|
+
return () => listeners.delete(listener);
|
|
84
|
+
},
|
|
85
|
+
notify: () => listeners.forEach((listener) => listener())
|
|
86
|
+
};
|
|
87
|
+
}
|
|
78
88
|
function createGetInjections(injections) {
|
|
79
89
|
return () => [...injections.values()].sort((a, b) => a.priority - b.priority);
|
|
80
90
|
}
|
|
81
|
-
function createUseInjections(getInjections) {
|
|
82
|
-
|
|
91
|
+
function createUseInjections(getInjections, subscribe) {
|
|
92
|
+
let snapshot = null;
|
|
93
|
+
subscribe(() => {
|
|
94
|
+
snapshot = null;
|
|
95
|
+
});
|
|
96
|
+
const getSnapshot = () => {
|
|
97
|
+
if (!snapshot) {
|
|
98
|
+
snapshot = getInjections();
|
|
99
|
+
}
|
|
100
|
+
return snapshot;
|
|
101
|
+
};
|
|
102
|
+
return () => (0, import_react3.useSyncExternalStore)(subscribe, getSnapshot);
|
|
83
103
|
}
|
|
84
104
|
function wrapInjectedComponent(Component2) {
|
|
85
105
|
return (props) => /* @__PURE__ */ React2.createElement(InjectedComponentWrapper, null, /* @__PURE__ */ React2.createElement(Component2, { ...props }));
|
|
@@ -88,11 +108,15 @@ function wrapInjectedComponent(Component2) {
|
|
|
88
108
|
// src/create-location.tsx
|
|
89
109
|
function createLocation() {
|
|
90
110
|
const injections = /* @__PURE__ */ new Map();
|
|
111
|
+
const { subscribe, notify } = createSubscription();
|
|
91
112
|
const getInjections = createGetInjections(injections);
|
|
92
|
-
const useInjections = createUseInjections(getInjections);
|
|
113
|
+
const useInjections = createUseInjections(getInjections, subscribe);
|
|
93
114
|
const Slot = createSlot(useInjections);
|
|
94
|
-
const inject = createInject(injections);
|
|
95
|
-
flushInjectionsFns.push(() =>
|
|
115
|
+
const inject = createInject(injections, notify);
|
|
116
|
+
flushInjectionsFns.push(() => {
|
|
117
|
+
injections.clear();
|
|
118
|
+
notify();
|
|
119
|
+
});
|
|
96
120
|
return {
|
|
97
121
|
inject,
|
|
98
122
|
getInjections,
|
|
@@ -106,7 +130,7 @@ function createSlot(useInjections) {
|
|
|
106
130
|
return /* @__PURE__ */ React3.createElement(React3.Fragment, null, injections.map(({ id, component: Component2 }) => /* @__PURE__ */ React3.createElement(Component2, { ...props, key: id })));
|
|
107
131
|
};
|
|
108
132
|
}
|
|
109
|
-
function createInject(injections) {
|
|
133
|
+
function createInject(injections, notify) {
|
|
110
134
|
return ({ component, id, options = {} }) => {
|
|
111
135
|
if (injections.has(id) && !options?.overwrite) {
|
|
112
136
|
console.warn(
|
|
@@ -119,6 +143,7 @@ function createInject(injections) {
|
|
|
119
143
|
component: wrapInjectedComponent(component),
|
|
120
144
|
priority: options.priority ?? DEFAULT_PRIORITY
|
|
121
145
|
});
|
|
146
|
+
notify();
|
|
122
147
|
};
|
|
123
148
|
}
|
|
124
149
|
|
|
@@ -126,11 +151,15 @@ function createInject(injections) {
|
|
|
126
151
|
var React4 = __toESM(require("react"));
|
|
127
152
|
function createReplaceableLocation() {
|
|
128
153
|
const injections = /* @__PURE__ */ new Map();
|
|
154
|
+
const { subscribe, notify } = createSubscription();
|
|
129
155
|
const getInjections = createGetInjections(injections);
|
|
130
|
-
const useInjections = createUseInjections(getInjections);
|
|
156
|
+
const useInjections = createUseInjections(getInjections, subscribe);
|
|
131
157
|
const Slot = createReplaceable(useInjections);
|
|
132
|
-
const inject = createRegister(injections);
|
|
133
|
-
flushInjectionsFns.push(() =>
|
|
158
|
+
const inject = createRegister(injections, notify);
|
|
159
|
+
flushInjectionsFns.push(() => {
|
|
160
|
+
injections.clear();
|
|
161
|
+
notify();
|
|
162
|
+
});
|
|
134
163
|
return {
|
|
135
164
|
getInjections,
|
|
136
165
|
useInjections,
|
|
@@ -148,7 +177,7 @@ function createReplaceable(useInjections) {
|
|
|
148
177
|
return /* @__PURE__ */ React4.createElement(Component2, { ...props });
|
|
149
178
|
};
|
|
150
179
|
}
|
|
151
|
-
function createRegister(injections) {
|
|
180
|
+
function createRegister(injections, notify) {
|
|
152
181
|
return ({ component, id, condition = () => true, options = {} }) => {
|
|
153
182
|
injections.set(id, {
|
|
154
183
|
id,
|
|
@@ -156,6 +185,7 @@ function createRegister(injections) {
|
|
|
156
185
|
condition,
|
|
157
186
|
priority: options.priority ?? DEFAULT_PRIORITY
|
|
158
187
|
});
|
|
188
|
+
notify();
|
|
159
189
|
};
|
|
160
190
|
}
|
|
161
191
|
// Annotate the CommonJS export names for ESM import in node:
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/create-location.tsx","../src/injections.tsx","../src/components/injected-component-wrapper.tsx","../src/components/error-boundary.tsx","../src/create-replaceable-location.tsx"],"sourcesContent":["export * from './types';\n\nexport { createLocation } from './create-location';\nexport { createReplaceableLocation } from './create-replaceable-location';\nexport { flushAllInjections as __flushAllInjections } from './injections';\n","import * as React from 'react';\n\nimport {\n\tcreateGetInjections,\n\tcreateUseInjections,\n\tDEFAULT_PRIORITY,\n\tflushInjectionsFns,\n\twrapInjectedComponent,\n} from './injections';\nimport { type AnyProps, type Id, type InjectArgs, type Injection, type Location } from './types';\n\ntype InjectionsMap< TProps extends object = AnyProps > = Map< Id, Injection< TProps > >;\n\nexport function createLocation< TProps extends object = AnyProps >(): Location< TProps > {\n\tconst injections: InjectionsMap< TProps > = new Map();\n\n\tconst getInjections = createGetInjections( injections );\n\tconst useInjections = createUseInjections( getInjections );\n\tconst Slot = createSlot( useInjections );\n\tconst inject = createInject( injections );\n\n\t// Push the clear function to the flushInjectionsFns array, so we can flush all injections at once.\n\tflushInjectionsFns.push( () => injections.clear() );\n\n\treturn {\n\t\tinject,\n\t\tgetInjections,\n\t\tuseInjections,\n\t\tSlot,\n\t};\n}\n\nfunction createSlot< TProps extends object = AnyProps >( useInjections: Location< TProps >[ 'useInjections' ] ) {\n\treturn ( props: TProps ) => {\n\t\tconst injections = useInjections();\n\n\t\treturn (\n\t\t\t<>\n\t\t\t\t{ injections.map( ( { id, component: Component } ) => (\n\t\t\t\t\t<Component { ...props } key={ id } />\n\t\t\t\t) ) }\n\t\t\t</>\n\t\t);\n\t};\n}\n\nfunction createInject< TProps extends object = AnyProps >( injections: InjectionsMap< TProps > ) {\n\treturn ( { component, id, options = {} }: InjectArgs< TProps > ) => {\n\t\tif ( injections.has( id ) && ! options?.overwrite ) {\n\t\t\t// eslint-disable-next-line no-console\n\t\t\tconsole.warn(\n\t\t\t\t`An injection with the id \"${ id }\" already exists. Did you mean to use \"options.overwrite\"?`\n\t\t\t);\n\n\t\t\treturn;\n\t\t}\n\n\t\tinjections.set( id, {\n\t\t\tid,\n\t\t\tcomponent: wrapInjectedComponent( component ),\n\t\t\tpriority: options.priority ?? DEFAULT_PRIORITY,\n\t\t} );\n\t};\n}\n","import * as React from 'react';\nimport { useMemo } from 'react';\n\nimport InjectedComponentWrapper from './components/injected-component-wrapper';\nimport { type AnyProps, type Id, type InjectedComponent, type Injection, type Location } from './types';\n\ntype InjectionsMap< TProps extends object = AnyProps > = Map< Id, Injection< TProps > >;\n\nexport const DEFAULT_PRIORITY = 10;\n\n// Allow flushing all injections at once, for testing purposes.\nexport const flushInjectionsFns: ( () => void )[] = [];\n\nexport function flushAllInjections() {\n\tflushInjectionsFns.forEach( ( flush ) => flush() );\n}\n\nexport function createGetInjections< TProps extends object = AnyProps >( injections: InjectionsMap< TProps > ) {\n\treturn () => [ ...injections.values() ].sort( ( a, b ) => a.priority - b.priority );\n}\n\nexport function createUseInjections< TProps extends object = AnyProps >(\n\tgetInjections: Location< TProps >[ 'getInjections' ]\n) {\n\treturn () => useMemo( () => getInjections(), [] );\n}\n\nexport function wrapInjectedComponent< TProps extends object = AnyProps >( Component: InjectedComponent< TProps > ) {\n\treturn ( props: TProps ) => (\n\t\t<InjectedComponentWrapper>\n\t\t\t<Component { ...props } />\n\t\t</InjectedComponentWrapper>\n\t);\n}\n","import * as React from 'react';\nimport { type ReactNode, Suspense } from 'react';\n\nimport ErrorBoundary from './error-boundary';\n\nexport default function InjectedComponentWrapper( { children }: { children: ReactNode } ) {\n\treturn (\n\t\t<ErrorBoundary fallback={ null }>\n\t\t\t<Suspense fallback={ null }>{ children }</Suspense>\n\t\t</ErrorBoundary>\n\t);\n}\n","import { Component, type ReactNode } from 'react';\n\ninterface Props {\n\tchildren?: ReactNode;\n\tfallback: ReactNode;\n}\n\ninterface State {\n\thasError: boolean;\n}\n\nexport default class ErrorBoundary extends Component< Props, State > {\n\tpublic state: State = {\n\t\thasError: false,\n\t};\n\n\tpublic static getDerivedStateFromError(): State {\n\t\t// Update state so the next render will show the fallback UI.\n\t\treturn { hasError: true };\n\t}\n\n\tpublic render() {\n\t\tif ( this.state.hasError ) {\n\t\t\treturn this.props.fallback;\n\t\t}\n\n\t\treturn this.props.children;\n\t}\n}\n","import * as React from 'react';\nimport { type PropsWithChildren } from 'react';\n\nimport {\n\tcreateGetInjections,\n\tcreateUseInjections,\n\tDEFAULT_PRIORITY,\n\tflushInjectionsFns,\n\twrapInjectedComponent,\n} from './injections';\nimport {\n\ttype AnyProps,\n\ttype Id,\n\ttype ReplaceableInjectArgs,\n\ttype ReplaceableInjection,\n\ttype ReplaceableLocation,\n} from './types';\n\ntype ReplaceableInjectionsMap< TProps extends object = AnyProps > = Map< Id, ReplaceableInjection< TProps > >;\n\nexport function createReplaceableLocation< TProps extends object = AnyProps >(): ReplaceableLocation< TProps > {\n\tconst injections: ReplaceableInjectionsMap< TProps > = new Map();\n\n\tconst getInjections = createGetInjections( injections );\n\tconst useInjections = createUseInjections( getInjections );\n\tconst Slot = createReplaceable( useInjections );\n\tconst inject = createRegister( injections );\n\n\t// Push the clear function to the flushInjectionsFns array, so we can flush all injections at once.\n\tflushInjectionsFns.push( () => injections.clear() );\n\n\treturn {\n\t\tgetInjections,\n\t\tuseInjections,\n\t\tinject,\n\t\tSlot,\n\t};\n}\n\nfunction createReplaceable< TProps extends PropsWithChildren< object > = AnyProps >(\n\tuseInjections: ReplaceableLocation< TProps >[ 'useInjections' ]\n) {\n\treturn ( props: TProps ) => {\n\t\tconst injections = useInjections();\n\n\t\tconst { component: Component } = injections.find( ( { condition } ) => condition?.( props ) ) ?? {};\n\n\t\tif ( ! Component ) {\n\t\t\treturn props.children;\n\t\t}\n\n\t\treturn <Component { ...props } />;\n\t};\n}\n\nfunction createRegister< TProps extends object = AnyProps >( injections: ReplaceableInjectionsMap< TProps > ) {\n\treturn ( { component, id, condition = () => true, options = {} }: ReplaceableInjectArgs< TProps > ) => {\n\t\tinjections.set( id, {\n\t\t\tid,\n\t\t\tcomponent: wrapInjectedComponent( component ),\n\t\t\tcondition,\n\t\t\tpriority: options.priority ?? DEFAULT_PRIORITY,\n\t\t} );\n\t};\n}\n"],"mappings":"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","names":["React","React","import_react","import_react","Component","Component","React","Component"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/create-location.tsx","../src/injections.tsx","../src/components/injected-component-wrapper.tsx","../src/components/error-boundary.tsx","../src/create-replaceable-location.tsx"],"sourcesContent":["export * from './types';\n\nexport { createLocation } from './create-location';\nexport { createReplaceableLocation } from './create-replaceable-location';\nexport { flushAllInjections as __flushAllInjections } from './injections';\n","import * as React from 'react';\n\nimport {\n\tcreateGetInjections,\n\tcreateSubscription,\n\tcreateUseInjections,\n\tDEFAULT_PRIORITY,\n\tflushInjectionsFns,\n\twrapInjectedComponent,\n} from './injections';\nimport { type AnyProps, type Id, type InjectArgs, type Injection, type Location } from './types';\n\ntype InjectionsMap< TProps extends object = AnyProps > = Map< Id, Injection< TProps > >;\n\nexport function createLocation< TProps extends object = AnyProps >(): Location< TProps > {\n\tconst injections: InjectionsMap< TProps > = new Map();\n\tconst { subscribe, notify } = createSubscription();\n\n\tconst getInjections = createGetInjections( injections );\n\tconst useInjections = createUseInjections( getInjections, subscribe );\n\tconst Slot = createSlot( useInjections );\n\tconst inject = createInject( injections, notify );\n\n\t// Push the clear function to the flushInjectionsFns array, so we can flush all injections at once.\n\t// `notify()` is called too, so any mounted `Slot` (and its cached snapshot) reflects the flush,\n\t// which matters for test isolation between test cases.\n\tflushInjectionsFns.push( () => {\n\t\tinjections.clear();\n\t\tnotify();\n\t} );\n\n\treturn {\n\t\tinject,\n\t\tgetInjections,\n\t\tuseInjections,\n\t\tSlot,\n\t};\n}\n\nfunction createSlot< TProps extends object = AnyProps >( useInjections: Location< TProps >[ 'useInjections' ] ) {\n\treturn ( props: TProps ) => {\n\t\tconst injections = useInjections();\n\n\t\treturn (\n\t\t\t<>\n\t\t\t\t{ injections.map( ( { id, component: Component } ) => (\n\t\t\t\t\t<Component { ...props } key={ id } />\n\t\t\t\t) ) }\n\t\t\t</>\n\t\t);\n\t};\n}\n\nfunction createInject< TProps extends object = AnyProps >( injections: InjectionsMap< TProps >, notify: () => void ) {\n\treturn ( { component, id, options = {} }: InjectArgs< TProps > ) => {\n\t\tif ( injections.has( id ) && ! options?.overwrite ) {\n\t\t\t// eslint-disable-next-line no-console\n\t\t\tconsole.warn(\n\t\t\t\t`An injection with the id \"${ id }\" already exists. Did you mean to use \"options.overwrite\"?`\n\t\t\t);\n\n\t\t\treturn;\n\t\t}\n\n\t\tinjections.set( id, {\n\t\t\tid,\n\t\t\tcomponent: wrapInjectedComponent( component ),\n\t\t\tpriority: options.priority ?? DEFAULT_PRIORITY,\n\t\t} );\n\n\t\tnotify();\n\t};\n}\n","import * as React from 'react';\nimport { useSyncExternalStore } from 'react';\n\nimport InjectedComponentWrapper from './components/injected-component-wrapper';\nimport { type AnyProps, type Id, type InjectedComponent, type Injection, type Location } from './types';\n\ntype InjectionsMap< TProps extends object = AnyProps > = Map< Id, Injection< TProps > >;\n\nexport const DEFAULT_PRIORITY = 10;\n\n// Allow flushing all injections at once, for testing purposes.\nexport const flushInjectionsFns: ( () => void )[] = [];\n\nexport function flushAllInjections() {\n\tflushInjectionsFns.forEach( ( flush ) => flush() );\n}\n\nexport type Subscribe = ( listener: () => void ) => () => void;\n\nexport function createSubscription() {\n\tconst listeners = new Set< () => void >();\n\n\treturn {\n\t\tsubscribe: ( listener: () => void ) => {\n\t\t\tlisteners.add( listener );\n\n\t\t\treturn () => listeners.delete( listener );\n\t\t},\n\t\tnotify: () => listeners.forEach( ( listener ) => listener() ),\n\t};\n}\n\nexport function createGetInjections< TProps extends object = AnyProps >( injections: InjectionsMap< TProps > ) {\n\treturn () => [ ...injections.values() ].sort( ( a, b ) => a.priority - b.priority );\n}\n\n// Injections registered after the `Slot` has already mounted (e.g. by an external plugin's\n// script that loads after the editor has rendered) must still be reflected in the UI, so the\n// snapshot is invalidated and re-read whenever `notify()` is called.\nexport function createUseInjections< TProps extends object = AnyProps >(\n\tgetInjections: Location< TProps >[ 'getInjections' ],\n\tsubscribe: Subscribe\n) {\n\tlet snapshot: ReturnType< Location< TProps >[ 'getInjections' ] > | null = null;\n\n\tsubscribe( () => {\n\t\tsnapshot = null;\n\t} );\n\n\tconst getSnapshot = () => {\n\t\tif ( ! snapshot ) {\n\t\t\tsnapshot = getInjections();\n\t\t}\n\n\t\treturn snapshot;\n\t};\n\n\treturn () => useSyncExternalStore( subscribe, getSnapshot );\n}\n\nexport function wrapInjectedComponent< TProps extends object = AnyProps >( Component: InjectedComponent< TProps > ) {\n\treturn ( props: TProps ) => (\n\t\t<InjectedComponentWrapper>\n\t\t\t<Component { ...props } />\n\t\t</InjectedComponentWrapper>\n\t);\n}\n","import * as React from 'react';\nimport { type ReactNode, Suspense } from 'react';\n\nimport ErrorBoundary from './error-boundary';\n\nexport default function InjectedComponentWrapper( { children }: { children: ReactNode } ) {\n\treturn (\n\t\t<ErrorBoundary fallback={ null }>\n\t\t\t<Suspense fallback={ null }>{ children }</Suspense>\n\t\t</ErrorBoundary>\n\t);\n}\n","import { Component, type ReactNode } from 'react';\n\ninterface Props {\n\tchildren?: ReactNode;\n\tfallback: ReactNode;\n}\n\ninterface State {\n\thasError: boolean;\n}\n\nexport default class ErrorBoundary extends Component< Props, State > {\n\tpublic state: State = {\n\t\thasError: false,\n\t};\n\n\tpublic static getDerivedStateFromError(): State {\n\t\t// Update state so the next render will show the fallback UI.\n\t\treturn { hasError: true };\n\t}\n\n\tpublic render() {\n\t\tif ( this.state.hasError ) {\n\t\t\treturn this.props.fallback;\n\t\t}\n\n\t\treturn this.props.children;\n\t}\n}\n","import * as React from 'react';\nimport { type PropsWithChildren } from 'react';\n\nimport {\n\tcreateGetInjections,\n\tcreateSubscription,\n\tcreateUseInjections,\n\tDEFAULT_PRIORITY,\n\tflushInjectionsFns,\n\twrapInjectedComponent,\n} from './injections';\nimport {\n\ttype AnyProps,\n\ttype Id,\n\ttype ReplaceableInjectArgs,\n\ttype ReplaceableInjection,\n\ttype ReplaceableLocation,\n} from './types';\n\ntype ReplaceableInjectionsMap< TProps extends object = AnyProps > = Map< Id, ReplaceableInjection< TProps > >;\n\nexport function createReplaceableLocation< TProps extends object = AnyProps >(): ReplaceableLocation< TProps > {\n\tconst injections: ReplaceableInjectionsMap< TProps > = new Map();\n\tconst { subscribe, notify } = createSubscription();\n\n\tconst getInjections = createGetInjections( injections );\n\tconst useInjections = createUseInjections( getInjections, subscribe );\n\tconst Slot = createReplaceable( useInjections );\n\tconst inject = createRegister( injections, notify );\n\n\t// Push the clear function to the flushInjectionsFns array, so we can flush all injections at once.\n\t// `notify()` is called too, so any mounted `Slot` (and its cached snapshot) reflects the flush,\n\t// which matters for test isolation between test cases.\n\tflushInjectionsFns.push( () => {\n\t\tinjections.clear();\n\t\tnotify();\n\t} );\n\n\treturn {\n\t\tgetInjections,\n\t\tuseInjections,\n\t\tinject,\n\t\tSlot,\n\t};\n}\n\nfunction createReplaceable< TProps extends PropsWithChildren< object > = AnyProps >(\n\tuseInjections: ReplaceableLocation< TProps >[ 'useInjections' ]\n) {\n\treturn ( props: TProps ) => {\n\t\tconst injections = useInjections();\n\n\t\tconst { component: Component } = injections.find( ( { condition } ) => condition?.( props ) ) ?? {};\n\n\t\tif ( ! Component ) {\n\t\t\treturn props.children;\n\t\t}\n\n\t\treturn <Component { ...props } />;\n\t};\n}\n\nfunction createRegister< TProps extends object = AnyProps >(\n\tinjections: ReplaceableInjectionsMap< TProps >,\n\tnotify: () => void\n) {\n\treturn ( { component, id, condition = () => true, options = {} }: ReplaceableInjectArgs< TProps > ) => {\n\t\tinjections.set( id, {\n\t\t\tid,\n\t\t\tcomponent: wrapInjectedComponent( component ),\n\t\t\tcondition,\n\t\t\tpriority: options.priority ?? DEFAULT_PRIORITY,\n\t\t} );\n\n\t\tnotify();\n\t};\n}\n"],"mappings":"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","names":["React","React","import_react","import_react","Component","Component","React","Component"]}
|
package/dist/index.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import * as React3 from "react";
|
|
|
3
3
|
|
|
4
4
|
// src/injections.tsx
|
|
5
5
|
import * as React2 from "react";
|
|
6
|
-
import {
|
|
6
|
+
import { useSyncExternalStore } from "react";
|
|
7
7
|
|
|
8
8
|
// src/components/injected-component-wrapper.tsx
|
|
9
9
|
import * as React from "react";
|
|
@@ -37,11 +37,31 @@ var flushInjectionsFns = [];
|
|
|
37
37
|
function flushAllInjections() {
|
|
38
38
|
flushInjectionsFns.forEach((flush) => flush());
|
|
39
39
|
}
|
|
40
|
+
function createSubscription() {
|
|
41
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
42
|
+
return {
|
|
43
|
+
subscribe: (listener) => {
|
|
44
|
+
listeners.add(listener);
|
|
45
|
+
return () => listeners.delete(listener);
|
|
46
|
+
},
|
|
47
|
+
notify: () => listeners.forEach((listener) => listener())
|
|
48
|
+
};
|
|
49
|
+
}
|
|
40
50
|
function createGetInjections(injections) {
|
|
41
51
|
return () => [...injections.values()].sort((a, b) => a.priority - b.priority);
|
|
42
52
|
}
|
|
43
|
-
function createUseInjections(getInjections) {
|
|
44
|
-
|
|
53
|
+
function createUseInjections(getInjections, subscribe) {
|
|
54
|
+
let snapshot = null;
|
|
55
|
+
subscribe(() => {
|
|
56
|
+
snapshot = null;
|
|
57
|
+
});
|
|
58
|
+
const getSnapshot = () => {
|
|
59
|
+
if (!snapshot) {
|
|
60
|
+
snapshot = getInjections();
|
|
61
|
+
}
|
|
62
|
+
return snapshot;
|
|
63
|
+
};
|
|
64
|
+
return () => useSyncExternalStore(subscribe, getSnapshot);
|
|
45
65
|
}
|
|
46
66
|
function wrapInjectedComponent(Component2) {
|
|
47
67
|
return (props) => /* @__PURE__ */ React2.createElement(InjectedComponentWrapper, null, /* @__PURE__ */ React2.createElement(Component2, { ...props }));
|
|
@@ -50,11 +70,15 @@ function wrapInjectedComponent(Component2) {
|
|
|
50
70
|
// src/create-location.tsx
|
|
51
71
|
function createLocation() {
|
|
52
72
|
const injections = /* @__PURE__ */ new Map();
|
|
73
|
+
const { subscribe, notify } = createSubscription();
|
|
53
74
|
const getInjections = createGetInjections(injections);
|
|
54
|
-
const useInjections = createUseInjections(getInjections);
|
|
75
|
+
const useInjections = createUseInjections(getInjections, subscribe);
|
|
55
76
|
const Slot = createSlot(useInjections);
|
|
56
|
-
const inject = createInject(injections);
|
|
57
|
-
flushInjectionsFns.push(() =>
|
|
77
|
+
const inject = createInject(injections, notify);
|
|
78
|
+
flushInjectionsFns.push(() => {
|
|
79
|
+
injections.clear();
|
|
80
|
+
notify();
|
|
81
|
+
});
|
|
58
82
|
return {
|
|
59
83
|
inject,
|
|
60
84
|
getInjections,
|
|
@@ -68,7 +92,7 @@ function createSlot(useInjections) {
|
|
|
68
92
|
return /* @__PURE__ */ React3.createElement(React3.Fragment, null, injections.map(({ id, component: Component2 }) => /* @__PURE__ */ React3.createElement(Component2, { ...props, key: id })));
|
|
69
93
|
};
|
|
70
94
|
}
|
|
71
|
-
function createInject(injections) {
|
|
95
|
+
function createInject(injections, notify) {
|
|
72
96
|
return ({ component, id, options = {} }) => {
|
|
73
97
|
if (injections.has(id) && !options?.overwrite) {
|
|
74
98
|
console.warn(
|
|
@@ -81,6 +105,7 @@ function createInject(injections) {
|
|
|
81
105
|
component: wrapInjectedComponent(component),
|
|
82
106
|
priority: options.priority ?? DEFAULT_PRIORITY
|
|
83
107
|
});
|
|
108
|
+
notify();
|
|
84
109
|
};
|
|
85
110
|
}
|
|
86
111
|
|
|
@@ -88,11 +113,15 @@ function createInject(injections) {
|
|
|
88
113
|
import * as React4 from "react";
|
|
89
114
|
function createReplaceableLocation() {
|
|
90
115
|
const injections = /* @__PURE__ */ new Map();
|
|
116
|
+
const { subscribe, notify } = createSubscription();
|
|
91
117
|
const getInjections = createGetInjections(injections);
|
|
92
|
-
const useInjections = createUseInjections(getInjections);
|
|
118
|
+
const useInjections = createUseInjections(getInjections, subscribe);
|
|
93
119
|
const Slot = createReplaceable(useInjections);
|
|
94
|
-
const inject = createRegister(injections);
|
|
95
|
-
flushInjectionsFns.push(() =>
|
|
120
|
+
const inject = createRegister(injections, notify);
|
|
121
|
+
flushInjectionsFns.push(() => {
|
|
122
|
+
injections.clear();
|
|
123
|
+
notify();
|
|
124
|
+
});
|
|
96
125
|
return {
|
|
97
126
|
getInjections,
|
|
98
127
|
useInjections,
|
|
@@ -110,7 +139,7 @@ function createReplaceable(useInjections) {
|
|
|
110
139
|
return /* @__PURE__ */ React4.createElement(Component2, { ...props });
|
|
111
140
|
};
|
|
112
141
|
}
|
|
113
|
-
function createRegister(injections) {
|
|
142
|
+
function createRegister(injections, notify) {
|
|
114
143
|
return ({ component, id, condition = () => true, options = {} }) => {
|
|
115
144
|
injections.set(id, {
|
|
116
145
|
id,
|
|
@@ -118,6 +147,7 @@ function createRegister(injections) {
|
|
|
118
147
|
condition,
|
|
119
148
|
priority: options.priority ?? DEFAULT_PRIORITY
|
|
120
149
|
});
|
|
150
|
+
notify();
|
|
121
151
|
};
|
|
122
152
|
}
|
|
123
153
|
export {
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/create-location.tsx","../src/injections.tsx","../src/components/injected-component-wrapper.tsx","../src/components/error-boundary.tsx","../src/create-replaceable-location.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport {\n\tcreateGetInjections,\n\tcreateUseInjections,\n\tDEFAULT_PRIORITY,\n\tflushInjectionsFns,\n\twrapInjectedComponent,\n} from './injections';\nimport { type AnyProps, type Id, type InjectArgs, type Injection, type Location } from './types';\n\ntype InjectionsMap< TProps extends object = AnyProps > = Map< Id, Injection< TProps > >;\n\nexport function createLocation< TProps extends object = AnyProps >(): Location< TProps > {\n\tconst injections: InjectionsMap< TProps > = new Map();\n\n\tconst getInjections = createGetInjections( injections );\n\tconst useInjections = createUseInjections( getInjections );\n\tconst Slot = createSlot( useInjections );\n\tconst inject = createInject( injections );\n\n\t// Push the clear function to the flushInjectionsFns array, so we can flush all injections at once.\n\tflushInjectionsFns.push( () => injections.clear() );\n\n\treturn {\n\t\tinject,\n\t\tgetInjections,\n\t\tuseInjections,\n\t\tSlot,\n\t};\n}\n\nfunction createSlot< TProps extends object = AnyProps >( useInjections: Location< TProps >[ 'useInjections' ] ) {\n\treturn ( props: TProps ) => {\n\t\tconst injections = useInjections();\n\n\t\treturn (\n\t\t\t<>\n\t\t\t\t{ injections.map( ( { id, component: Component } ) => (\n\t\t\t\t\t<Component { ...props } key={ id } />\n\t\t\t\t) ) }\n\t\t\t</>\n\t\t);\n\t};\n}\n\nfunction createInject< TProps extends object = AnyProps >( injections: InjectionsMap< TProps > ) {\n\treturn ( { component, id, options = {} }: InjectArgs< TProps > ) => {\n\t\tif ( injections.has( id ) && ! options?.overwrite ) {\n\t\t\t// eslint-disable-next-line no-console\n\t\t\tconsole.warn(\n\t\t\t\t`An injection with the id \"${ id }\" already exists. Did you mean to use \"options.overwrite\"?`\n\t\t\t);\n\n\t\t\treturn;\n\t\t}\n\n\t\tinjections.set( id, {\n\t\t\tid,\n\t\t\tcomponent: wrapInjectedComponent( component ),\n\t\t\tpriority: options.priority ?? DEFAULT_PRIORITY,\n\t\t} );\n\t};\n}\n","import * as React from 'react';\nimport { useMemo } from 'react';\n\nimport InjectedComponentWrapper from './components/injected-component-wrapper';\nimport { type AnyProps, type Id, type InjectedComponent, type Injection, type Location } from './types';\n\ntype InjectionsMap< TProps extends object = AnyProps > = Map< Id, Injection< TProps > >;\n\nexport const DEFAULT_PRIORITY = 10;\n\n// Allow flushing all injections at once, for testing purposes.\nexport const flushInjectionsFns: ( () => void )[] = [];\n\nexport function flushAllInjections() {\n\tflushInjectionsFns.forEach( ( flush ) => flush() );\n}\n\nexport function createGetInjections< TProps extends object = AnyProps >( injections: InjectionsMap< TProps > ) {\n\treturn () => [ ...injections.values() ].sort( ( a, b ) => a.priority - b.priority );\n}\n\nexport function createUseInjections< TProps extends object = AnyProps >(\n\tgetInjections: Location< TProps >[ 'getInjections' ]\n) {\n\treturn () => useMemo( () => getInjections(), [] );\n}\n\nexport function wrapInjectedComponent< TProps extends object = AnyProps >( Component: InjectedComponent< TProps > ) {\n\treturn ( props: TProps ) => (\n\t\t<InjectedComponentWrapper>\n\t\t\t<Component { ...props } />\n\t\t</InjectedComponentWrapper>\n\t);\n}\n","import * as React from 'react';\nimport { type ReactNode, Suspense } from 'react';\n\nimport ErrorBoundary from './error-boundary';\n\nexport default function InjectedComponentWrapper( { children }: { children: ReactNode } ) {\n\treturn (\n\t\t<ErrorBoundary fallback={ null }>\n\t\t\t<Suspense fallback={ null }>{ children }</Suspense>\n\t\t</ErrorBoundary>\n\t);\n}\n","import { Component, type ReactNode } from 'react';\n\ninterface Props {\n\tchildren?: ReactNode;\n\tfallback: ReactNode;\n}\n\ninterface State {\n\thasError: boolean;\n}\n\nexport default class ErrorBoundary extends Component< Props, State > {\n\tpublic state: State = {\n\t\thasError: false,\n\t};\n\n\tpublic static getDerivedStateFromError(): State {\n\t\t// Update state so the next render will show the fallback UI.\n\t\treturn { hasError: true };\n\t}\n\n\tpublic render() {\n\t\tif ( this.state.hasError ) {\n\t\t\treturn this.props.fallback;\n\t\t}\n\n\t\treturn this.props.children;\n\t}\n}\n","import * as React from 'react';\nimport { type PropsWithChildren } from 'react';\n\nimport {\n\tcreateGetInjections,\n\tcreateUseInjections,\n\tDEFAULT_PRIORITY,\n\tflushInjectionsFns,\n\twrapInjectedComponent,\n} from './injections';\nimport {\n\ttype AnyProps,\n\ttype Id,\n\ttype ReplaceableInjectArgs,\n\ttype ReplaceableInjection,\n\ttype ReplaceableLocation,\n} from './types';\n\ntype ReplaceableInjectionsMap< TProps extends object = AnyProps > = Map< Id, ReplaceableInjection< TProps > >;\n\nexport function createReplaceableLocation< TProps extends object = AnyProps >(): ReplaceableLocation< TProps > {\n\tconst injections: ReplaceableInjectionsMap< TProps > = new Map();\n\n\tconst getInjections = createGetInjections( injections );\n\tconst useInjections = createUseInjections( getInjections );\n\tconst Slot = createReplaceable( useInjections );\n\tconst inject = createRegister( injections );\n\n\t// Push the clear function to the flushInjectionsFns array, so we can flush all injections at once.\n\tflushInjectionsFns.push( () => injections.clear() );\n\n\treturn {\n\t\tgetInjections,\n\t\tuseInjections,\n\t\tinject,\n\t\tSlot,\n\t};\n}\n\nfunction createReplaceable< TProps extends PropsWithChildren< object > = AnyProps >(\n\tuseInjections: ReplaceableLocation< TProps >[ 'useInjections' ]\n) {\n\treturn ( props: TProps ) => {\n\t\tconst injections = useInjections();\n\n\t\tconst { component: Component } = injections.find( ( { condition } ) => condition?.( props ) ) ?? {};\n\n\t\tif ( ! Component ) {\n\t\t\treturn props.children;\n\t\t}\n\n\t\treturn <Component { ...props } />;\n\t};\n}\n\nfunction createRegister< TProps extends object = AnyProps >( injections: ReplaceableInjectionsMap< TProps > ) {\n\treturn ( { component, id, condition = () => true, options = {} }: ReplaceableInjectArgs< TProps > ) => {\n\t\tinjections.set( id, {\n\t\t\tid,\n\t\t\tcomponent: wrapInjectedComponent( component ),\n\t\t\tcondition,\n\t\t\tpriority: options.priority ?? DEFAULT_PRIORITY,\n\t\t} );\n\t};\n}\n"],"mappings":"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","names":["React","React","Component","Component","React","Component"]}
|
|
1
|
+
{"version":3,"sources":["../src/create-location.tsx","../src/injections.tsx","../src/components/injected-component-wrapper.tsx","../src/components/error-boundary.tsx","../src/create-replaceable-location.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport {\n\tcreateGetInjections,\n\tcreateSubscription,\n\tcreateUseInjections,\n\tDEFAULT_PRIORITY,\n\tflushInjectionsFns,\n\twrapInjectedComponent,\n} from './injections';\nimport { type AnyProps, type Id, type InjectArgs, type Injection, type Location } from './types';\n\ntype InjectionsMap< TProps extends object = AnyProps > = Map< Id, Injection< TProps > >;\n\nexport function createLocation< TProps extends object = AnyProps >(): Location< TProps > {\n\tconst injections: InjectionsMap< TProps > = new Map();\n\tconst { subscribe, notify } = createSubscription();\n\n\tconst getInjections = createGetInjections( injections );\n\tconst useInjections = createUseInjections( getInjections, subscribe );\n\tconst Slot = createSlot( useInjections );\n\tconst inject = createInject( injections, notify );\n\n\t// Push the clear function to the flushInjectionsFns array, so we can flush all injections at once.\n\t// `notify()` is called too, so any mounted `Slot` (and its cached snapshot) reflects the flush,\n\t// which matters for test isolation between test cases.\n\tflushInjectionsFns.push( () => {\n\t\tinjections.clear();\n\t\tnotify();\n\t} );\n\n\treturn {\n\t\tinject,\n\t\tgetInjections,\n\t\tuseInjections,\n\t\tSlot,\n\t};\n}\n\nfunction createSlot< TProps extends object = AnyProps >( useInjections: Location< TProps >[ 'useInjections' ] ) {\n\treturn ( props: TProps ) => {\n\t\tconst injections = useInjections();\n\n\t\treturn (\n\t\t\t<>\n\t\t\t\t{ injections.map( ( { id, component: Component } ) => (\n\t\t\t\t\t<Component { ...props } key={ id } />\n\t\t\t\t) ) }\n\t\t\t</>\n\t\t);\n\t};\n}\n\nfunction createInject< TProps extends object = AnyProps >( injections: InjectionsMap< TProps >, notify: () => void ) {\n\treturn ( { component, id, options = {} }: InjectArgs< TProps > ) => {\n\t\tif ( injections.has( id ) && ! options?.overwrite ) {\n\t\t\t// eslint-disable-next-line no-console\n\t\t\tconsole.warn(\n\t\t\t\t`An injection with the id \"${ id }\" already exists. Did you mean to use \"options.overwrite\"?`\n\t\t\t);\n\n\t\t\treturn;\n\t\t}\n\n\t\tinjections.set( id, {\n\t\t\tid,\n\t\t\tcomponent: wrapInjectedComponent( component ),\n\t\t\tpriority: options.priority ?? DEFAULT_PRIORITY,\n\t\t} );\n\n\t\tnotify();\n\t};\n}\n","import * as React from 'react';\nimport { useSyncExternalStore } from 'react';\n\nimport InjectedComponentWrapper from './components/injected-component-wrapper';\nimport { type AnyProps, type Id, type InjectedComponent, type Injection, type Location } from './types';\n\ntype InjectionsMap< TProps extends object = AnyProps > = Map< Id, Injection< TProps > >;\n\nexport const DEFAULT_PRIORITY = 10;\n\n// Allow flushing all injections at once, for testing purposes.\nexport const flushInjectionsFns: ( () => void )[] = [];\n\nexport function flushAllInjections() {\n\tflushInjectionsFns.forEach( ( flush ) => flush() );\n}\n\nexport type Subscribe = ( listener: () => void ) => () => void;\n\nexport function createSubscription() {\n\tconst listeners = new Set< () => void >();\n\n\treturn {\n\t\tsubscribe: ( listener: () => void ) => {\n\t\t\tlisteners.add( listener );\n\n\t\t\treturn () => listeners.delete( listener );\n\t\t},\n\t\tnotify: () => listeners.forEach( ( listener ) => listener() ),\n\t};\n}\n\nexport function createGetInjections< TProps extends object = AnyProps >( injections: InjectionsMap< TProps > ) {\n\treturn () => [ ...injections.values() ].sort( ( a, b ) => a.priority - b.priority );\n}\n\n// Injections registered after the `Slot` has already mounted (e.g. by an external plugin's\n// script that loads after the editor has rendered) must still be reflected in the UI, so the\n// snapshot is invalidated and re-read whenever `notify()` is called.\nexport function createUseInjections< TProps extends object = AnyProps >(\n\tgetInjections: Location< TProps >[ 'getInjections' ],\n\tsubscribe: Subscribe\n) {\n\tlet snapshot: ReturnType< Location< TProps >[ 'getInjections' ] > | null = null;\n\n\tsubscribe( () => {\n\t\tsnapshot = null;\n\t} );\n\n\tconst getSnapshot = () => {\n\t\tif ( ! snapshot ) {\n\t\t\tsnapshot = getInjections();\n\t\t}\n\n\t\treturn snapshot;\n\t};\n\n\treturn () => useSyncExternalStore( subscribe, getSnapshot );\n}\n\nexport function wrapInjectedComponent< TProps extends object = AnyProps >( Component: InjectedComponent< TProps > ) {\n\treturn ( props: TProps ) => (\n\t\t<InjectedComponentWrapper>\n\t\t\t<Component { ...props } />\n\t\t</InjectedComponentWrapper>\n\t);\n}\n","import * as React from 'react';\nimport { type ReactNode, Suspense } from 'react';\n\nimport ErrorBoundary from './error-boundary';\n\nexport default function InjectedComponentWrapper( { children }: { children: ReactNode } ) {\n\treturn (\n\t\t<ErrorBoundary fallback={ null }>\n\t\t\t<Suspense fallback={ null }>{ children }</Suspense>\n\t\t</ErrorBoundary>\n\t);\n}\n","import { Component, type ReactNode } from 'react';\n\ninterface Props {\n\tchildren?: ReactNode;\n\tfallback: ReactNode;\n}\n\ninterface State {\n\thasError: boolean;\n}\n\nexport default class ErrorBoundary extends Component< Props, State > {\n\tpublic state: State = {\n\t\thasError: false,\n\t};\n\n\tpublic static getDerivedStateFromError(): State {\n\t\t// Update state so the next render will show the fallback UI.\n\t\treturn { hasError: true };\n\t}\n\n\tpublic render() {\n\t\tif ( this.state.hasError ) {\n\t\t\treturn this.props.fallback;\n\t\t}\n\n\t\treturn this.props.children;\n\t}\n}\n","import * as React from 'react';\nimport { type PropsWithChildren } from 'react';\n\nimport {\n\tcreateGetInjections,\n\tcreateSubscription,\n\tcreateUseInjections,\n\tDEFAULT_PRIORITY,\n\tflushInjectionsFns,\n\twrapInjectedComponent,\n} from './injections';\nimport {\n\ttype AnyProps,\n\ttype Id,\n\ttype ReplaceableInjectArgs,\n\ttype ReplaceableInjection,\n\ttype ReplaceableLocation,\n} from './types';\n\ntype ReplaceableInjectionsMap< TProps extends object = AnyProps > = Map< Id, ReplaceableInjection< TProps > >;\n\nexport function createReplaceableLocation< TProps extends object = AnyProps >(): ReplaceableLocation< TProps > {\n\tconst injections: ReplaceableInjectionsMap< TProps > = new Map();\n\tconst { subscribe, notify } = createSubscription();\n\n\tconst getInjections = createGetInjections( injections );\n\tconst useInjections = createUseInjections( getInjections, subscribe );\n\tconst Slot = createReplaceable( useInjections );\n\tconst inject = createRegister( injections, notify );\n\n\t// Push the clear function to the flushInjectionsFns array, so we can flush all injections at once.\n\t// `notify()` is called too, so any mounted `Slot` (and its cached snapshot) reflects the flush,\n\t// which matters for test isolation between test cases.\n\tflushInjectionsFns.push( () => {\n\t\tinjections.clear();\n\t\tnotify();\n\t} );\n\n\treturn {\n\t\tgetInjections,\n\t\tuseInjections,\n\t\tinject,\n\t\tSlot,\n\t};\n}\n\nfunction createReplaceable< TProps extends PropsWithChildren< object > = AnyProps >(\n\tuseInjections: ReplaceableLocation< TProps >[ 'useInjections' ]\n) {\n\treturn ( props: TProps ) => {\n\t\tconst injections = useInjections();\n\n\t\tconst { component: Component } = injections.find( ( { condition } ) => condition?.( props ) ) ?? {};\n\n\t\tif ( ! Component ) {\n\t\t\treturn props.children;\n\t\t}\n\n\t\treturn <Component { ...props } />;\n\t};\n}\n\nfunction createRegister< TProps extends object = AnyProps >(\n\tinjections: ReplaceableInjectionsMap< TProps >,\n\tnotify: () => void\n) {\n\treturn ( { component, id, condition = () => true, options = {} }: ReplaceableInjectArgs< TProps > ) => {\n\t\tinjections.set( id, {\n\t\t\tid,\n\t\t\tcomponent: wrapInjectedComponent( component ),\n\t\t\tcondition,\n\t\t\tpriority: options.priority ?? DEFAULT_PRIORITY,\n\t\t} );\n\n\t\tnotify();\n\t};\n}\n"],"mappings":"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","names":["React","React","Component","Component","React","Component"]}
|
package/package.json
CHANGED
package/src/create-location.tsx
CHANGED
|
@@ -2,6 +2,7 @@ import * as React from 'react';
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
createGetInjections,
|
|
5
|
+
createSubscription,
|
|
5
6
|
createUseInjections,
|
|
6
7
|
DEFAULT_PRIORITY,
|
|
7
8
|
flushInjectionsFns,
|
|
@@ -13,14 +14,20 @@ type InjectionsMap< TProps extends object = AnyProps > = Map< Id, Injection< TPr
|
|
|
13
14
|
|
|
14
15
|
export function createLocation< TProps extends object = AnyProps >(): Location< TProps > {
|
|
15
16
|
const injections: InjectionsMap< TProps > = new Map();
|
|
17
|
+
const { subscribe, notify } = createSubscription();
|
|
16
18
|
|
|
17
19
|
const getInjections = createGetInjections( injections );
|
|
18
|
-
const useInjections = createUseInjections( getInjections );
|
|
20
|
+
const useInjections = createUseInjections( getInjections, subscribe );
|
|
19
21
|
const Slot = createSlot( useInjections );
|
|
20
|
-
const inject = createInject( injections );
|
|
22
|
+
const inject = createInject( injections, notify );
|
|
21
23
|
|
|
22
24
|
// Push the clear function to the flushInjectionsFns array, so we can flush all injections at once.
|
|
23
|
-
|
|
25
|
+
// `notify()` is called too, so any mounted `Slot` (and its cached snapshot) reflects the flush,
|
|
26
|
+
// which matters for test isolation between test cases.
|
|
27
|
+
flushInjectionsFns.push( () => {
|
|
28
|
+
injections.clear();
|
|
29
|
+
notify();
|
|
30
|
+
} );
|
|
24
31
|
|
|
25
32
|
return {
|
|
26
33
|
inject,
|
|
@@ -44,7 +51,7 @@ function createSlot< TProps extends object = AnyProps >( useInjections: Location
|
|
|
44
51
|
};
|
|
45
52
|
}
|
|
46
53
|
|
|
47
|
-
function createInject< TProps extends object = AnyProps >( injections: InjectionsMap< TProps
|
|
54
|
+
function createInject< TProps extends object = AnyProps >( injections: InjectionsMap< TProps >, notify: () => void ) {
|
|
48
55
|
return ( { component, id, options = {} }: InjectArgs< TProps > ) => {
|
|
49
56
|
if ( injections.has( id ) && ! options?.overwrite ) {
|
|
50
57
|
// eslint-disable-next-line no-console
|
|
@@ -60,5 +67,7 @@ function createInject< TProps extends object = AnyProps >( injections: Injection
|
|
|
60
67
|
component: wrapInjectedComponent( component ),
|
|
61
68
|
priority: options.priority ?? DEFAULT_PRIORITY,
|
|
62
69
|
} );
|
|
70
|
+
|
|
71
|
+
notify();
|
|
63
72
|
};
|
|
64
73
|
}
|
|
@@ -3,6 +3,7 @@ import { type PropsWithChildren } from 'react';
|
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
createGetInjections,
|
|
6
|
+
createSubscription,
|
|
6
7
|
createUseInjections,
|
|
7
8
|
DEFAULT_PRIORITY,
|
|
8
9
|
flushInjectionsFns,
|
|
@@ -20,14 +21,20 @@ type ReplaceableInjectionsMap< TProps extends object = AnyProps > = Map< Id, Rep
|
|
|
20
21
|
|
|
21
22
|
export function createReplaceableLocation< TProps extends object = AnyProps >(): ReplaceableLocation< TProps > {
|
|
22
23
|
const injections: ReplaceableInjectionsMap< TProps > = new Map();
|
|
24
|
+
const { subscribe, notify } = createSubscription();
|
|
23
25
|
|
|
24
26
|
const getInjections = createGetInjections( injections );
|
|
25
|
-
const useInjections = createUseInjections( getInjections );
|
|
27
|
+
const useInjections = createUseInjections( getInjections, subscribe );
|
|
26
28
|
const Slot = createReplaceable( useInjections );
|
|
27
|
-
const inject = createRegister( injections );
|
|
29
|
+
const inject = createRegister( injections, notify );
|
|
28
30
|
|
|
29
31
|
// Push the clear function to the flushInjectionsFns array, so we can flush all injections at once.
|
|
30
|
-
|
|
32
|
+
// `notify()` is called too, so any mounted `Slot` (and its cached snapshot) reflects the flush,
|
|
33
|
+
// which matters for test isolation between test cases.
|
|
34
|
+
flushInjectionsFns.push( () => {
|
|
35
|
+
injections.clear();
|
|
36
|
+
notify();
|
|
37
|
+
} );
|
|
31
38
|
|
|
32
39
|
return {
|
|
33
40
|
getInjections,
|
|
@@ -53,7 +60,10 @@ function createReplaceable< TProps extends PropsWithChildren< object > = AnyProp
|
|
|
53
60
|
};
|
|
54
61
|
}
|
|
55
62
|
|
|
56
|
-
function createRegister< TProps extends object = AnyProps >(
|
|
63
|
+
function createRegister< TProps extends object = AnyProps >(
|
|
64
|
+
injections: ReplaceableInjectionsMap< TProps >,
|
|
65
|
+
notify: () => void
|
|
66
|
+
) {
|
|
57
67
|
return ( { component, id, condition = () => true, options = {} }: ReplaceableInjectArgs< TProps > ) => {
|
|
58
68
|
injections.set( id, {
|
|
59
69
|
id,
|
|
@@ -61,5 +71,7 @@ function createRegister< TProps extends object = AnyProps >( injections: Replace
|
|
|
61
71
|
condition,
|
|
62
72
|
priority: options.priority ?? DEFAULT_PRIORITY,
|
|
63
73
|
} );
|
|
74
|
+
|
|
75
|
+
notify();
|
|
64
76
|
};
|
|
65
77
|
}
|
package/src/injections.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { useSyncExternalStore } from 'react';
|
|
3
3
|
|
|
4
4
|
import InjectedComponentWrapper from './components/injected-component-wrapper';
|
|
5
5
|
import { type AnyProps, type Id, type InjectedComponent, type Injection, type Location } from './types';
|
|
@@ -15,14 +15,47 @@ export function flushAllInjections() {
|
|
|
15
15
|
flushInjectionsFns.forEach( ( flush ) => flush() );
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
export type Subscribe = ( listener: () => void ) => () => void;
|
|
19
|
+
|
|
20
|
+
export function createSubscription() {
|
|
21
|
+
const listeners = new Set< () => void >();
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
subscribe: ( listener: () => void ) => {
|
|
25
|
+
listeners.add( listener );
|
|
26
|
+
|
|
27
|
+
return () => listeners.delete( listener );
|
|
28
|
+
},
|
|
29
|
+
notify: () => listeners.forEach( ( listener ) => listener() ),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
18
33
|
export function createGetInjections< TProps extends object = AnyProps >( injections: InjectionsMap< TProps > ) {
|
|
19
34
|
return () => [ ...injections.values() ].sort( ( a, b ) => a.priority - b.priority );
|
|
20
35
|
}
|
|
21
36
|
|
|
37
|
+
// Injections registered after the `Slot` has already mounted (e.g. by an external plugin's
|
|
38
|
+
// script that loads after the editor has rendered) must still be reflected in the UI, so the
|
|
39
|
+
// snapshot is invalidated and re-read whenever `notify()` is called.
|
|
22
40
|
export function createUseInjections< TProps extends object = AnyProps >(
|
|
23
|
-
getInjections: Location< TProps >[ 'getInjections' ]
|
|
41
|
+
getInjections: Location< TProps >[ 'getInjections' ],
|
|
42
|
+
subscribe: Subscribe
|
|
24
43
|
) {
|
|
25
|
-
|
|
44
|
+
let snapshot: ReturnType< Location< TProps >[ 'getInjections' ] > | null = null;
|
|
45
|
+
|
|
46
|
+
subscribe( () => {
|
|
47
|
+
snapshot = null;
|
|
48
|
+
} );
|
|
49
|
+
|
|
50
|
+
const getSnapshot = () => {
|
|
51
|
+
if ( ! snapshot ) {
|
|
52
|
+
snapshot = getInjections();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return snapshot;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
return () => useSyncExternalStore( subscribe, getSnapshot );
|
|
26
59
|
}
|
|
27
60
|
|
|
28
61
|
export function wrapInjectedComponent< TProps extends object = AnyProps >( Component: InjectedComponent< TProps > ) {
|