@elementor/locations 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +674 -0
- package/README.md +5 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +137 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +95 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +38 -0
- package/src/__tests__/index.test.tsx +217 -0
- package/src/components/error-boundary.tsx +29 -0
- package/src/components/filler-wrapper.tsx +13 -0
- package/src/components/slot.tsx +19 -0
- package/src/hooks/use-injections-of.ts +18 -0
- package/src/index.ts +4 -0
- package/src/injections.tsx +64 -0
- package/src/types.ts +19 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ComponentType } from 'react';
|
|
2
|
+
|
|
3
|
+
type Location = string;
|
|
4
|
+
type Filler = ComponentType;
|
|
5
|
+
type Name = string;
|
|
6
|
+
type Id = string;
|
|
7
|
+
type Priority = number;
|
|
8
|
+
type InjectionOptions = {
|
|
9
|
+
priority?: Priority;
|
|
10
|
+
overwrite?: boolean;
|
|
11
|
+
};
|
|
12
|
+
type Injection = {
|
|
13
|
+
location: Location;
|
|
14
|
+
filler: Filler;
|
|
15
|
+
priority: Priority;
|
|
16
|
+
id: Id;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type InjectArgs = {
|
|
20
|
+
location: Location;
|
|
21
|
+
filler: Filler;
|
|
22
|
+
name: Name;
|
|
23
|
+
options?: InjectionOptions;
|
|
24
|
+
};
|
|
25
|
+
declare function inject({ location, filler, name, options }: InjectArgs): void;
|
|
26
|
+
declare function createInjectorFor(location: Location): ({ filler, name, options }: Omit<InjectArgs, 'location'>) => void;
|
|
27
|
+
declare function getInjectionsOf(location: string): Injection[];
|
|
28
|
+
declare function flushInjections(): void;
|
|
29
|
+
|
|
30
|
+
declare function useInjectionsOf(locations: string[]): Injection[][];
|
|
31
|
+
declare function useInjectionsOf(location: string): Injection[];
|
|
32
|
+
|
|
33
|
+
type Props = {
|
|
34
|
+
location: Location;
|
|
35
|
+
};
|
|
36
|
+
declare function Slot({ location }: Props): JSX.Element;
|
|
37
|
+
|
|
38
|
+
export { Filler, Id, Injection, InjectionOptions, Location, Name, Priority, Slot, createInjectorFor, flushInjections, getInjectionsOf, inject, useInjectionsOf };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
Slot: () => Slot,
|
|
34
|
+
createInjectorFor: () => createInjectorFor,
|
|
35
|
+
flushInjections: () => flushInjections,
|
|
36
|
+
getInjectionsOf: () => getInjectionsOf,
|
|
37
|
+
inject: () => inject,
|
|
38
|
+
useInjectionsOf: () => useInjectionsOf
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(src_exports);
|
|
41
|
+
|
|
42
|
+
// src/injections.tsx
|
|
43
|
+
var React2 = __toESM(require("react"));
|
|
44
|
+
|
|
45
|
+
// src/components/filler-wrapper.tsx
|
|
46
|
+
var React = __toESM(require("react"));
|
|
47
|
+
var import_react2 = require("react");
|
|
48
|
+
|
|
49
|
+
// src/components/error-boundary.tsx
|
|
50
|
+
var import_react = require("react");
|
|
51
|
+
var ErrorBoundary = class extends import_react.Component {
|
|
52
|
+
state = {
|
|
53
|
+
hasError: false
|
|
54
|
+
};
|
|
55
|
+
static getDerivedStateFromError() {
|
|
56
|
+
return { hasError: true };
|
|
57
|
+
}
|
|
58
|
+
render() {
|
|
59
|
+
if (this.state.hasError) {
|
|
60
|
+
return this.props.fallback;
|
|
61
|
+
}
|
|
62
|
+
return this.props.children;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// src/components/filler-wrapper.tsx
|
|
67
|
+
function FillerWrapper({ children }) {
|
|
68
|
+
return /* @__PURE__ */ React.createElement(ErrorBoundary, { fallback: null }, /* @__PURE__ */ React.createElement(import_react2.Suspense, { fallback: null }, children));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/injections.tsx
|
|
72
|
+
var DEFAULT_PRIORITY = 10;
|
|
73
|
+
var injections = /* @__PURE__ */ new Map();
|
|
74
|
+
function inject({ location, filler, name, options = {} }) {
|
|
75
|
+
const id = generateId(location, name);
|
|
76
|
+
if (injections.has(id) && !options?.overwrite) {
|
|
77
|
+
console.error(
|
|
78
|
+
`An injection named "${name}" under location "${location}" already exists. Did you mean to use "options.overwrite"?`
|
|
79
|
+
);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const injection = {
|
|
83
|
+
id,
|
|
84
|
+
location,
|
|
85
|
+
filler: wrapFiller(filler),
|
|
86
|
+
priority: options.priority ?? DEFAULT_PRIORITY
|
|
87
|
+
};
|
|
88
|
+
injections.set(id, injection);
|
|
89
|
+
}
|
|
90
|
+
function createInjectorFor(location) {
|
|
91
|
+
return ({ filler, name, options }) => {
|
|
92
|
+
return inject({ location, name, filler, options });
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function getInjectionsOf(location) {
|
|
96
|
+
return [...injections.values()].filter((injection) => injection.location === location).sort((a, b) => a.priority - b.priority);
|
|
97
|
+
}
|
|
98
|
+
function flushInjections() {
|
|
99
|
+
injections.clear();
|
|
100
|
+
}
|
|
101
|
+
function wrapFiller(FillerComponent) {
|
|
102
|
+
return (props) => /* @__PURE__ */ React2.createElement(FillerWrapper, null, /* @__PURE__ */ React2.createElement(FillerComponent, { ...props }));
|
|
103
|
+
}
|
|
104
|
+
function generateId(location, name) {
|
|
105
|
+
return `${location}::${name}`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/hooks/use-injections-of.ts
|
|
109
|
+
var import_react3 = require("react");
|
|
110
|
+
function useInjectionsOf(locations) {
|
|
111
|
+
return (0, import_react3.useMemo)(
|
|
112
|
+
() => {
|
|
113
|
+
if (Array.isArray(locations)) {
|
|
114
|
+
return locations.map((location) => getInjectionsOf(location));
|
|
115
|
+
}
|
|
116
|
+
return getInjectionsOf(locations);
|
|
117
|
+
},
|
|
118
|
+
[locations]
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/components/slot.tsx
|
|
123
|
+
var React3 = __toESM(require("react"));
|
|
124
|
+
function Slot({ location }) {
|
|
125
|
+
const injections2 = useInjectionsOf(location);
|
|
126
|
+
return /* @__PURE__ */ React3.createElement(React3.Fragment, null, injections2.map(({ id, filler: Filler }) => /* @__PURE__ */ React3.createElement(Filler, { key: id })));
|
|
127
|
+
}
|
|
128
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
129
|
+
0 && (module.exports = {
|
|
130
|
+
Slot,
|
|
131
|
+
createInjectorFor,
|
|
132
|
+
flushInjections,
|
|
133
|
+
getInjectionsOf,
|
|
134
|
+
inject,
|
|
135
|
+
useInjectionsOf
|
|
136
|
+
});
|
|
137
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/injections.tsx","../src/components/filler-wrapper.tsx","../src/components/error-boundary.tsx","../src/hooks/use-injections-of.ts","../src/components/slot.tsx"],"sourcesContent":["export * from './types';\nexport { inject, getInjectionsOf, flushInjections, createInjectorFor } from './injections';\nexport { default as useInjectionsOf } from './hooks/use-injections-of';\nexport { default as Slot } from './components/slot';\n","import * as React from 'react';\nimport { Location, Filler, Injection, InjectionOptions, Name, Id } from './types';\nimport FillerWrapper from './components/filler-wrapper';\n\nconst DEFAULT_PRIORITY = 10;\n\nconst injections: Map<Id, Injection> = new Map();\n\ntype InjectArgs = {\n\tlocation: Location;\n\tfiller: Filler;\n\tname: Name;\n\toptions?: InjectionOptions;\n}\n\nexport function inject( { location, filler, name, options = {} }: InjectArgs ) {\n\tconst id = generateId( location, name );\n\n\tif ( injections.has( id ) && ! options?.overwrite ) {\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.error(\n\t\t\t`An injection named \"${ name }\" under location \"${ location }\" already exists. Did you mean to use \"options.overwrite\"?`\n\t\t);\n\n\t\treturn;\n\t}\n\n\tconst injection = {\n\t\tid,\n\t\tlocation,\n\t\tfiller: wrapFiller( filler ),\n\t\tpriority: options.priority ?? DEFAULT_PRIORITY,\n\t};\n\n\tinjections.set( id, injection );\n}\n\nexport function createInjectorFor( location: Location ) {\n\treturn ( { filler, name, options }: Omit<InjectArgs, 'location'> ) => {\n\t\treturn inject( { location, name, filler, options } );\n\t};\n}\n\nexport function getInjectionsOf( location: string ) {\n\treturn [ ...injections.values() ]\n\t\t.filter( ( injection ) => injection.location === location )\n\t\t.sort( ( a, b ) => a.priority - b.priority );\n}\n\nexport function flushInjections() {\n\tinjections.clear();\n}\n\nfunction wrapFiller( FillerComponent: Filler ) {\n\treturn ( props: object ) => (\n\t\t<FillerWrapper>\n\t\t\t<FillerComponent { ...props } />\n\t\t</FillerWrapper>\n\t);\n}\n\nfunction generateId( location: Location, name: Name ) {\n\treturn `${ location }::${ name }`;\n}\n","import * as React from 'react';\nimport { ReactNode, Suspense } from 'react';\nimport ErrorBoundary from './error-boundary';\n\nexport default function FillerWrapper( { children }: { children: ReactNode } ) {\n\treturn (\n\t\t<ErrorBoundary fallback={ null }>\n\t\t\t<Suspense fallback={ null }>\n\t\t\t\t{ children }\n\t\t\t</Suspense>\n\t\t</ErrorBoundary>\n\t);\n}\n","import { Component, 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 { useMemo } from 'react';\nimport { getInjectionsOf } from '../injections';\nimport { Injection } from '../types';\n\nexport default function useInjectionsOf( locations: string[] ): Injection[][];\nexport default function useInjectionsOf( location: string ): Injection[];\nexport default function useInjectionsOf( locations: string | string[] ) {\n\treturn useMemo(\n\t\t() => {\n\t\t\tif ( Array.isArray( locations ) ) {\n\t\t\t\treturn locations.map( ( location ) => getInjectionsOf( location ) );\n\t\t\t}\n\n\t\t\treturn getInjectionsOf( locations );\n\t\t},\n\t\t[ locations ]\n\t);\n}\n","import * as React from 'react';\nimport useInjectionsOf from '../hooks/use-injections-of';\nimport { Location } from '../types';\n\ntype Props = {\n\tlocation: Location\n}\n\nexport default function Slot( { location }: Props ) {\n\tconst injections = useInjectionsOf( location );\n\n\treturn (\n\t\t<>\n\t\t\t{ injections.map( ( { id, filler: Filler } ) => (\n\t\t\t\t<Filler key={ id } />\n\t\t\t) ) }\n\t\t</>\n\t);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,IAAAA,SAAuB;;;ACAvB,YAAuB;AACvB,IAAAC,gBAAoC;;;ACDpC,mBAAqC;AAWrC,IAAqB,gBAArB,cAA2C,uBAAwB;AAAA,EAC3D,QAAe;AAAA,IACrB,UAAU;AAAA,EACX;AAAA,EAEA,OAAc,2BAAkC;AAE/C,WAAO,EAAE,UAAU,KAAK;AAAA,EACzB;AAAA,EAEO,SAAS;AACf,QAAK,KAAK,MAAM,UAAW;AAC1B,aAAO,KAAK,MAAM;AAAA,IACnB;AAEA,WAAO,KAAK,MAAM;AAAA,EACnB;AACD;;;ADxBe,SAAR,cAAgC,EAAE,SAAS,GAA6B;AAC9E,SACC,oCAAC,iBAAc,UAAW,QACzB,oCAAC,0BAAS,UAAW,QAClB,QACH,CACD;AAEF;;;ADRA,IAAM,mBAAmB;AAEzB,IAAM,aAAiC,oBAAI,IAAI;AASxC,SAAS,OAAQ,EAAE,UAAU,QAAQ,MAAM,UAAU,CAAC,EAAE,GAAgB;AAC9E,QAAM,KAAK,WAAY,UAAU,IAAK;AAEtC,MAAK,WAAW,IAAK,EAAG,KAAK,CAAE,SAAS,WAAY;AAEnD,YAAQ;AAAA,MACP,uBAAwB,yBAA2B;AAAA,IACpD;AAEA;AAAA,EACD;AAEA,QAAM,YAAY;AAAA,IACjB;AAAA,IACA;AAAA,IACA,QAAQ,WAAY,MAAO;AAAA,IAC3B,UAAU,QAAQ,YAAY;AAAA,EAC/B;AAEA,aAAW,IAAK,IAAI,SAAU;AAC/B;AAEO,SAAS,kBAAmB,UAAqB;AACvD,SAAO,CAAE,EAAE,QAAQ,MAAM,QAAQ,MAAqC;AACrE,WAAO,OAAQ,EAAE,UAAU,MAAM,QAAQ,QAAQ,CAAE;AAAA,EACpD;AACD;AAEO,SAAS,gBAAiB,UAAmB;AACnD,SAAO,CAAE,GAAG,WAAW,OAAO,CAAE,EAC9B,OAAQ,CAAE,cAAe,UAAU,aAAa,QAAS,EACzD,KAAM,CAAE,GAAG,MAAO,EAAE,WAAW,EAAE,QAAS;AAC7C;AAEO,SAAS,kBAAkB;AACjC,aAAW,MAAM;AAClB;AAEA,SAAS,WAAY,iBAA0B;AAC9C,SAAO,CAAE,UACR,qCAAC,qBACA,qCAAC,mBAAkB,GAAG,OAAQ,CAC/B;AAEF;AAEA,SAAS,WAAY,UAAoB,MAAa;AACrD,SAAO,GAAI,aAAe;AAC3B;;;AG/DA,IAAAC,gBAAwB;AAMT,SAAR,gBAAkC,WAA+B;AACvE,aAAO;AAAA,IACN,MAAM;AACL,UAAK,MAAM,QAAS,SAAU,GAAI;AACjC,eAAO,UAAU,IAAK,CAAE,aAAc,gBAAiB,QAAS,CAAE;AAAA,MACnE;AAEA,aAAO,gBAAiB,SAAU;AAAA,IACnC;AAAA,IACA,CAAE,SAAU;AAAA,EACb;AACD;;;ACjBA,IAAAC,SAAuB;AAQR,SAAR,KAAuB,EAAE,SAAS,GAAW;AACnD,QAAMC,cAAa,gBAAiB,QAAS;AAE7C,SACC,4DACGA,YAAW,IAAK,CAAE,EAAE,IAAI,QAAQ,OAAO,MACxC,qCAAC,UAAO,KAAM,IAAK,CAClB,CACH;AAEF;","names":["React","import_react","import_react","React","injections"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// src/injections.tsx
|
|
2
|
+
import * as React2 from "react";
|
|
3
|
+
|
|
4
|
+
// src/components/filler-wrapper.tsx
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import { Suspense } from "react";
|
|
7
|
+
|
|
8
|
+
// src/components/error-boundary.tsx
|
|
9
|
+
import { Component } from "react";
|
|
10
|
+
var ErrorBoundary = class extends Component {
|
|
11
|
+
state = {
|
|
12
|
+
hasError: false
|
|
13
|
+
};
|
|
14
|
+
static getDerivedStateFromError() {
|
|
15
|
+
return { hasError: true };
|
|
16
|
+
}
|
|
17
|
+
render() {
|
|
18
|
+
if (this.state.hasError) {
|
|
19
|
+
return this.props.fallback;
|
|
20
|
+
}
|
|
21
|
+
return this.props.children;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// src/components/filler-wrapper.tsx
|
|
26
|
+
function FillerWrapper({ children }) {
|
|
27
|
+
return /* @__PURE__ */ React.createElement(ErrorBoundary, { fallback: null }, /* @__PURE__ */ React.createElement(Suspense, { fallback: null }, children));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/injections.tsx
|
|
31
|
+
var DEFAULT_PRIORITY = 10;
|
|
32
|
+
var injections = /* @__PURE__ */ new Map();
|
|
33
|
+
function inject({ location, filler, name, options = {} }) {
|
|
34
|
+
const id = generateId(location, name);
|
|
35
|
+
if (injections.has(id) && !options?.overwrite) {
|
|
36
|
+
console.error(
|
|
37
|
+
`An injection named "${name}" under location "${location}" already exists. Did you mean to use "options.overwrite"?`
|
|
38
|
+
);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const injection = {
|
|
42
|
+
id,
|
|
43
|
+
location,
|
|
44
|
+
filler: wrapFiller(filler),
|
|
45
|
+
priority: options.priority ?? DEFAULT_PRIORITY
|
|
46
|
+
};
|
|
47
|
+
injections.set(id, injection);
|
|
48
|
+
}
|
|
49
|
+
function createInjectorFor(location) {
|
|
50
|
+
return ({ filler, name, options }) => {
|
|
51
|
+
return inject({ location, name, filler, options });
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function getInjectionsOf(location) {
|
|
55
|
+
return [...injections.values()].filter((injection) => injection.location === location).sort((a, b) => a.priority - b.priority);
|
|
56
|
+
}
|
|
57
|
+
function flushInjections() {
|
|
58
|
+
injections.clear();
|
|
59
|
+
}
|
|
60
|
+
function wrapFiller(FillerComponent) {
|
|
61
|
+
return (props) => /* @__PURE__ */ React2.createElement(FillerWrapper, null, /* @__PURE__ */ React2.createElement(FillerComponent, { ...props }));
|
|
62
|
+
}
|
|
63
|
+
function generateId(location, name) {
|
|
64
|
+
return `${location}::${name}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/hooks/use-injections-of.ts
|
|
68
|
+
import { useMemo } from "react";
|
|
69
|
+
function useInjectionsOf(locations) {
|
|
70
|
+
return useMemo(
|
|
71
|
+
() => {
|
|
72
|
+
if (Array.isArray(locations)) {
|
|
73
|
+
return locations.map((location) => getInjectionsOf(location));
|
|
74
|
+
}
|
|
75
|
+
return getInjectionsOf(locations);
|
|
76
|
+
},
|
|
77
|
+
[locations]
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/components/slot.tsx
|
|
82
|
+
import * as React3 from "react";
|
|
83
|
+
function Slot({ location }) {
|
|
84
|
+
const injections2 = useInjectionsOf(location);
|
|
85
|
+
return /* @__PURE__ */ React3.createElement(React3.Fragment, null, injections2.map(({ id, filler: Filler }) => /* @__PURE__ */ React3.createElement(Filler, { key: id })));
|
|
86
|
+
}
|
|
87
|
+
export {
|
|
88
|
+
Slot,
|
|
89
|
+
createInjectorFor,
|
|
90
|
+
flushInjections,
|
|
91
|
+
getInjectionsOf,
|
|
92
|
+
inject,
|
|
93
|
+
useInjectionsOf
|
|
94
|
+
};
|
|
95
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/injections.tsx","../src/components/filler-wrapper.tsx","../src/components/error-boundary.tsx","../src/hooks/use-injections-of.ts","../src/components/slot.tsx"],"sourcesContent":["import * as React from 'react';\nimport { Location, Filler, Injection, InjectionOptions, Name, Id } from './types';\nimport FillerWrapper from './components/filler-wrapper';\n\nconst DEFAULT_PRIORITY = 10;\n\nconst injections: Map<Id, Injection> = new Map();\n\ntype InjectArgs = {\n\tlocation: Location;\n\tfiller: Filler;\n\tname: Name;\n\toptions?: InjectionOptions;\n}\n\nexport function inject( { location, filler, name, options = {} }: InjectArgs ) {\n\tconst id = generateId( location, name );\n\n\tif ( injections.has( id ) && ! options?.overwrite ) {\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.error(\n\t\t\t`An injection named \"${ name }\" under location \"${ location }\" already exists. Did you mean to use \"options.overwrite\"?`\n\t\t);\n\n\t\treturn;\n\t}\n\n\tconst injection = {\n\t\tid,\n\t\tlocation,\n\t\tfiller: wrapFiller( filler ),\n\t\tpriority: options.priority ?? DEFAULT_PRIORITY,\n\t};\n\n\tinjections.set( id, injection );\n}\n\nexport function createInjectorFor( location: Location ) {\n\treturn ( { filler, name, options }: Omit<InjectArgs, 'location'> ) => {\n\t\treturn inject( { location, name, filler, options } );\n\t};\n}\n\nexport function getInjectionsOf( location: string ) {\n\treturn [ ...injections.values() ]\n\t\t.filter( ( injection ) => injection.location === location )\n\t\t.sort( ( a, b ) => a.priority - b.priority );\n}\n\nexport function flushInjections() {\n\tinjections.clear();\n}\n\nfunction wrapFiller( FillerComponent: Filler ) {\n\treturn ( props: object ) => (\n\t\t<FillerWrapper>\n\t\t\t<FillerComponent { ...props } />\n\t\t</FillerWrapper>\n\t);\n}\n\nfunction generateId( location: Location, name: Name ) {\n\treturn `${ location }::${ name }`;\n}\n","import * as React from 'react';\nimport { ReactNode, Suspense } from 'react';\nimport ErrorBoundary from './error-boundary';\n\nexport default function FillerWrapper( { children }: { children: ReactNode } ) {\n\treturn (\n\t\t<ErrorBoundary fallback={ null }>\n\t\t\t<Suspense fallback={ null }>\n\t\t\t\t{ children }\n\t\t\t</Suspense>\n\t\t</ErrorBoundary>\n\t);\n}\n","import { Component, 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 { useMemo } from 'react';\nimport { getInjectionsOf } from '../injections';\nimport { Injection } from '../types';\n\nexport default function useInjectionsOf( locations: string[] ): Injection[][];\nexport default function useInjectionsOf( location: string ): Injection[];\nexport default function useInjectionsOf( locations: string | string[] ) {\n\treturn useMemo(\n\t\t() => {\n\t\t\tif ( Array.isArray( locations ) ) {\n\t\t\t\treturn locations.map( ( location ) => getInjectionsOf( location ) );\n\t\t\t}\n\n\t\t\treturn getInjectionsOf( locations );\n\t\t},\n\t\t[ locations ]\n\t);\n}\n","import * as React from 'react';\nimport useInjectionsOf from '../hooks/use-injections-of';\nimport { Location } from '../types';\n\ntype Props = {\n\tlocation: Location\n}\n\nexport default function Slot( { location }: Props ) {\n\tconst injections = useInjectionsOf( location );\n\n\treturn (\n\t\t<>\n\t\t\t{ injections.map( ( { id, filler: Filler } ) => (\n\t\t\t\t<Filler key={ id } />\n\t\t\t) ) }\n\t\t</>\n\t);\n}\n"],"mappings":";AAAA,YAAYA,YAAW;;;ACAvB,YAAY,WAAW;AACvB,SAAoB,gBAAgB;;;ACDpC,SAAS,iBAA4B;AAWrC,IAAqB,gBAArB,cAA2C,UAAwB;AAAA,EAC3D,QAAe;AAAA,IACrB,UAAU;AAAA,EACX;AAAA,EAEA,OAAc,2BAAkC;AAE/C,WAAO,EAAE,UAAU,KAAK;AAAA,EACzB;AAAA,EAEO,SAAS;AACf,QAAK,KAAK,MAAM,UAAW;AAC1B,aAAO,KAAK,MAAM;AAAA,IACnB;AAEA,WAAO,KAAK,MAAM;AAAA,EACnB;AACD;;;ADxBe,SAAR,cAAgC,EAAE,SAAS,GAA6B;AAC9E,SACC,oCAAC,iBAAc,UAAW,QACzB,oCAAC,YAAS,UAAW,QAClB,QACH,CACD;AAEF;;;ADRA,IAAM,mBAAmB;AAEzB,IAAM,aAAiC,oBAAI,IAAI;AASxC,SAAS,OAAQ,EAAE,UAAU,QAAQ,MAAM,UAAU,CAAC,EAAE,GAAgB;AAC9E,QAAM,KAAK,WAAY,UAAU,IAAK;AAEtC,MAAK,WAAW,IAAK,EAAG,KAAK,CAAE,SAAS,WAAY;AAEnD,YAAQ;AAAA,MACP,uBAAwB,yBAA2B;AAAA,IACpD;AAEA;AAAA,EACD;AAEA,QAAM,YAAY;AAAA,IACjB;AAAA,IACA;AAAA,IACA,QAAQ,WAAY,MAAO;AAAA,IAC3B,UAAU,QAAQ,YAAY;AAAA,EAC/B;AAEA,aAAW,IAAK,IAAI,SAAU;AAC/B;AAEO,SAAS,kBAAmB,UAAqB;AACvD,SAAO,CAAE,EAAE,QAAQ,MAAM,QAAQ,MAAqC;AACrE,WAAO,OAAQ,EAAE,UAAU,MAAM,QAAQ,QAAQ,CAAE;AAAA,EACpD;AACD;AAEO,SAAS,gBAAiB,UAAmB;AACnD,SAAO,CAAE,GAAG,WAAW,OAAO,CAAE,EAC9B,OAAQ,CAAE,cAAe,UAAU,aAAa,QAAS,EACzD,KAAM,CAAE,GAAG,MAAO,EAAE,WAAW,EAAE,QAAS;AAC7C;AAEO,SAAS,kBAAkB;AACjC,aAAW,MAAM;AAClB;AAEA,SAAS,WAAY,iBAA0B;AAC9C,SAAO,CAAE,UACR,qCAAC,qBACA,qCAAC,mBAAkB,GAAG,OAAQ,CAC/B;AAEF;AAEA,SAAS,WAAY,UAAoB,MAAa;AACrD,SAAO,GAAI,aAAe;AAC3B;;;AG/DA,SAAS,eAAe;AAMT,SAAR,gBAAkC,WAA+B;AACvE,SAAO;AAAA,IACN,MAAM;AACL,UAAK,MAAM,QAAS,SAAU,GAAI;AACjC,eAAO,UAAU,IAAK,CAAE,aAAc,gBAAiB,QAAS,CAAE;AAAA,MACnE;AAEA,aAAO,gBAAiB,SAAU;AAAA,IACnC;AAAA,IACA,CAAE,SAAU;AAAA,EACb;AACD;;;ACjBA,YAAYC,YAAW;AAQR,SAAR,KAAuB,EAAE,SAAS,GAAW;AACnD,QAAMC,cAAa,gBAAiB,QAAS;AAE7C,SACC,4DACGA,YAAW,IAAK,CAAE,EAAE,IAAI,QAAQ,OAAO,MACxC,qCAAC,UAAO,KAAM,IAAK,CAClB,CACH;AAEF;","names":["React","React","injections"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@elementor/locations",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"author": "Elementor Team",
|
|
6
|
+
"homepage": "https://elementor.com/",
|
|
7
|
+
"license": "GPL-3.0-or-later",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"module": "dist/index.mjs",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./dist/index.mjs",
|
|
14
|
+
"require": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts"
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/elementor/elementor-packages.git",
|
|
22
|
+
"directory": "packages/locations"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/elementor/elementor-packages/issues"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsup --config=../../tsup.build.ts",
|
|
32
|
+
"dev": "tsup --config=../../tsup.dev.ts"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"react": "17.x"
|
|
36
|
+
},
|
|
37
|
+
"gitHead": "2ba9f13a9dbd085eb6ed8e6e303e9275ce626b8d"
|
|
38
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import { lazy } from 'react';
|
|
4
|
+
import { inject, getInjectionsOf } from '../injections';
|
|
5
|
+
import Slot from '../components/slot';
|
|
6
|
+
|
|
7
|
+
describe( '@elementor/locations injections', () => {
|
|
8
|
+
it( 'should render components based on the location name', () => {
|
|
9
|
+
// Arrange.
|
|
10
|
+
inject( {
|
|
11
|
+
name: 'test-1',
|
|
12
|
+
location: 'test',
|
|
13
|
+
filler: () => <div data-testid="element">First div</div>,
|
|
14
|
+
} );
|
|
15
|
+
|
|
16
|
+
inject( {
|
|
17
|
+
name: 'test-2',
|
|
18
|
+
location: 'test',
|
|
19
|
+
filler: () => <div data-testid="element">Second div</div>,
|
|
20
|
+
} );
|
|
21
|
+
|
|
22
|
+
inject( {
|
|
23
|
+
name: 'test-3',
|
|
24
|
+
location: 'test2',
|
|
25
|
+
filler: () => <div data-testid="element">Should not exist</div>,
|
|
26
|
+
} );
|
|
27
|
+
|
|
28
|
+
// Act.
|
|
29
|
+
const { getAllByTestId } = render( <Slot location="test" /> );
|
|
30
|
+
|
|
31
|
+
// Assert.
|
|
32
|
+
const elements = getAllByTestId( 'element' );
|
|
33
|
+
|
|
34
|
+
expect( elements ).toHaveLength( 2 );
|
|
35
|
+
expect( elements[ 0 ].innerHTML ).toBe( 'First div' );
|
|
36
|
+
expect( elements[ 1 ].innerHTML ).toBe( 'Second div' );
|
|
37
|
+
} );
|
|
38
|
+
|
|
39
|
+
it( 'should render components based on priority', () => {
|
|
40
|
+
// Arrange.
|
|
41
|
+
inject( {
|
|
42
|
+
name: 'test-1',
|
|
43
|
+
location: 'test',
|
|
44
|
+
filler: () => <div data-testid="element">Third div</div>,
|
|
45
|
+
// Default priority is 10.
|
|
46
|
+
} );
|
|
47
|
+
|
|
48
|
+
inject( {
|
|
49
|
+
name: 'test-2',
|
|
50
|
+
location: 'test',
|
|
51
|
+
filler: () => <div data-testid="element">First div</div>,
|
|
52
|
+
options: { priority: 5 },
|
|
53
|
+
} );
|
|
54
|
+
|
|
55
|
+
inject( {
|
|
56
|
+
name: 'test-3',
|
|
57
|
+
location: 'test',
|
|
58
|
+
filler: () => <div data-testid="element">Second div</div>,
|
|
59
|
+
options: { priority: 5 },
|
|
60
|
+
} );
|
|
61
|
+
|
|
62
|
+
// Act.
|
|
63
|
+
const { getAllByTestId } = render( <Slot location="test" /> );
|
|
64
|
+
|
|
65
|
+
// Assert.
|
|
66
|
+
const elements = getAllByTestId( 'element' );
|
|
67
|
+
|
|
68
|
+
expect( elements ).toHaveLength( 3 );
|
|
69
|
+
expect( elements[ 0 ].innerHTML ).toBe( 'First div' );
|
|
70
|
+
expect( elements[ 1 ].innerHTML ).toBe( 'Second div' );
|
|
71
|
+
expect( elements[ 2 ].innerHTML ).toBe( 'Third div' );
|
|
72
|
+
} );
|
|
73
|
+
|
|
74
|
+
it( 'should render empty slot when there are no fills', () => {
|
|
75
|
+
// Arrange + Act.
|
|
76
|
+
const { container } = render( <Slot location="empty" /> );
|
|
77
|
+
|
|
78
|
+
// Assert.
|
|
79
|
+
expect( container.innerHTML ).toBe( '' );
|
|
80
|
+
} );
|
|
81
|
+
|
|
82
|
+
it( 'should render lazy components', async () => {
|
|
83
|
+
// Arrange.
|
|
84
|
+
inject( {
|
|
85
|
+
name: 'test-1',
|
|
86
|
+
location: 'test',
|
|
87
|
+
filler: () => <div>First div</div>,
|
|
88
|
+
} );
|
|
89
|
+
|
|
90
|
+
inject( {
|
|
91
|
+
name: 'test-2',
|
|
92
|
+
location: 'test',
|
|
93
|
+
filler: lazy( () => Promise.resolve( {
|
|
94
|
+
default: () => <div>Second div</div>,
|
|
95
|
+
} ) ),
|
|
96
|
+
} );
|
|
97
|
+
|
|
98
|
+
// Act.
|
|
99
|
+
const { queryByText, findByText } = render( <Slot location="test" /> );
|
|
100
|
+
|
|
101
|
+
// Assert.
|
|
102
|
+
expect( queryByText( 'First div' ) ).toBeTruthy();
|
|
103
|
+
expect( queryByText( 'Second div' ) ).toBeNull();
|
|
104
|
+
|
|
105
|
+
// Waits for the lazy component to be loaded.
|
|
106
|
+
await findByText( 'Second div' );
|
|
107
|
+
|
|
108
|
+
expect( queryByText( 'First div' ) ).toBeTruthy();
|
|
109
|
+
expect( queryByText( 'Second div' ) ).toBeTruthy();
|
|
110
|
+
} );
|
|
111
|
+
|
|
112
|
+
it( 'should error when injecting filler with the same name (without overwrite option)', async () => {
|
|
113
|
+
// Arrange.
|
|
114
|
+
inject( {
|
|
115
|
+
name: 'test',
|
|
116
|
+
location: 'test',
|
|
117
|
+
filler: () => <div>First div</div>,
|
|
118
|
+
} );
|
|
119
|
+
|
|
120
|
+
inject( {
|
|
121
|
+
name: 'test',
|
|
122
|
+
location: 'test',
|
|
123
|
+
filler: () => <div>Second div</div>,
|
|
124
|
+
} );
|
|
125
|
+
|
|
126
|
+
// Act
|
|
127
|
+
const { queryByText } = render( <Slot location="test" /> );
|
|
128
|
+
|
|
129
|
+
// Assert.
|
|
130
|
+
expect( queryByText( 'First div' ) ).toBeTruthy();
|
|
131
|
+
expect( queryByText( 'Second div' ) ).toBeNull();
|
|
132
|
+
expect( console ).toHaveErrored();
|
|
133
|
+
} );
|
|
134
|
+
|
|
135
|
+
it( 'should overwrite the filler if has same name', async () => {
|
|
136
|
+
// Arrange.
|
|
137
|
+
inject( {
|
|
138
|
+
name: 'test',
|
|
139
|
+
location: 'test',
|
|
140
|
+
filler: () => <div>First div</div>,
|
|
141
|
+
} );
|
|
142
|
+
|
|
143
|
+
inject( {
|
|
144
|
+
name: 'test',
|
|
145
|
+
location: 'test',
|
|
146
|
+
filler: () => <div>Second div</div>,
|
|
147
|
+
options: { overwrite: true },
|
|
148
|
+
} );
|
|
149
|
+
|
|
150
|
+
inject( {
|
|
151
|
+
name: 'test-2',
|
|
152
|
+
location: 'test',
|
|
153
|
+
filler: () => <div>Third div</div>,
|
|
154
|
+
options: { overwrite: true },
|
|
155
|
+
} );
|
|
156
|
+
|
|
157
|
+
// Act
|
|
158
|
+
const { queryByText } = render( <Slot location="test" /> );
|
|
159
|
+
|
|
160
|
+
// Assert.
|
|
161
|
+
expect( queryByText( 'First div' ) ).toBeNull();
|
|
162
|
+
expect( queryByText( 'Second div' ) ).toBeTruthy();
|
|
163
|
+
expect( queryByText( 'Third div' ) ).toBeTruthy();
|
|
164
|
+
} );
|
|
165
|
+
|
|
166
|
+
it( 'should overwrite the injection priority', () => {
|
|
167
|
+
// Arrange.
|
|
168
|
+
inject( {
|
|
169
|
+
name: 'test-1',
|
|
170
|
+
location: 'test',
|
|
171
|
+
filler: () => <div />,
|
|
172
|
+
options: { priority: 5 },
|
|
173
|
+
} );
|
|
174
|
+
|
|
175
|
+
inject( {
|
|
176
|
+
name: 'test-1',
|
|
177
|
+
location: 'test',
|
|
178
|
+
filler: () => <div />,
|
|
179
|
+
options: { overwrite: true },
|
|
180
|
+
} );
|
|
181
|
+
|
|
182
|
+
// Act + Assert.
|
|
183
|
+
expect( getInjectionsOf( 'test' ) ).toHaveLength( 1 );
|
|
184
|
+
expect( getInjectionsOf( 'test' )[ 0 ].priority ).toBe( 10 );
|
|
185
|
+
} );
|
|
186
|
+
|
|
187
|
+
it( 'should catch filler errors with error boundary', () => {
|
|
188
|
+
// Arrange.
|
|
189
|
+
inject( {
|
|
190
|
+
name: 'test-1',
|
|
191
|
+
location: 'test',
|
|
192
|
+
filler: () => <div>Test 1</div>,
|
|
193
|
+
} );
|
|
194
|
+
|
|
195
|
+
inject( {
|
|
196
|
+
name: 'test-2',
|
|
197
|
+
location: 'test',
|
|
198
|
+
filler: () => {
|
|
199
|
+
throw new Error( 'Error' );
|
|
200
|
+
},
|
|
201
|
+
} );
|
|
202
|
+
|
|
203
|
+
inject( {
|
|
204
|
+
name: 'test-3',
|
|
205
|
+
location: 'test',
|
|
206
|
+
filler: () => <div>Test 3</div>,
|
|
207
|
+
} );
|
|
208
|
+
|
|
209
|
+
// Act.
|
|
210
|
+
const { queryByText } = render( <Slot location="test" /> );
|
|
211
|
+
|
|
212
|
+
// Assert.
|
|
213
|
+
expect( queryByText( 'Test 1' ) ).toBeTruthy();
|
|
214
|
+
expect( queryByText( 'Test 3' ) ).toBeTruthy();
|
|
215
|
+
expect( console ).toHaveErrored();
|
|
216
|
+
} );
|
|
217
|
+
} );
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Component, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
children?: ReactNode;
|
|
5
|
+
fallback: ReactNode;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface State {
|
|
9
|
+
hasError: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default class ErrorBoundary extends Component<Props, State> {
|
|
13
|
+
public state: State = {
|
|
14
|
+
hasError: false,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
public static getDerivedStateFromError(): State {
|
|
18
|
+
// Update state so the next render will show the fallback UI.
|
|
19
|
+
return { hasError: true };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public render() {
|
|
23
|
+
if ( this.state.hasError ) {
|
|
24
|
+
return this.props.fallback;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return this.props.children;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ReactNode, Suspense } from 'react';
|
|
3
|
+
import ErrorBoundary from './error-boundary';
|
|
4
|
+
|
|
5
|
+
export default function FillerWrapper( { children }: { children: ReactNode } ) {
|
|
6
|
+
return (
|
|
7
|
+
<ErrorBoundary fallback={ null }>
|
|
8
|
+
<Suspense fallback={ null }>
|
|
9
|
+
{ children }
|
|
10
|
+
</Suspense>
|
|
11
|
+
</ErrorBoundary>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import useInjectionsOf from '../hooks/use-injections-of';
|
|
3
|
+
import { Location } from '../types';
|
|
4
|
+
|
|
5
|
+
type Props = {
|
|
6
|
+
location: Location
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function Slot( { location }: Props ) {
|
|
10
|
+
const injections = useInjectionsOf( location );
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<>
|
|
14
|
+
{ injections.map( ( { id, filler: Filler } ) => (
|
|
15
|
+
<Filler key={ id } />
|
|
16
|
+
) ) }
|
|
17
|
+
</>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { getInjectionsOf } from '../injections';
|
|
3
|
+
import { Injection } from '../types';
|
|
4
|
+
|
|
5
|
+
export default function useInjectionsOf( locations: string[] ): Injection[][];
|
|
6
|
+
export default function useInjectionsOf( location: string ): Injection[];
|
|
7
|
+
export default function useInjectionsOf( locations: string | string[] ) {
|
|
8
|
+
return useMemo(
|
|
9
|
+
() => {
|
|
10
|
+
if ( Array.isArray( locations ) ) {
|
|
11
|
+
return locations.map( ( location ) => getInjectionsOf( location ) );
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return getInjectionsOf( locations );
|
|
15
|
+
},
|
|
16
|
+
[ locations ]
|
|
17
|
+
);
|
|
18
|
+
}
|
package/src/index.ts
ADDED