@ryo-98/react-api-bridge
Version:
A registry for component imperative api
428 lines (424 loc) • 14.8 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// ../src/bridge.tsx
var bridge_exports = {};
__export(bridge_exports, {
default: () => bridge_default
});
module.exports = __toCommonJS(bridge_exports);
var import_react = __toESM(require("react"));
// ../src/utils.ts
var removeArrayElement = (entity, element) => {
if (Array.isArray(entity)) {
const deleteIndex = entity.findIndex((r) => r === element);
if (deleteIndex > -1) {
entity.splice(deleteIndex, 1);
}
}
};
function tryInvoke(mayFn) {
if (typeof mayFn === "function") {
mayFn();
return true;
}
return false;
}
var prefixes = [];
var lastOccupiedNumber = 0;
var genHookId = () => {
if (lastOccupiedNumber < Number.MAX_SAFE_INTEGER) {
lastOccupiedNumber++;
} else {
prefixes.push(String.fromCharCode(prefixes.length));
lastOccupiedNumber = 0;
}
return [...prefixes, lastOccupiedNumber].join("");
};
function appendToMappedValue(map, key, element) {
let arr = map.get(key);
if (typeof arr === "undefined") {
arr = [element];
map.set(key, arr);
} else {
arr.push(element);
}
return () => {
removeArrayElement(arr, element);
if (arr?.length === 0) {
map.delete(key);
}
};
}
// ../src/bridge.tsx
var createBridge = (...args) => {
const payload = args[0];
const output = genOutput(payload);
const currying = (options) => {
return genOutput(payload, options);
};
return Object.assign(currying, output);
};
var bridge_default = createBridge;
function genOutput(payload, bridgeOptions) {
const defaultContextValue = {
bridge: {},
parent: void 0,
payload
};
const BridgeContext = (0, import_react.createContext)(defaultContextValue);
const cacheInitCbMap = /* @__PURE__ */ new WeakMap();
const pendingResolverMap = /* @__PURE__ */ new Map();
const _getPendingContextMap = (_apiNList) => {
return pendingResolverMap.get(_apiNList);
};
const _setPendingContextMap = (_apiNList, map) => {
return pendingResolverMap.set(_apiNList, map);
};
function getIsMulti(name) {
return bridgeOptions?.[name]?.isMulti;
}
const _getApiDesc = (name, source) => {
let isInitial = false;
if (!source[name]) {
isInitial = true;
const isMulti = getIsMulti(name);
const apiNList = isMulti ? [] : (0, import_react.createRef)();
source[name] = {
options: bridgeOptions?.[name],
apiNList
};
}
return { apiNList: source[name].apiNList, isInitial };
};
const Boundary = (0, import_react.forwardRef)((props, ref) => {
const _ownBridge = (0, import_react.useMemo)(() => ({}), []);
const bridge = props.contextValue?.bridge || _ownBridge;
const paramParent = props.contextValue?.parent;
const upperParent = (0, import_react.useContext)(BridgeContext);
const parent = paramParent || upperParent;
const _payload = props.contextValue?.payload ?? props.payload;
(0, import_react.useImperativeHandle)(ref, () => {
return {
getAPI: (name) => {
return _getApiDesc(name, bridge).apiNList;
},
parent,
payload: _payload
};
}, [_payload]);
const contextValue = (0, import_react.useMemo)(() => {
const final = {
...props.contextValue || {
bridge,
parent
},
payload: _payload
};
return final;
}, [props.contextValue, _payload]);
return /* @__PURE__ */ import_react.default.createElement(BridgeContext.Provider, { value: contextValue }, props.children);
});
Boundary.displayName = "Boundary";
const useFinalContextValue = (options) => {
const { contextValue: _outerContextValue } = options || {};
const ownContextValue = (0, import_react.useContext)(BridgeContext);
return _outerContextValue || ownContextValue;
};
const initializedOnInitMap = /* @__PURE__ */ new Map();
function mountHookInitEffect(name, onInit, apiNList, hookId) {
if (!onInit) return;
const isMulti = getIsMulti(name) ?? false;
const involvedApiList = [];
let deferFn;
if (apiNList && !isMulti) {
const _assertedApi = apiNList;
if (!_assertedApi.current) return;
const _assertedOnInit = onInit;
deferFn = () => {
return _assertedOnInit(_assertedApi);
};
involvedApiList.push(_assertedApi);
} else if (apiNList && isMulti) {
const _assertedApiList = apiNList;
if (!_assertedApiList.length) return;
const _assertedOnInit = onInit;
deferFn = () => {
return _assertedOnInit(void 0, _assertedApiList);
};
involvedApiList.push(..._assertedApiList);
} else {
throw new Error("This might the internal Error of react-api-bridge");
}
let clearFns = [];
if (involvedApiList.some((apiRef) => !initializedOnInitMap.get(apiRef)?.includes(hookId))) {
clearFns.push(
deferFn?.(),
...involvedApiList.map((apiRef) => {
return appendToMappedValue(initializedOnInitMap, apiRef, hookId);
})
);
}
return () => {
clearFns.forEach(tryInvoke);
};
}
function useInitEffect(onInit, name, apiNList, contextValue) {
const hookId = useHookId();
(0, import_react.useEffect)(() => {
if (!onInit) return;
const removeCache = appendToMappedValue(cacheInitCbMap, apiNList, { onInit, hookId });
const clearInitEffect = mountHookInitEffect(name, onInit, apiNList, hookId);
return () => {
removeCache();
clearInitEffect?.();
};
}, []);
(0, import_react.useEffect)(() => {
if (!onInit) return;
const cacheCbs = cacheInitCbMap.get(apiNList);
cacheCbs?.forEach((couple) => {
if (couple.hookId === hookId) {
couple.onInit = onInit;
}
});
}, [onInit]);
(0, import_react.useEffect)(() => {
return () => {
initializedOnInitMap.forEach((arr, key) => {
removeArrayElement(arr, hookId);
});
};
}, []);
}
function _getUpperContextValue(start, shouldForwardYield) {
let parent = start.parent;
while (true) {
if (!parent) break;
if (!shouldForwardYield) break;
const apiNames = Object.keys(parent.bridge);
const allAPI = apiNames.reduce((all, apiName) => {
const bridge = parent.bridge[apiName];
all[apiName] = bridge.apiNList;
return all;
}, {});
if (!!shouldForwardYield({
payload: parent.payload,
parent: parent.parent,
allAPI
})) break;
parent = parent.parent;
}
return parent;
}
function _getUpperApiDesc(contextValue, _name, options) {
const parent = _getUpperContextValue(options?.contextValue || contextValue, options?.shouldForwardYield);
if (!parent) return;
return _getApiDesc(_name, parent.bridge);
}
function _getResolverOrCreateWhenMissing(apiNList, contextValue, initial) {
let contextValueMap = _getPendingContextMap(apiNList);
if (!contextValueMap) {
contextValueMap = /* @__PURE__ */ new Map();
_setPendingContextMap(apiNList, contextValueMap);
}
let resolvers = contextValueMap.get(contextValue);
if (!resolvers) {
resolvers = [];
contextValueMap.set(contextValue, resolvers);
}
let resolver = resolvers.find((r) => r.initial);
if (!resolver || !initial) {
let outerResolve;
const newPromise = new Promise((resolve) => {
outerResolve = resolve;
});
resolver = {
initial,
promise: newPromise,
resolve: outerResolve
};
resolvers.push(resolver);
}
return {
resolver,
contextValueMap,
resolvers
};
}
return {
Boundary,
initCbMap: cacheInitCbMap,
initializedCallbacksMap: initializedOnInitMap,
getAPI(name, contextValue = defaultContextValue) {
return _getApiDesc(name, contextValue.bridge).apiNList;
},
getAPIAsync(name, options) {
const { contextValue, initial } = {
contextValue: defaultContextValue,
initial: true,
...options
};
const { apiNList } = _getApiDesc(name, contextValue.bridge);
const { resolver } = _getResolverOrCreateWhenMissing(apiNList, contextValue, initial);
return resolver.promise;
},
useAPI: (name, hookOptions) => {
const { onInit } = hookOptions || {};
const contextValue = useFinalContextValue(hookOptions);
const { apiNList } = _getApiDesc(name, contextValue.bridge);
useInitEffect(onInit, name, apiNList, contextValue);
return apiNList;
},
useBoundaryPayload: (hookOptions) => {
const contextValue = useFinalContextValue(hookOptions);
return contextValue.payload;
},
useBoundaryRef: () => {
return (0, import_react.useRef)(null);
},
useContextValue: (...args) => {
const payload2 = args[0];
const parent = (0, import_react.useContext)(BridgeContext);
return (0, import_react.useMemo)(() => {
return {
bridge: {},
parent,
payload: payload2
};
}, []);
},
useRegister: (name, init, deps, hookOptions) => {
const isMulti = getIsMulti(name);
const contextValue = useFinalContextValue(hookOptions);
const { apiNList } = (0, import_react.useMemo)(() => _getApiDesc(name, contextValue.bridge), [name, contextValue]);
const apiRef = useUniqueElementRef(apiNList);
const registerId = useHookId();
(0, import_react.useImperativeHandle)(apiRef, () => {
return init();
}, deps);
(0, import_react.useEffect)(() => {
let clearFns = [];
const { resolvers, resolver: initialResolver } = _getResolverOrCreateWhenMissing(apiNList, contextValue, true);
resolvers.forEach((resolver) => {
resolver.resolve(apiNList);
});
resolvers.length = 0;
resolvers.push(initialResolver);
const callbacks = cacheInitCbMap.get(apiNList);
callbacks?.filter((initInfo) => {
return !initializedOnInitMap.get(apiRef)?.includes(initInfo.hookId);
}).forEach((initInfo) => {
clearFns.push(appendToMappedValue(initializedOnInitMap, apiRef, initInfo.hookId));
const onInit = initInfo.onInit;
if (isMulti) {
const _assertedOnInit = onInit;
clearFns.push(_assertedOnInit(apiRef, apiNList));
} else {
const _assertedOnInit = onInit;
clearFns.push(_assertedOnInit(apiRef));
}
});
return () => {
clearFns.forEach(tryInvoke);
initializedOnInitMap.delete(apiRef);
};
}, []);
},
useTools: (hookOptions) => {
const contextValue = useFinalContextValue(hookOptions);
const getAPI = (0, import_react.useCallback)((_name, options) => {
return _getApiDesc(_name, options?.contextValue?.bridge || contextValue.bridge).apiNList;
}, [contextValue.bridge]);
const getBoundaryPayload = (0, import_react.useCallback)((options) => {
return (options?.contextValue || contextValue).payload;
}, [contextValue.payload]);
const getUpperAPI = (0, import_react.useCallback)((_name, options) => {
return _getUpperApiDesc(options?.contextValue || contextValue, _name, options)?.apiNList;
}, []);
const getUpperBoundaryPayload = (0, import_react.useCallback)((options) => {
const parent = _getUpperContextValue(options?.contextValue || contextValue, options?.shouldForwardYield);
if (!parent) return;
return parent.payload;
}, []);
const getAPIAsync = (_name, options) => {
const { initial } = {
initial: true,
...options
};
const { apiNList } = _getApiDesc(_name, (options?.contextValue || contextValue).bridge);
const { resolver } = _getResolverOrCreateWhenMissing(apiNList, contextValue, initial);
return resolver.promise;
};
return {
getAPI,
getBoundaryPayload,
getUpperAPI,
getUpperBoundaryPayload,
getAPIAsync
};
},
useUpperAPI: (name, hookOptions) => {
const {
onInit
} = hookOptions || {};
const contextValue = useFinalContextValue(hookOptions);
const _apiNList = (0, import_react.useMemo)(() => {
return _getUpperApiDesc(contextValue, name, hookOptions)?.apiNList;
}, []);
if (!_apiNList) return;
useInitEffect(onInit, name, _apiNList, contextValue);
return _apiNList;
},
useUpperBoundaryPayload: (hookOptions) => {
const { shouldForwardYield } = hookOptions || {};
const contextValue = useFinalContextValue(hookOptions);
const boundaryContextValue = (0, import_react.useMemo)(() => {
return _getUpperContextValue(contextValue, shouldForwardYield);
}, []);
return boundaryContextValue?.payload;
}
};
}
function useUniqueElementRef(entity) {
const elRef = (0, import_react.useRef)(null);
(0, import_react.useEffect)(() => {
if (Array.isArray(entity)) {
entity.push(elRef);
}
return () => {
removeArrayElement(entity, elRef);
};
}, [entity]);
return Array.isArray(entity) ? elRef : entity;
}
function useHookId() {
return (0, import_react.useMemo)(() => {
return true ? genHookId() : Symbol("hookId");
}, []);
}