@servlyadmin/runtime-core 0.1.45 → 0.2.1
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/README.md +215 -1
- package/dist/index.cjs +623 -1
- package/dist/index.js +615 -1
- package/package.json +3 -3
- package/dist/index.d.cts +0 -2076
- package/dist/index.d.ts +0 -2076
package/dist/index.js
CHANGED
|
@@ -718,8 +718,96 @@ function resolveBindingPath(path, context) {
|
|
|
718
718
|
}
|
|
719
719
|
return navigatePath(source, parts.slice(startIndex));
|
|
720
720
|
}
|
|
721
|
+
var FUNCTION_CALL_REGEX = /^(\w+)\s*\((.*)\)$/s;
|
|
722
|
+
function executeGlobalFunction(funcName, argsStr, context) {
|
|
723
|
+
if (context.functionMap?.[funcName]) {
|
|
724
|
+
try {
|
|
725
|
+
const args = parseFunctionArguments(argsStr, context);
|
|
726
|
+
return context.functionMap[funcName](...args);
|
|
727
|
+
} catch (error) {
|
|
728
|
+
console.error(`[GlobalFunction] Error executing ${funcName}:`, error);
|
|
729
|
+
return void 0;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
const func = context.globalFunctions?.find((f) => f.name === funcName);
|
|
733
|
+
if (!func) {
|
|
734
|
+
console.warn(`[GlobalFunction] Function not found: ${funcName}`);
|
|
735
|
+
return void 0;
|
|
736
|
+
}
|
|
737
|
+
try {
|
|
738
|
+
const args = parseFunctionArguments(argsStr, context);
|
|
739
|
+
const paramNames = func.parameters.map((p) => p.name);
|
|
740
|
+
const fnBody = `
|
|
741
|
+
const state = __ctx__.state || {};
|
|
742
|
+
const props = __ctx__.props || {};
|
|
743
|
+
const config = __ctx__.configs || __ctx__.context || {};
|
|
744
|
+
${func.code}
|
|
745
|
+
`;
|
|
746
|
+
const fn = new Function("__ctx__", ...paramNames, fnBody);
|
|
747
|
+
return fn(context, ...args);
|
|
748
|
+
} catch (error) {
|
|
749
|
+
console.error(`[GlobalFunction] Error executing ${funcName}:`, error);
|
|
750
|
+
return void 0;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
function parseFunctionArguments(argsStr, context) {
|
|
754
|
+
if (!argsStr.trim()) return [];
|
|
755
|
+
const args = [];
|
|
756
|
+
let current = "";
|
|
757
|
+
let depth = 0;
|
|
758
|
+
let inString = false;
|
|
759
|
+
let stringChar = "";
|
|
760
|
+
for (let i = 0; i < argsStr.length; i++) {
|
|
761
|
+
const char = argsStr[i];
|
|
762
|
+
const prevChar = argsStr[i - 1];
|
|
763
|
+
if ((char === '"' || char === "'") && prevChar !== "\\") {
|
|
764
|
+
if (!inString) {
|
|
765
|
+
inString = true;
|
|
766
|
+
stringChar = char;
|
|
767
|
+
} else if (char === stringChar) {
|
|
768
|
+
inString = false;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
if (!inString) {
|
|
772
|
+
if (char === "(" || char === "[" || char === "{") depth++;
|
|
773
|
+
if (char === ")" || char === "]" || char === "}") depth--;
|
|
774
|
+
}
|
|
775
|
+
if (char === "," && depth === 0 && !inString) {
|
|
776
|
+
args.push(parseArgumentValue(current.trim(), context));
|
|
777
|
+
current = "";
|
|
778
|
+
} else {
|
|
779
|
+
current += char;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
if (current.trim()) {
|
|
783
|
+
args.push(parseArgumentValue(current.trim(), context));
|
|
784
|
+
}
|
|
785
|
+
return args;
|
|
786
|
+
}
|
|
787
|
+
function parseArgumentValue(value, context) {
|
|
788
|
+
const trimmed = value.trim();
|
|
789
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
790
|
+
return trimmed.slice(1, -1);
|
|
791
|
+
}
|
|
792
|
+
if (!isNaN(Number(trimmed)) && trimmed !== "") {
|
|
793
|
+
return Number(trimmed);
|
|
794
|
+
}
|
|
795
|
+
if (trimmed === "true") return true;
|
|
796
|
+
if (trimmed === "false") return false;
|
|
797
|
+
if (trimmed === "null") return null;
|
|
798
|
+
if (trimmed === "undefined") return void 0;
|
|
799
|
+
if (trimmed.startsWith("{{") && trimmed.endsWith("}}")) {
|
|
800
|
+
return resolveTemplateValue(trimmed, context);
|
|
801
|
+
}
|
|
802
|
+
return resolveBindingPath(trimmed, context);
|
|
803
|
+
}
|
|
721
804
|
function resolveExpression(expression, context) {
|
|
722
805
|
const trimmed = expression.trim();
|
|
806
|
+
const funcMatch = trimmed.match(FUNCTION_CALL_REGEX);
|
|
807
|
+
if (funcMatch) {
|
|
808
|
+
const [, funcName, argsStr] = funcMatch;
|
|
809
|
+
return executeGlobalFunction(funcName, argsStr, context);
|
|
810
|
+
}
|
|
723
811
|
const comparisonOperators = ["===", "!==", "==", "!=", ">=", "<=", ">", "<"];
|
|
724
812
|
for (const op of comparisonOperators) {
|
|
725
813
|
if (trimmed.includes(op)) {
|
|
@@ -1636,6 +1724,203 @@ function getUrlInfo() {
|
|
|
1636
1724
|
}
|
|
1637
1725
|
|
|
1638
1726
|
// src/eventSystem.ts
|
|
1727
|
+
function createShortcuts(ctx) {
|
|
1728
|
+
return {
|
|
1729
|
+
/**
|
|
1730
|
+
* Render a dynamic list of items using a blueprint view
|
|
1731
|
+
* This is a simplified version for runtime - full implementation requires DOM access
|
|
1732
|
+
*/
|
|
1733
|
+
renderDynamicList: (options) => {
|
|
1734
|
+
const { blueprint, targetContainer, data = [], clearExisting = true, itemProps = {} } = options;
|
|
1735
|
+
const container = typeof document !== "undefined" ? document.querySelector(targetContainer) || document.querySelector(`[data-servly-id="${targetContainer.replace("#", "")}"]`) : null;
|
|
1736
|
+
if (!container) {
|
|
1737
|
+
console.warn(`[shortcuts.renderDynamicList] Container not found: ${targetContainer}`);
|
|
1738
|
+
return { success: false, error: "Container not found" };
|
|
1739
|
+
}
|
|
1740
|
+
if (clearExisting) {
|
|
1741
|
+
container.innerHTML = "";
|
|
1742
|
+
}
|
|
1743
|
+
const event = new CustomEvent("servly:renderDynamicList", {
|
|
1744
|
+
bubbles: true,
|
|
1745
|
+
detail: {
|
|
1746
|
+
blueprint,
|
|
1747
|
+
targetContainer,
|
|
1748
|
+
data,
|
|
1749
|
+
itemProps,
|
|
1750
|
+
container
|
|
1751
|
+
}
|
|
1752
|
+
});
|
|
1753
|
+
container.dispatchEvent(event);
|
|
1754
|
+
return { success: true, itemCount: data.length };
|
|
1755
|
+
},
|
|
1756
|
+
/**
|
|
1757
|
+
* Quick list rendering helper
|
|
1758
|
+
*/
|
|
1759
|
+
renderList: (blueprint, targetContainer, data, options = {}) => {
|
|
1760
|
+
return createShortcuts(ctx).renderDynamicList({
|
|
1761
|
+
blueprint,
|
|
1762
|
+
targetContainer,
|
|
1763
|
+
data,
|
|
1764
|
+
...options
|
|
1765
|
+
});
|
|
1766
|
+
},
|
|
1767
|
+
/**
|
|
1768
|
+
* Set state value
|
|
1769
|
+
*/
|
|
1770
|
+
setState: (key, value) => {
|
|
1771
|
+
if (ctx.stateManager) {
|
|
1772
|
+
ctx.stateManager.set(key, value, ctx.elementId);
|
|
1773
|
+
return { success: true };
|
|
1774
|
+
}
|
|
1775
|
+
return { success: false, error: "No state manager" };
|
|
1776
|
+
},
|
|
1777
|
+
/**
|
|
1778
|
+
* Get state value
|
|
1779
|
+
*/
|
|
1780
|
+
getState: (key) => {
|
|
1781
|
+
if (ctx.stateManager) {
|
|
1782
|
+
return ctx.stateManager.get(key);
|
|
1783
|
+
}
|
|
1784
|
+
return void 0;
|
|
1785
|
+
},
|
|
1786
|
+
/**
|
|
1787
|
+
* Toggle state value
|
|
1788
|
+
*/
|
|
1789
|
+
toggleState: (key) => {
|
|
1790
|
+
if (ctx.stateManager) {
|
|
1791
|
+
ctx.stateManager.toggle(key, ctx.elementId);
|
|
1792
|
+
return { success: true };
|
|
1793
|
+
}
|
|
1794
|
+
return { success: false, error: "No state manager" };
|
|
1795
|
+
},
|
|
1796
|
+
/**
|
|
1797
|
+
* Navigate to URL
|
|
1798
|
+
*/
|
|
1799
|
+
navigateTo: (url, options) => {
|
|
1800
|
+
navigateTo(url, options);
|
|
1801
|
+
return { success: true };
|
|
1802
|
+
},
|
|
1803
|
+
/**
|
|
1804
|
+
* Log to console (for debugging)
|
|
1805
|
+
*/
|
|
1806
|
+
log: (...args) => {
|
|
1807
|
+
console.log("[Servly]", ...args);
|
|
1808
|
+
},
|
|
1809
|
+
/**
|
|
1810
|
+
* Show alert
|
|
1811
|
+
*/
|
|
1812
|
+
alert: (message) => {
|
|
1813
|
+
if (typeof alert !== "undefined") {
|
|
1814
|
+
alert(message);
|
|
1815
|
+
}
|
|
1816
|
+
},
|
|
1817
|
+
/**
|
|
1818
|
+
* Get element by ID
|
|
1819
|
+
*/
|
|
1820
|
+
getElement: (id) => {
|
|
1821
|
+
if (typeof document === "undefined") return null;
|
|
1822
|
+
return document.querySelector(`[data-servly-id="${id}"]`) || document.getElementById(id);
|
|
1823
|
+
},
|
|
1824
|
+
/**
|
|
1825
|
+
* Set element text content
|
|
1826
|
+
*/
|
|
1827
|
+
setText: (selector, text) => {
|
|
1828
|
+
if (typeof document === "undefined") return { success: false };
|
|
1829
|
+
const el = document.querySelector(selector) || document.querySelector(`[data-servly-id="${selector.replace("#", "")}"]`);
|
|
1830
|
+
if (el) {
|
|
1831
|
+
el.textContent = text;
|
|
1832
|
+
return { success: true };
|
|
1833
|
+
}
|
|
1834
|
+
return { success: false, error: "Element not found" };
|
|
1835
|
+
},
|
|
1836
|
+
/**
|
|
1837
|
+
* Add class to element
|
|
1838
|
+
*/
|
|
1839
|
+
addClass: (selector, className) => {
|
|
1840
|
+
if (typeof document === "undefined") return { success: false };
|
|
1841
|
+
const el = document.querySelector(selector);
|
|
1842
|
+
if (el) {
|
|
1843
|
+
el.classList.add(className);
|
|
1844
|
+
return { success: true };
|
|
1845
|
+
}
|
|
1846
|
+
return { success: false, error: "Element not found" };
|
|
1847
|
+
},
|
|
1848
|
+
/**
|
|
1849
|
+
* Remove class from element
|
|
1850
|
+
*/
|
|
1851
|
+
removeClass: (selector, className) => {
|
|
1852
|
+
if (typeof document === "undefined") return { success: false };
|
|
1853
|
+
const el = document.querySelector(selector);
|
|
1854
|
+
if (el) {
|
|
1855
|
+
el.classList.remove(className);
|
|
1856
|
+
return { success: true };
|
|
1857
|
+
}
|
|
1858
|
+
return { success: false, error: "Element not found" };
|
|
1859
|
+
},
|
|
1860
|
+
/**
|
|
1861
|
+
* Toggle class on element
|
|
1862
|
+
*/
|
|
1863
|
+
toggleClass: (selector, className) => {
|
|
1864
|
+
if (typeof document === "undefined") return { success: false };
|
|
1865
|
+
const el = document.querySelector(selector);
|
|
1866
|
+
if (el) {
|
|
1867
|
+
el.classList.toggle(className);
|
|
1868
|
+
return { success: true };
|
|
1869
|
+
}
|
|
1870
|
+
return { success: false, error: "Element not found" };
|
|
1871
|
+
},
|
|
1872
|
+
/**
|
|
1873
|
+
* Local storage helpers
|
|
1874
|
+
*/
|
|
1875
|
+
localStorage: {
|
|
1876
|
+
get: (key, defaultValue) => getLocalStorage(key, defaultValue),
|
|
1877
|
+
set: (key, value) => setLocalStorage(key, value),
|
|
1878
|
+
remove: (key) => {
|
|
1879
|
+
if (typeof localStorage !== "undefined") {
|
|
1880
|
+
localStorage.removeItem(key);
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
},
|
|
1884
|
+
/**
|
|
1885
|
+
* Session storage helpers
|
|
1886
|
+
*/
|
|
1887
|
+
sessionStorage: {
|
|
1888
|
+
get: (key, defaultValue) => getSessionStorage(key, defaultValue),
|
|
1889
|
+
set: (key, value) => setSessionStorage(key, value),
|
|
1890
|
+
remove: (key) => {
|
|
1891
|
+
if (typeof sessionStorage !== "undefined") {
|
|
1892
|
+
sessionStorage.removeItem(key);
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
},
|
|
1896
|
+
/**
|
|
1897
|
+
* Clipboard helpers
|
|
1898
|
+
*/
|
|
1899
|
+
clipboard: {
|
|
1900
|
+
copy: async (text) => {
|
|
1901
|
+
if (typeof navigator !== "undefined" && navigator.clipboard) {
|
|
1902
|
+
try {
|
|
1903
|
+
await navigator.clipboard.writeText(text);
|
|
1904
|
+
return { success: true };
|
|
1905
|
+
} catch (error) {
|
|
1906
|
+
return { success: false, error };
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
return { success: false, error: "Clipboard not available" };
|
|
1910
|
+
}
|
|
1911
|
+
},
|
|
1912
|
+
/**
|
|
1913
|
+
* Delay execution
|
|
1914
|
+
*/
|
|
1915
|
+
delay: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
1916
|
+
/**
|
|
1917
|
+
* Current context accessors
|
|
1918
|
+
*/
|
|
1919
|
+
currentItem: ctx.currentItem,
|
|
1920
|
+
currentIndex: ctx.currentIndex,
|
|
1921
|
+
elementId: ctx.elementId
|
|
1922
|
+
};
|
|
1923
|
+
}
|
|
1639
1924
|
var builtInPlugins = {
|
|
1640
1925
|
/**
|
|
1641
1926
|
* Set state value
|
|
@@ -1762,6 +2047,7 @@ var builtInPlugins = {
|
|
|
1762
2047
|
const code = action.code || action.config?.code;
|
|
1763
2048
|
if (!code) return { success: false, error: "No code provided" };
|
|
1764
2049
|
try {
|
|
2050
|
+
const shortcuts = createShortcuts(ctx);
|
|
1765
2051
|
const fn = new Function(
|
|
1766
2052
|
"event",
|
|
1767
2053
|
"props",
|
|
@@ -1770,6 +2056,7 @@ var builtInPlugins = {
|
|
|
1770
2056
|
"stateManager",
|
|
1771
2057
|
"currentItem",
|
|
1772
2058
|
"currentIndex",
|
|
2059
|
+
"shortcuts",
|
|
1773
2060
|
code
|
|
1774
2061
|
);
|
|
1775
2062
|
const result = fn(
|
|
@@ -1779,7 +2066,8 @@ var builtInPlugins = {
|
|
|
1779
2066
|
ctx.context.context || {},
|
|
1780
2067
|
ctx.stateManager,
|
|
1781
2068
|
ctx.currentItem,
|
|
1782
|
-
ctx.currentIndex
|
|
2069
|
+
ctx.currentIndex,
|
|
2070
|
+
shortcuts
|
|
1783
2071
|
);
|
|
1784
2072
|
return { success: true, result };
|
|
1785
2073
|
} catch (error) {
|
|
@@ -4479,6 +4767,275 @@ async function getDependencyTree(id, options = {}) {
|
|
|
4479
4767
|
return data.data;
|
|
4480
4768
|
}
|
|
4481
4769
|
|
|
4770
|
+
// src/mount.ts
|
|
4771
|
+
async function mount(options) {
|
|
4772
|
+
const {
|
|
4773
|
+
componentId,
|
|
4774
|
+
target,
|
|
4775
|
+
props = {},
|
|
4776
|
+
state = {},
|
|
4777
|
+
context = {},
|
|
4778
|
+
version = "latest",
|
|
4779
|
+
eventHandlers,
|
|
4780
|
+
fetchOptions = {},
|
|
4781
|
+
onReady,
|
|
4782
|
+
onError,
|
|
4783
|
+
onLoadStart,
|
|
4784
|
+
onLoadEnd,
|
|
4785
|
+
loadingComponent,
|
|
4786
|
+
errorComponent,
|
|
4787
|
+
loadingDelay = 0,
|
|
4788
|
+
minLoadingTime = 0
|
|
4789
|
+
} = options;
|
|
4790
|
+
const container = typeof target === "string" ? document.querySelector(target) : target;
|
|
4791
|
+
if (!container) {
|
|
4792
|
+
const err = new Error(`Target container not found: ${target}`);
|
|
4793
|
+
onError?.(err);
|
|
4794
|
+
throw err;
|
|
4795
|
+
}
|
|
4796
|
+
let loadingElement = null;
|
|
4797
|
+
let loadingTimeout = null;
|
|
4798
|
+
let loadingStartTime = null;
|
|
4799
|
+
const showLoading = () => {
|
|
4800
|
+
if (loadingComponent) {
|
|
4801
|
+
loadingStartTime = Date.now();
|
|
4802
|
+
if (typeof loadingComponent === "string") {
|
|
4803
|
+
loadingElement = document.createElement("div");
|
|
4804
|
+
loadingElement.innerHTML = loadingComponent;
|
|
4805
|
+
loadingElement = loadingElement.firstElementChild || loadingElement;
|
|
4806
|
+
} else {
|
|
4807
|
+
loadingElement = loadingComponent.cloneNode(true);
|
|
4808
|
+
}
|
|
4809
|
+
container.appendChild(loadingElement);
|
|
4810
|
+
}
|
|
4811
|
+
};
|
|
4812
|
+
const removeLoading = async () => {
|
|
4813
|
+
if (loadingTimeout) {
|
|
4814
|
+
clearTimeout(loadingTimeout);
|
|
4815
|
+
loadingTimeout = null;
|
|
4816
|
+
}
|
|
4817
|
+
if (loadingStartTime && minLoadingTime > 0) {
|
|
4818
|
+
const elapsed = Date.now() - loadingStartTime;
|
|
4819
|
+
if (elapsed < minLoadingTime) {
|
|
4820
|
+
await new Promise((resolve) => setTimeout(resolve, minLoadingTime - elapsed));
|
|
4821
|
+
}
|
|
4822
|
+
}
|
|
4823
|
+
if (loadingElement && loadingElement.parentNode) {
|
|
4824
|
+
loadingElement.parentNode.removeChild(loadingElement);
|
|
4825
|
+
loadingElement = null;
|
|
4826
|
+
}
|
|
4827
|
+
};
|
|
4828
|
+
const showError = (err) => {
|
|
4829
|
+
if (errorComponent) {
|
|
4830
|
+
const errorContent = errorComponent(err);
|
|
4831
|
+
if (typeof errorContent === "string") {
|
|
4832
|
+
container.innerHTML = errorContent;
|
|
4833
|
+
} else {
|
|
4834
|
+
container.innerHTML = "";
|
|
4835
|
+
container.appendChild(errorContent);
|
|
4836
|
+
}
|
|
4837
|
+
}
|
|
4838
|
+
};
|
|
4839
|
+
try {
|
|
4840
|
+
onLoadStart?.();
|
|
4841
|
+
if (loadingComponent) {
|
|
4842
|
+
if (loadingDelay > 0) {
|
|
4843
|
+
loadingTimeout = setTimeout(showLoading, loadingDelay);
|
|
4844
|
+
} else {
|
|
4845
|
+
showLoading();
|
|
4846
|
+
}
|
|
4847
|
+
}
|
|
4848
|
+
const fetchResult = await fetchComponentWithDependencies(componentId, {
|
|
4849
|
+
...fetchOptions,
|
|
4850
|
+
version
|
|
4851
|
+
});
|
|
4852
|
+
const { data, fromCache, version: resolvedVersion, registry, views } = fetchResult;
|
|
4853
|
+
await removeLoading();
|
|
4854
|
+
const bindingContext = {
|
|
4855
|
+
props,
|
|
4856
|
+
state,
|
|
4857
|
+
context
|
|
4858
|
+
};
|
|
4859
|
+
const renderResult = render({
|
|
4860
|
+
container,
|
|
4861
|
+
elements: data.layout,
|
|
4862
|
+
context: bindingContext,
|
|
4863
|
+
eventHandlers,
|
|
4864
|
+
componentRegistry: registry,
|
|
4865
|
+
views
|
|
4866
|
+
});
|
|
4867
|
+
const result = {
|
|
4868
|
+
update: (newContext) => {
|
|
4869
|
+
renderResult.update({
|
|
4870
|
+
props: newContext.props ?? props,
|
|
4871
|
+
state: newContext.state ?? state,
|
|
4872
|
+
context: newContext.context ?? context
|
|
4873
|
+
});
|
|
4874
|
+
},
|
|
4875
|
+
destroy: () => {
|
|
4876
|
+
renderResult.destroy();
|
|
4877
|
+
},
|
|
4878
|
+
rootElement: renderResult.rootElement,
|
|
4879
|
+
container,
|
|
4880
|
+
data,
|
|
4881
|
+
fromCache,
|
|
4882
|
+
version: resolvedVersion
|
|
4883
|
+
};
|
|
4884
|
+
onLoadEnd?.();
|
|
4885
|
+
onReady?.(result);
|
|
4886
|
+
return result;
|
|
4887
|
+
} catch (error) {
|
|
4888
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
4889
|
+
await removeLoading();
|
|
4890
|
+
showError(err);
|
|
4891
|
+
onLoadEnd?.();
|
|
4892
|
+
onError?.(err);
|
|
4893
|
+
throw err;
|
|
4894
|
+
}
|
|
4895
|
+
}
|
|
4896
|
+
function mountData(options) {
|
|
4897
|
+
const {
|
|
4898
|
+
data,
|
|
4899
|
+
target,
|
|
4900
|
+
props = {},
|
|
4901
|
+
state = {},
|
|
4902
|
+
context = {},
|
|
4903
|
+
eventHandlers
|
|
4904
|
+
} = options;
|
|
4905
|
+
const container = typeof target === "string" ? document.querySelector(target) : target;
|
|
4906
|
+
if (!container) {
|
|
4907
|
+
throw new Error(`Target container not found: ${target}`);
|
|
4908
|
+
}
|
|
4909
|
+
const bindingContext = {
|
|
4910
|
+
props,
|
|
4911
|
+
state,
|
|
4912
|
+
context
|
|
4913
|
+
};
|
|
4914
|
+
const renderResult = render({
|
|
4915
|
+
container,
|
|
4916
|
+
elements: data.layout,
|
|
4917
|
+
context: bindingContext,
|
|
4918
|
+
eventHandlers
|
|
4919
|
+
});
|
|
4920
|
+
return {
|
|
4921
|
+
update: (newContext) => {
|
|
4922
|
+
renderResult.update({
|
|
4923
|
+
props: newContext.props ?? props,
|
|
4924
|
+
state: newContext.state ?? state,
|
|
4925
|
+
context: newContext.context ?? context
|
|
4926
|
+
});
|
|
4927
|
+
},
|
|
4928
|
+
destroy: () => {
|
|
4929
|
+
renderResult.destroy();
|
|
4930
|
+
},
|
|
4931
|
+
rootElement: renderResult.rootElement,
|
|
4932
|
+
container,
|
|
4933
|
+
data
|
|
4934
|
+
};
|
|
4935
|
+
}
|
|
4936
|
+
|
|
4937
|
+
// src/prefetch.ts
|
|
4938
|
+
async function prefetch(componentId, version = "latest", options = {}) {
|
|
4939
|
+
const { includeDependencies = true, ...fetchOptions } = options;
|
|
4940
|
+
try {
|
|
4941
|
+
if (includeDependencies) {
|
|
4942
|
+
await fetchComponentWithDependencies(componentId, { ...fetchOptions, version });
|
|
4943
|
+
} else {
|
|
4944
|
+
await fetchComponent(componentId, { ...fetchOptions, version });
|
|
4945
|
+
}
|
|
4946
|
+
return true;
|
|
4947
|
+
} catch (error) {
|
|
4948
|
+
console.warn(`[Servly] Failed to prefetch ${componentId}:`, error);
|
|
4949
|
+
return false;
|
|
4950
|
+
}
|
|
4951
|
+
}
|
|
4952
|
+
async function prefetchAll(components, options = {}) {
|
|
4953
|
+
const results = await Promise.allSettled(
|
|
4954
|
+
components.map((comp) => {
|
|
4955
|
+
const id = typeof comp === "string" ? comp : comp.id;
|
|
4956
|
+
const version = typeof comp === "string" ? "latest" : comp.version || "latest";
|
|
4957
|
+
return prefetch(id, version, options).then((success2) => ({ id, success: success2 }));
|
|
4958
|
+
})
|
|
4959
|
+
);
|
|
4960
|
+
const success = [];
|
|
4961
|
+
const failed = [];
|
|
4962
|
+
for (const result of results) {
|
|
4963
|
+
if (result.status === "fulfilled" && result.value.success) {
|
|
4964
|
+
success.push(result.value.id);
|
|
4965
|
+
} else {
|
|
4966
|
+
const id = result.status === "fulfilled" ? result.value.id : "unknown";
|
|
4967
|
+
failed.push(id);
|
|
4968
|
+
}
|
|
4969
|
+
}
|
|
4970
|
+
return { success, failed };
|
|
4971
|
+
}
|
|
4972
|
+
function prefetchOnVisible(target, components, options = {}) {
|
|
4973
|
+
const { rootMargin = "100px", threshold = 0, ...prefetchOptions } = options;
|
|
4974
|
+
const element = typeof target === "string" ? document.querySelector(target) : target;
|
|
4975
|
+
if (!element) {
|
|
4976
|
+
console.warn(`[Servly] prefetchOnVisible: Target not found: ${target}`);
|
|
4977
|
+
return () => {
|
|
4978
|
+
};
|
|
4979
|
+
}
|
|
4980
|
+
let hasPrefetched = false;
|
|
4981
|
+
const observer = new IntersectionObserver(
|
|
4982
|
+
(entries) => {
|
|
4983
|
+
for (const entry of entries) {
|
|
4984
|
+
if (entry.isIntersecting && !hasPrefetched) {
|
|
4985
|
+
hasPrefetched = true;
|
|
4986
|
+
prefetchAll(components, prefetchOptions);
|
|
4987
|
+
observer.disconnect();
|
|
4988
|
+
}
|
|
4989
|
+
}
|
|
4990
|
+
},
|
|
4991
|
+
{ rootMargin, threshold }
|
|
4992
|
+
);
|
|
4993
|
+
observer.observe(element);
|
|
4994
|
+
return () => observer.disconnect();
|
|
4995
|
+
}
|
|
4996
|
+
function prefetchOnIdle(components, options = {}) {
|
|
4997
|
+
const { timeout = 5e3, ...prefetchOptions } = options;
|
|
4998
|
+
const callback = () => {
|
|
4999
|
+
prefetchAll(components, prefetchOptions);
|
|
5000
|
+
};
|
|
5001
|
+
if ("requestIdleCallback" in window) {
|
|
5002
|
+
window.requestIdleCallback(callback, { timeout });
|
|
5003
|
+
} else {
|
|
5004
|
+
setTimeout(callback, 1);
|
|
5005
|
+
}
|
|
5006
|
+
}
|
|
5007
|
+
function prefetchOnHover(target, componentId, version = "latest", options = {}) {
|
|
5008
|
+
const { delay = 100, ...prefetchOptions } = options;
|
|
5009
|
+
const element = typeof target === "string" ? document.querySelector(target) : target;
|
|
5010
|
+
if (!element) {
|
|
5011
|
+
console.warn(`[Servly] prefetchOnHover: Target not found: ${target}`);
|
|
5012
|
+
return () => {
|
|
5013
|
+
};
|
|
5014
|
+
}
|
|
5015
|
+
let timeoutId = null;
|
|
5016
|
+
let hasPrefetched = false;
|
|
5017
|
+
const handleMouseEnter = () => {
|
|
5018
|
+
if (hasPrefetched) return;
|
|
5019
|
+
timeoutId = setTimeout(() => {
|
|
5020
|
+
hasPrefetched = true;
|
|
5021
|
+
prefetch(componentId, version, prefetchOptions);
|
|
5022
|
+
}, delay);
|
|
5023
|
+
};
|
|
5024
|
+
const handleMouseLeave = () => {
|
|
5025
|
+
if (timeoutId) {
|
|
5026
|
+
clearTimeout(timeoutId);
|
|
5027
|
+
timeoutId = null;
|
|
5028
|
+
}
|
|
5029
|
+
};
|
|
5030
|
+
element.addEventListener("mouseenter", handleMouseEnter);
|
|
5031
|
+
element.addEventListener("mouseleave", handleMouseLeave);
|
|
5032
|
+
return () => {
|
|
5033
|
+
element.removeEventListener("mouseenter", handleMouseEnter);
|
|
5034
|
+
element.removeEventListener("mouseleave", handleMouseLeave);
|
|
5035
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
5036
|
+
};
|
|
5037
|
+
}
|
|
5038
|
+
|
|
4482
5039
|
// src/version.ts
|
|
4483
5040
|
function parseVersion(version) {
|
|
4484
5041
|
const match = version.match(/^(\d+)\.(\d+)\.(\d+)$/);
|
|
@@ -4845,6 +5402,55 @@ function getSampleValue(def) {
|
|
|
4845
5402
|
return def.defaultValue;
|
|
4846
5403
|
}
|
|
4847
5404
|
}
|
|
5405
|
+
|
|
5406
|
+
// src/slotBindings.ts
|
|
5407
|
+
function extractSlotBindings(layout) {
|
|
5408
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
5409
|
+
const elementMap = /* @__PURE__ */ new Map();
|
|
5410
|
+
for (const element of layout) {
|
|
5411
|
+
elementMap.set(element.i, element);
|
|
5412
|
+
}
|
|
5413
|
+
const traceToProps = (element, propPath, visited = /* @__PURE__ */ new Set()) => {
|
|
5414
|
+
if (visited.has(element.i)) return null;
|
|
5415
|
+
visited.add(element.i);
|
|
5416
|
+
const inputs = element.configuration?.bindings?.inputs || {};
|
|
5417
|
+
const binding = inputs[propPath];
|
|
5418
|
+
if (!binding) return null;
|
|
5419
|
+
if (binding.source === "props" && binding.path) {
|
|
5420
|
+
return binding.path;
|
|
5421
|
+
}
|
|
5422
|
+
if (binding.source === "parent" && binding.path && element.parent) {
|
|
5423
|
+
const parentElement = elementMap.get(element.parent);
|
|
5424
|
+
if (parentElement) {
|
|
5425
|
+
return traceToProps(parentElement, binding.path, visited);
|
|
5426
|
+
}
|
|
5427
|
+
}
|
|
5428
|
+
return null;
|
|
5429
|
+
};
|
|
5430
|
+
for (const element of layout) {
|
|
5431
|
+
if (element.componentId !== "slot") continue;
|
|
5432
|
+
const inputs = element.configuration?.bindings?.inputs || {};
|
|
5433
|
+
const slotId = element.configuration?.slotName || element.i;
|
|
5434
|
+
for (const [targetProp, binding] of Object.entries(inputs)) {
|
|
5435
|
+
if (["child", "children", "content"].includes(targetProp)) {
|
|
5436
|
+
const b = binding;
|
|
5437
|
+
if (!b) continue;
|
|
5438
|
+
if (b.source === "props" && b.path) {
|
|
5439
|
+
bindings.set(b.path, slotId);
|
|
5440
|
+
} else if (b.source === "parent" && b.path && element.parent) {
|
|
5441
|
+
const parentElement = elementMap.get(element.parent);
|
|
5442
|
+
if (parentElement) {
|
|
5443
|
+
const originalProp = traceToProps(parentElement, b.path);
|
|
5444
|
+
if (originalProp) {
|
|
5445
|
+
bindings.set(originalProp, slotId);
|
|
5446
|
+
}
|
|
5447
|
+
}
|
|
5448
|
+
}
|
|
5449
|
+
}
|
|
5450
|
+
}
|
|
5451
|
+
}
|
|
5452
|
+
return bindings;
|
|
5453
|
+
}
|
|
4848
5454
|
export {
|
|
4849
5455
|
AnalyticsCollector,
|
|
4850
5456
|
DEFAULT_CACHE_CONFIG,
|
|
@@ -4893,6 +5499,7 @@ export {
|
|
|
4893
5499
|
extractDependenciesFromCode,
|
|
4894
5500
|
extractOverrideDependencies,
|
|
4895
5501
|
extractReferencedViewIds,
|
|
5502
|
+
extractSlotBindings,
|
|
4896
5503
|
fetchComponent,
|
|
4897
5504
|
fetchComponentWithDependencies,
|
|
4898
5505
|
formatStyleValue,
|
|
@@ -4946,9 +5553,16 @@ export {
|
|
|
4946
5553
|
loadFont,
|
|
4947
5554
|
loadFonts,
|
|
4948
5555
|
markElementReady,
|
|
5556
|
+
mount,
|
|
5557
|
+
mountData,
|
|
4949
5558
|
navigateTo,
|
|
4950
5559
|
parseVersion,
|
|
5560
|
+
prefetch,
|
|
5561
|
+
prefetchAll,
|
|
4951
5562
|
prefetchComponents,
|
|
5563
|
+
prefetchOnHover,
|
|
5564
|
+
prefetchOnIdle,
|
|
5565
|
+
prefetchOnVisible,
|
|
4952
5566
|
preloadIcons,
|
|
4953
5567
|
preloadTailwind,
|
|
4954
5568
|
preventFOUC,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@servlyadmin/runtime-core",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "Framework-agnostic core renderer for Servly components",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Framework-agnostic core renderer for Servly components with prefetching and loading states",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
7
7
|
"module": "./dist/index.js",
|
|
@@ -40,4 +40,4 @@
|
|
|
40
40
|
"vitest": "^1.0.0",
|
|
41
41
|
"fast-check": "^3.15.0"
|
|
42
42
|
}
|
|
43
|
-
}
|
|
43
|
+
}
|