@unsetsoft/ryunixjs 1.2.5-canary.12 → 1.2.5-canary.13
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/Ryunix.esm.js +580 -204
- package/dist/Ryunix.esm.js.map +1 -1
- package/dist/Ryunix.umd.js +593 -203
- package/dist/Ryunix.umd.js.map +1 -1
- package/dist/Ryunix.umd.min.js +1 -1
- package/dist/Ryunix.umd.min.js.map +1 -1
- package/package.json +2 -2
- package/types/index.d.ts +12 -0
package/dist/Ryunix.esm.js
CHANGED
|
@@ -426,6 +426,7 @@ const updateDom = (dom, prevProps = {}, nextProps = {}) => {
|
|
|
426
426
|
const el = dom;
|
|
427
427
|
const domEl = el;
|
|
428
428
|
const handlerMap = domEl._ryunixHandlers;
|
|
429
|
+
const elRecord = el;
|
|
429
430
|
Object.keys(prevProps)
|
|
430
431
|
.filter(isEvent)
|
|
431
432
|
.filter((key) => isGone(nextProps)(key) || isNew(prevProps, nextProps)(key))
|
|
@@ -460,7 +461,7 @@ const updateDom = (dom, prevProps = {}, nextProps = {}) => {
|
|
|
460
461
|
el.removeAttribute(attrName);
|
|
461
462
|
}
|
|
462
463
|
else {
|
|
463
|
-
|
|
464
|
+
elRecord[propKey] = '';
|
|
464
465
|
el.removeAttribute(propKey);
|
|
465
466
|
}
|
|
466
467
|
});
|
|
@@ -481,8 +482,8 @@ const updateDom = (dom, prevProps = {}, nextProps = {}) => {
|
|
|
481
482
|
}
|
|
482
483
|
else {
|
|
483
484
|
if (propKey === 'value' || propKey === 'checked') {
|
|
484
|
-
if (
|
|
485
|
-
|
|
485
|
+
if (elRecord[propKey] !== nextProps[propKey]) {
|
|
486
|
+
elRecord[propKey] = nextProps[propKey];
|
|
486
487
|
}
|
|
487
488
|
}
|
|
488
489
|
else {
|
|
@@ -490,15 +491,15 @@ const updateDom = (dom, prevProps = {}, nextProps = {}) => {
|
|
|
490
491
|
if (isSvgNode) {
|
|
491
492
|
const attrName = toSvgAttrName(propKey);
|
|
492
493
|
const svgValidated = checkAttributeUri(attrName, nextProps[propKey]);
|
|
493
|
-
el.setAttribute(attrName, svgValidated);
|
|
494
|
+
el.setAttribute(attrName, String(svgValidated));
|
|
494
495
|
}
|
|
495
496
|
else {
|
|
496
497
|
const attrVal = nextProps[propKey];
|
|
497
498
|
const safeValue = checkAttributeUri(propKey, attrVal);
|
|
498
|
-
|
|
499
|
+
elRecord[propKey] = safeValue;
|
|
499
500
|
if (typeof attrVal !== 'object' &&
|
|
500
501
|
typeof attrVal !== 'function') {
|
|
501
|
-
el.setAttribute(propKey, safeValue);
|
|
502
|
+
el.setAttribute(propKey, String(safeValue));
|
|
502
503
|
}
|
|
503
504
|
}
|
|
504
505
|
}
|
|
@@ -665,9 +666,7 @@ const runLayoutEffects = (fiber) => {
|
|
|
665
666
|
}
|
|
666
667
|
try {
|
|
667
668
|
const cleanup = hook.effect();
|
|
668
|
-
hook.cancel = is.function(cleanup)
|
|
669
|
-
? cleanup
|
|
670
|
-
: null;
|
|
669
|
+
hook.cancel = is.function(cleanup) ? cleanup : null;
|
|
671
670
|
}
|
|
672
671
|
catch (error) {
|
|
673
672
|
if (process.env.NODE_ENV !== 'production') {
|
|
@@ -699,9 +698,7 @@ const runNormalEffects = (fiber) => {
|
|
|
699
698
|
}
|
|
700
699
|
try {
|
|
701
700
|
const cleanup = hook.effect();
|
|
702
|
-
hook.cancel = is.function(cleanup)
|
|
703
|
-
? cleanup
|
|
704
|
-
: null;
|
|
701
|
+
hook.cancel = is.function(cleanup) ? cleanup : null;
|
|
705
702
|
}
|
|
706
703
|
catch (error) {
|
|
707
704
|
if (process.env.NODE_ENV !== 'production') {
|
|
@@ -717,6 +714,8 @@ function commitRoot() {
|
|
|
717
714
|
const state = getState();
|
|
718
715
|
state.deletions.forEach(commitWork);
|
|
719
716
|
const finishedWork = state.wipRoot;
|
|
717
|
+
if (!finishedWork)
|
|
718
|
+
return;
|
|
720
719
|
state.currentRoot = finishedWork;
|
|
721
720
|
if (state.isHydrating || state.hydrationFailed) {
|
|
722
721
|
if (process.env.NODE_ENV !== 'production' && process.env.RYUNIX_DEBUG) {
|
|
@@ -773,6 +772,8 @@ function commitWork(fiber) {
|
|
|
773
772
|
return;
|
|
774
773
|
}
|
|
775
774
|
const domParent = domParentFiber.dom;
|
|
775
|
+
if (!domParent)
|
|
776
|
+
return;
|
|
776
777
|
if (fiber.effectTag === EFFECT_TAGS.PLACEMENT) {
|
|
777
778
|
if (fiber.dom != null) {
|
|
778
779
|
if (process.env.NODE_ENV !== 'production' && process.env.RYUNIX_DEBUG) {
|
|
@@ -786,7 +787,7 @@ function commitWork(fiber) {
|
|
|
786
787
|
else if (fiber.effectTag === EFFECT_TAGS.UPDATE) {
|
|
787
788
|
cancelEffects(fiber);
|
|
788
789
|
if (fiber.dom != null) {
|
|
789
|
-
updateDom(fiber.dom, fiber.alternate
|
|
790
|
+
updateDom(fiber.dom, fiber.alternate?.props, fiber.props);
|
|
790
791
|
}
|
|
791
792
|
runLayoutEffects(fiber);
|
|
792
793
|
runNormalEffects(fiber);
|
|
@@ -835,7 +836,7 @@ const commitPortalWork = (fiber, portalContainer) => {
|
|
|
835
836
|
else if (fiber.effectTag === EFFECT_TAGS.UPDATE) {
|
|
836
837
|
cancelEffects(fiber);
|
|
837
838
|
if (fiber.dom != null) {
|
|
838
|
-
updateDom(fiber.dom, fiber.alternate
|
|
839
|
+
updateDom(fiber.dom, fiber.alternate?.props, fiber.props);
|
|
839
840
|
}
|
|
840
841
|
runLayoutEffects(fiber);
|
|
841
842
|
runNormalEffects(fiber);
|
|
@@ -888,6 +889,12 @@ const reconcileChildren = (wipFiber, elements) => {
|
|
|
888
889
|
let newFiber;
|
|
889
890
|
const sameType = matchedFiber && element.type === matchedFiber.type;
|
|
890
891
|
if (sameType && matchedFiber) {
|
|
892
|
+
const matchedType = matchedFiber.type;
|
|
893
|
+
const isErrorBoundary = typeof matchedFiber.type === 'function' &&
|
|
894
|
+
matchedType?.ryunix_type === 'RYUNIX_ERROR_BOUNDARY';
|
|
895
|
+
const preserveBoundaryError = isErrorBoundary &&
|
|
896
|
+
matchedFiber.stateError != null &&
|
|
897
|
+
matchedFiber.child == null;
|
|
891
898
|
newFiber = {
|
|
892
899
|
type: matchedFiber.type,
|
|
893
900
|
props: element.props,
|
|
@@ -896,7 +903,7 @@ const reconcileChildren = (wipFiber, elements) => {
|
|
|
896
903
|
alternate: matchedFiber,
|
|
897
904
|
effectTag: EFFECT_TAGS.UPDATE,
|
|
898
905
|
hooks: matchedFiber.hooks,
|
|
899
|
-
stateError: matchedFiber.stateError,
|
|
906
|
+
stateError: preserveBoundaryError ? matchedFiber.stateError : undefined,
|
|
900
907
|
key: element.key,
|
|
901
908
|
index,
|
|
902
909
|
};
|
|
@@ -964,8 +971,7 @@ const findNearestHydrationBoundary = (fiber) => {
|
|
|
964
971
|
const maybeTyped = type;
|
|
965
972
|
if (type &&
|
|
966
973
|
typeof type === 'function' &&
|
|
967
|
-
|
|
968
|
-
maybeTyped?.ryunix_type === 'RYUNIX_SERVER_BOUNDARY')) {
|
|
974
|
+
maybeTyped?.ryunix_type === 'RYUNIX_HYDRATION_BOUNDARY') {
|
|
969
975
|
return current;
|
|
970
976
|
}
|
|
971
977
|
current = current.parent || null;
|
|
@@ -1031,11 +1037,15 @@ const updateFunctionComponent = (fiber) => {
|
|
|
1031
1037
|
const state = getState();
|
|
1032
1038
|
state.wipFiber = fiber;
|
|
1033
1039
|
state.hookIndex = 0;
|
|
1034
|
-
|
|
1040
|
+
fiber.hooks = [];
|
|
1041
|
+
const componentType = fiber.type;
|
|
1042
|
+
if (state.hydrationRecover &&
|
|
1043
|
+
componentType.ryunix_type === 'RYUNIX_ERROR_BOUNDARY') {
|
|
1044
|
+
fiber.stateError = undefined;
|
|
1045
|
+
}
|
|
1035
1046
|
if (state.isHydrating) {
|
|
1036
1047
|
fiber.effectTag = EFFECT_TAGS.HYDRATE;
|
|
1037
1048
|
}
|
|
1038
|
-
const componentType = fiber.type;
|
|
1039
1049
|
if (componentType._isMemo && fiber.alternate) {
|
|
1040
1050
|
const { children: _pc, ...prevRest } = fiber.alternate.props || {};
|
|
1041
1051
|
const { children: _nc, ...nextRest } = fiber.props || {};
|
|
@@ -1049,7 +1059,7 @@ const updateFunctionComponent = (fiber) => {
|
|
|
1049
1059
|
return;
|
|
1050
1060
|
}
|
|
1051
1061
|
}
|
|
1052
|
-
|
|
1062
|
+
const children = [
|
|
1053
1063
|
componentType(fiber.props),
|
|
1054
1064
|
];
|
|
1055
1065
|
if (componentType._contextId && fiber.props?.value !== undefined) {
|
|
@@ -1067,11 +1077,24 @@ const isUnderClientOnlyBoundary = (fiber) => {
|
|
|
1067
1077
|
}
|
|
1068
1078
|
return false;
|
|
1069
1079
|
};
|
|
1080
|
+
const isUnderServerPreserveBoundary = (fiber) => {
|
|
1081
|
+
let current = fiber?.parent || null;
|
|
1082
|
+
while (current) {
|
|
1083
|
+
if (current._hydratePreserveServer)
|
|
1084
|
+
return true;
|
|
1085
|
+
current = current.parent || null;
|
|
1086
|
+
}
|
|
1087
|
+
return false;
|
|
1088
|
+
};
|
|
1089
|
+
const normalizeChildNodes = (children) => {
|
|
1090
|
+
if (children == null)
|
|
1091
|
+
return [];
|
|
1092
|
+
return Array.isArray(children) ? children : [children];
|
|
1093
|
+
};
|
|
1070
1094
|
const updateHostComponent = (fiber) => {
|
|
1071
1095
|
const state = getState();
|
|
1072
1096
|
if (fiber.type === RYUNIX_TYPES.RYUNIX_CONTEXT) {
|
|
1073
|
-
fiber._contextId =
|
|
1074
|
-
fiber.props?._contextId;
|
|
1097
|
+
fiber._contextId = fiber.props?._contextId;
|
|
1075
1098
|
fiber._contextValue = fiber.props?.value;
|
|
1076
1099
|
}
|
|
1077
1100
|
const isPassthrough = fiber.type === RYUNIX_TYPES.RYUNIX_FRAGMENT ||
|
|
@@ -1080,6 +1103,9 @@ const updateHostComponent = (fiber) => {
|
|
|
1080
1103
|
if (state.isHydrating && isPassthrough) {
|
|
1081
1104
|
fiber.effectTag = EFFECT_TAGS.HYDRATE;
|
|
1082
1105
|
}
|
|
1106
|
+
else if (state.isHydrating && isUnderServerPreserveBoundary(fiber)) {
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1083
1109
|
else if (state.isHydrating && isUnderClientOnlyBoundary(fiber)) {
|
|
1084
1110
|
if (!fiber.dom) {
|
|
1085
1111
|
fiber.dom = createDom(fiber);
|
|
@@ -1102,6 +1128,13 @@ const updateHostComponent = (fiber) => {
|
|
|
1102
1128
|
domNode.nodeValue = String(fiber.props.nodeValue);
|
|
1103
1129
|
logHydrationRecoverable('text');
|
|
1104
1130
|
}
|
|
1131
|
+
if (isElement &&
|
|
1132
|
+
domNode.hasAttribute('data-ryunix-server')) {
|
|
1133
|
+
fiber._hydratePreserveServer = true;
|
|
1134
|
+
state.hydrateCursor = nextValidSibling$1(domNode);
|
|
1135
|
+
reconcileChildren(fiber, []);
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1105
1138
|
if (isElement &&
|
|
1106
1139
|
domNode.hasAttribute('data-ryunix-hydrate-boundary')) {
|
|
1107
1140
|
fiber._hydrateClientOnly = true;
|
|
@@ -1110,7 +1143,7 @@ const updateHostComponent = (fiber) => {
|
|
|
1110
1143
|
}
|
|
1111
1144
|
else {
|
|
1112
1145
|
const policy = getHydrationPolicy();
|
|
1113
|
-
const detail = `Mismatch at ${getTypeLabel(fiber.type)}. Expected ${domNode.nodeType === 1 ? domNode.tagName : 'text'} but got ${String(fiber.type)}.`;
|
|
1146
|
+
const detail = `Mismatch at ${getTypeLabel(fiber.type ?? 'unknown')}. Expected ${domNode.nodeType === 1 ? domNode.tagName : 'text'} but got ${String(fiber.type)}.`;
|
|
1114
1147
|
const boundaryFiber = findNearestHydrationBoundary(fiber);
|
|
1115
1148
|
const boundaryDom = (boundaryFiber ? getBoundaryDom(boundaryFiber) : null) ??
|
|
1116
1149
|
findBoundaryDomFromNode(state.hydrateCursor);
|
|
@@ -1142,7 +1175,10 @@ const updateHostComponent = (fiber) => {
|
|
|
1142
1175
|
fiber.dom = createDom(fiber);
|
|
1143
1176
|
}
|
|
1144
1177
|
}
|
|
1145
|
-
|
|
1178
|
+
if (fiber._hydratePreserveServer) {
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
const children = normalizeChildNodes(fiber.props?.children);
|
|
1146
1182
|
reconcileChildren(fiber, children);
|
|
1147
1183
|
};
|
|
1148
1184
|
const getTypeLabel = (type) => {
|
|
@@ -1166,6 +1202,11 @@ const scheduleWork$1 = (root, priority) => {
|
|
|
1166
1202
|
}
|
|
1167
1203
|
};
|
|
1168
1204
|
|
|
1205
|
+
const getRootChild = (children) => {
|
|
1206
|
+
if (children == null)
|
|
1207
|
+
return undefined;
|
|
1208
|
+
return Array.isArray(children) ? children[0] : children;
|
|
1209
|
+
};
|
|
1169
1210
|
const renderSubtree = (element, container) => {
|
|
1170
1211
|
clearContainer(container);
|
|
1171
1212
|
const root = {
|
|
@@ -1195,7 +1236,7 @@ const recoverHydrationFailureIfNeeded = () => {
|
|
|
1195
1236
|
if (policy.recover === 'none')
|
|
1196
1237
|
return;
|
|
1197
1238
|
const container = state.containerRoot || state.currentRoot?.dom;
|
|
1198
|
-
const element = state.currentRoot?.props?.children
|
|
1239
|
+
const element = getRootChild(state.currentRoot?.props?.children);
|
|
1199
1240
|
if (!container || element == null)
|
|
1200
1241
|
return;
|
|
1201
1242
|
state.hydrationRecover = true;
|
|
@@ -1205,8 +1246,10 @@ const recoverHydrationFailureIfNeeded = () => {
|
|
|
1205
1246
|
renderSubtree(element, container);
|
|
1206
1247
|
};
|
|
1207
1248
|
const runHydrationRecovery = () => {
|
|
1249
|
+
const state = getState();
|
|
1208
1250
|
recoverScopedHydrationFailures();
|
|
1209
1251
|
recoverHydrationFailureIfNeeded();
|
|
1252
|
+
state.hydrationRecover = false;
|
|
1210
1253
|
};
|
|
1211
1254
|
|
|
1212
1255
|
let workQueue = [];
|
|
@@ -1226,32 +1269,36 @@ function performUnitOfWork(fiber) {
|
|
|
1226
1269
|
if (process.env.NODE_ENV !== 'production') {
|
|
1227
1270
|
console.error('[Ryunix ErrorBoundary] Caught error during render:', error);
|
|
1228
1271
|
try {
|
|
1229
|
-
const
|
|
1272
|
+
const fiberProps = fiber.props;
|
|
1273
|
+
const src = fiberProps?.__source;
|
|
1230
1274
|
if (src && error && typeof error === 'object') {
|
|
1275
|
+
;
|
|
1231
1276
|
error.__ryunix_source = src;
|
|
1232
1277
|
}
|
|
1233
1278
|
let targetFiber = fiber;
|
|
1234
1279
|
while (!error.__ryunix_source && targetFiber) {
|
|
1235
|
-
|
|
1236
|
-
|
|
1280
|
+
const targetProps = targetFiber.props;
|
|
1281
|
+
if (targetProps?.__source) {
|
|
1282
|
+
;
|
|
1283
|
+
error.__ryunix_source = targetProps.__source;
|
|
1237
1284
|
}
|
|
1238
1285
|
targetFiber = targetFiber.parent;
|
|
1239
1286
|
}
|
|
1240
1287
|
}
|
|
1241
|
-
catch (
|
|
1288
|
+
catch (_e) { }
|
|
1242
1289
|
}
|
|
1243
1290
|
let boundaryFiber = fiber.parent;
|
|
1244
1291
|
let foundBoundary = false;
|
|
1245
1292
|
while (boundaryFiber) {
|
|
1246
1293
|
if (boundaryFiber.type &&
|
|
1247
|
-
boundaryFiber.type
|
|
1248
|
-
|
|
1294
|
+
boundaryFiber.type.ryunix_type ===
|
|
1295
|
+
'RYUNIX_ERROR_BOUNDARY') {
|
|
1249
1296
|
foundBoundary = true;
|
|
1250
1297
|
break;
|
|
1251
1298
|
}
|
|
1252
1299
|
boundaryFiber = boundaryFiber.parent;
|
|
1253
1300
|
}
|
|
1254
|
-
if (foundBoundary) {
|
|
1301
|
+
if (foundBoundary && boundaryFiber) {
|
|
1255
1302
|
if (process.env.NODE_ENV !== 'production') {
|
|
1256
1303
|
console.warn('[Ryunix ErrorBoundary] Recovering tree at nearest boundary.');
|
|
1257
1304
|
}
|
|
@@ -1278,6 +1325,7 @@ function performUnitOfWork(fiber) {
|
|
|
1278
1325
|
}
|
|
1279
1326
|
nextFiber = nextFiber.parent;
|
|
1280
1327
|
}
|
|
1328
|
+
return null;
|
|
1281
1329
|
}
|
|
1282
1330
|
const workLoop = (deadline) => {
|
|
1283
1331
|
const state = getState();
|
|
@@ -1285,6 +1333,8 @@ const workLoop = (deadline) => {
|
|
|
1285
1333
|
while ((state.nextUnitOfWork || workQueue.length > 0) && !shouldYield) {
|
|
1286
1334
|
if (!state.nextUnitOfWork && workQueue.length > 0) {
|
|
1287
1335
|
const nextRoot = workQueue.shift();
|
|
1336
|
+
if (!nextRoot)
|
|
1337
|
+
continue;
|
|
1288
1338
|
state.wipRoot = nextRoot;
|
|
1289
1339
|
state.nextUnitOfWork = nextRoot;
|
|
1290
1340
|
state.deletions = [];
|
|
@@ -1345,9 +1395,7 @@ const render = (element, container) => {
|
|
|
1345
1395
|
const root = {
|
|
1346
1396
|
dom: container,
|
|
1347
1397
|
props: {
|
|
1348
|
-
children: [
|
|
1349
|
-
element,
|
|
1350
|
-
],
|
|
1398
|
+
children: [element],
|
|
1351
1399
|
},
|
|
1352
1400
|
alternate: state.currentRoot,
|
|
1353
1401
|
isHydrating: false,
|
|
@@ -1360,7 +1408,7 @@ const SSR_ROOT_ATTR = 'data-ryunix-ssr-root';
|
|
|
1360
1408
|
const nextValidSibling = (node) => {
|
|
1361
1409
|
let next = node;
|
|
1362
1410
|
while (next &&
|
|
1363
|
-
((next.nodeType === 3 && !next.nodeValue
|
|
1411
|
+
((next.nodeType === 3 && !next.nodeValue?.trim()) ||
|
|
1364
1412
|
next.nodeType === 8 ||
|
|
1365
1413
|
(next.nodeType === 1 &&
|
|
1366
1414
|
next.hasAttribute('data-ryunix-ssr')))) {
|
|
@@ -1374,9 +1422,7 @@ const hydrate = (element, container) => {
|
|
|
1374
1422
|
const root = {
|
|
1375
1423
|
dom: container,
|
|
1376
1424
|
props: {
|
|
1377
|
-
children: [
|
|
1378
|
-
element,
|
|
1379
|
-
],
|
|
1425
|
+
children: [element],
|
|
1380
1426
|
},
|
|
1381
1427
|
alternate: state.currentRoot,
|
|
1382
1428
|
isHydrating: true,
|
|
@@ -1385,7 +1431,7 @@ const hydrate = (element, container) => {
|
|
|
1385
1431
|
scheduleWork(root);
|
|
1386
1432
|
return root;
|
|
1387
1433
|
};
|
|
1388
|
-
const init = (MainElement, root = '__ryunix',
|
|
1434
|
+
const init = (MainElement, root = '__ryunix', _components = {}) => {
|
|
1389
1435
|
const state = getState();
|
|
1390
1436
|
const container = document.getElementById(root);
|
|
1391
1437
|
state.containerRoot = container;
|
|
@@ -1482,6 +1528,82 @@ const validateHookContext = (hookName = 'A hook') => {
|
|
|
1482
1528
|
}
|
|
1483
1529
|
};
|
|
1484
1530
|
|
|
1531
|
+
const INTERNAL_META_KEYS = new Set([
|
|
1532
|
+
'title',
|
|
1533
|
+
'pageTitle',
|
|
1534
|
+
'canonical',
|
|
1535
|
+
'titleTemplate',
|
|
1536
|
+
'titleDefault',
|
|
1537
|
+
'lastmod',
|
|
1538
|
+
'changefreq',
|
|
1539
|
+
'priority',
|
|
1540
|
+
'custom',
|
|
1541
|
+
'icon',
|
|
1542
|
+
'appleTouchIcon',
|
|
1543
|
+
]);
|
|
1544
|
+
const isTitleConfig = (value) => is.object(value) &&
|
|
1545
|
+
value !== null &&
|
|
1546
|
+
('default' in value || 'template' in value);
|
|
1547
|
+
const pickString = (value) => typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
1548
|
+
function resolvePageMetadata(meta = {}, options = {}) {
|
|
1549
|
+
const template = pickString(options.title?.template) ||
|
|
1550
|
+
pickString(meta.titleTemplate) ||
|
|
1551
|
+
(isTitleConfig(meta.title) ? pickString(meta.title.template) : undefined);
|
|
1552
|
+
const defaultTitle = pickString(options.title?.prefix) ||
|
|
1553
|
+
pickString(meta.titleDefault) ||
|
|
1554
|
+
(isTitleConfig(meta.title) ? pickString(meta.title.default) : undefined) ||
|
|
1555
|
+
'Ryunix App';
|
|
1556
|
+
const pageTitle = pickString(meta.pageTitle) ||
|
|
1557
|
+
(typeof meta.title === 'string' ? pickString(meta.title) : undefined);
|
|
1558
|
+
let title = defaultTitle;
|
|
1559
|
+
if (pageTitle) {
|
|
1560
|
+
title =
|
|
1561
|
+
template && template.includes('%s')
|
|
1562
|
+
? template.replace('%s', pageTitle)
|
|
1563
|
+
: pageTitle;
|
|
1564
|
+
}
|
|
1565
|
+
const tags = {};
|
|
1566
|
+
for (const [key, value] of Object.entries(meta)) {
|
|
1567
|
+
if (INTERNAL_META_KEYS.has(key))
|
|
1568
|
+
continue;
|
|
1569
|
+
if (key === 'title' && isTitleConfig(value))
|
|
1570
|
+
continue;
|
|
1571
|
+
if (Array.isArray(value)) {
|
|
1572
|
+
const items = value
|
|
1573
|
+
.map((item) => (typeof item === 'string' ? item.trim() : ''))
|
|
1574
|
+
.filter(Boolean);
|
|
1575
|
+
if (items.length > 0)
|
|
1576
|
+
tags[key] = items;
|
|
1577
|
+
continue;
|
|
1578
|
+
}
|
|
1579
|
+
if (typeof value === 'string' && value.trim()) {
|
|
1580
|
+
tags[key] = value.trim();
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
if (pickString(meta.canonical)) {
|
|
1584
|
+
tags.canonical = meta.canonical;
|
|
1585
|
+
}
|
|
1586
|
+
if (pickString(meta.icon)) {
|
|
1587
|
+
tags.icon = meta.icon;
|
|
1588
|
+
}
|
|
1589
|
+
if (pickString(meta.appleTouchIcon)) {
|
|
1590
|
+
tags.appleTouchIcon = meta.appleTouchIcon;
|
|
1591
|
+
}
|
|
1592
|
+
return { title, tags };
|
|
1593
|
+
}
|
|
1594
|
+
function mergeRouteMetadata(base = {}, next = {}) {
|
|
1595
|
+
const merged = { ...base, ...next };
|
|
1596
|
+
if (typeof next.title === 'string' && isTitleConfig(base.title)) {
|
|
1597
|
+
if (!pickString(merged.titleTemplate) && pickString(base.title.template)) {
|
|
1598
|
+
merged.titleTemplate = base.title.template;
|
|
1599
|
+
}
|
|
1600
|
+
if (!pickString(merged.titleDefault) && pickString(base.title.default)) {
|
|
1601
|
+
merged.titleDefault = base.title.default;
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
return merged;
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1485
1607
|
const haveDepsChanged = (oldDeps, newDeps) => {
|
|
1486
1608
|
if (!oldDeps || !newDeps)
|
|
1487
1609
|
return true;
|
|
@@ -1521,6 +1643,8 @@ const useReducer = (reducer, initialState, init, defaultPriority = getCurrentPri
|
|
|
1521
1643
|
validateHookContext();
|
|
1522
1644
|
const { hookIndex } = state;
|
|
1523
1645
|
const wipFiber = state.wipFiber;
|
|
1646
|
+
if (!wipFiber.hooks)
|
|
1647
|
+
wipFiber.hooks = [];
|
|
1524
1648
|
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
1525
1649
|
const hook = {
|
|
1526
1650
|
hookID: hookIndex,
|
|
@@ -1560,8 +1684,7 @@ const useReducer = (reducer, initialState, init, defaultPriority = getCurrentPri
|
|
|
1560
1684
|
};
|
|
1561
1685
|
queueUpdate(() => scheduleWork$1(newRoot, priority));
|
|
1562
1686
|
};
|
|
1563
|
-
wipFiber.hooks[hookIndex] =
|
|
1564
|
-
hook;
|
|
1687
|
+
wipFiber.hooks[hookIndex] = hook;
|
|
1565
1688
|
state.hookIndex++;
|
|
1566
1689
|
return [hook.state, dispatch];
|
|
1567
1690
|
};
|
|
@@ -1582,6 +1705,8 @@ const useEffect = (callback, deps) => {
|
|
|
1582
1705
|
}
|
|
1583
1706
|
const { hookIndex } = state;
|
|
1584
1707
|
const wipFiber = state.wipFiber;
|
|
1708
|
+
if (!wipFiber.hooks)
|
|
1709
|
+
wipFiber.hooks = [];
|
|
1585
1710
|
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
1586
1711
|
const hasChanged = haveDepsChanged(oldHook?.deps, deps);
|
|
1587
1712
|
const hook = {
|
|
@@ -1591,8 +1716,7 @@ const useEffect = (callback, deps) => {
|
|
|
1591
1716
|
effect: hasChanged ? callback : null,
|
|
1592
1717
|
cancel: oldHook?.cancel,
|
|
1593
1718
|
};
|
|
1594
|
-
wipFiber.hooks[hookIndex] =
|
|
1595
|
-
hook;
|
|
1719
|
+
wipFiber.hooks[hookIndex] = hook;
|
|
1596
1720
|
state.hookIndex++;
|
|
1597
1721
|
};
|
|
1598
1722
|
const useRef = (initialValue) => {
|
|
@@ -1606,6 +1730,8 @@ const useRef = (initialValue) => {
|
|
|
1606
1730
|
validateHookContext();
|
|
1607
1731
|
const { hookIndex } = state;
|
|
1608
1732
|
const wipFiber = state.wipFiber;
|
|
1733
|
+
if (!wipFiber.hooks)
|
|
1734
|
+
wipFiber.hooks = [];
|
|
1609
1735
|
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
1610
1736
|
const hook = {
|
|
1611
1737
|
hookID: hookIndex,
|
|
@@ -1614,8 +1740,7 @@ const useRef = (initialValue) => {
|
|
|
1614
1740
|
? oldHook.value
|
|
1615
1741
|
: { current: initialValue },
|
|
1616
1742
|
};
|
|
1617
|
-
wipFiber.hooks[hookIndex] =
|
|
1618
|
-
hook;
|
|
1743
|
+
wipFiber.hooks[hookIndex] = hook;
|
|
1619
1744
|
state.hookIndex++;
|
|
1620
1745
|
return hook.value;
|
|
1621
1746
|
};
|
|
@@ -1636,6 +1761,8 @@ const useMemo = (compute, deps) => {
|
|
|
1636
1761
|
}
|
|
1637
1762
|
const { hookIndex } = state;
|
|
1638
1763
|
const wipFiber = state.wipFiber;
|
|
1764
|
+
if (!wipFiber.hooks)
|
|
1765
|
+
wipFiber.hooks = [];
|
|
1639
1766
|
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
1640
1767
|
let value;
|
|
1641
1768
|
if (oldHook && !haveDepsChanged(oldHook.deps, deps)) {
|
|
@@ -1658,8 +1785,7 @@ const useMemo = (compute, deps) => {
|
|
|
1658
1785
|
value,
|
|
1659
1786
|
deps,
|
|
1660
1787
|
};
|
|
1661
|
-
wipFiber.hooks[hookIndex] =
|
|
1662
|
-
hook;
|
|
1788
|
+
wipFiber.hooks[hookIndex] = hook;
|
|
1663
1789
|
state.hookIndex++;
|
|
1664
1790
|
return value;
|
|
1665
1791
|
};
|
|
@@ -1670,7 +1796,7 @@ const useCallback = (callback, deps) => {
|
|
|
1670
1796
|
return useMemo(() => callback, deps);
|
|
1671
1797
|
};
|
|
1672
1798
|
const createContext = (contextId = RYUNIX_TYPES.RYUNIX_CONTEXT, defaultValue = {}) => {
|
|
1673
|
-
const Provider = ({ value, children }) => {
|
|
1799
|
+
const Provider = ({ value, children, }) => {
|
|
1674
1800
|
return createElement(RYUNIX_TYPES.RYUNIX_CONTEXT, { value, children, _contextId: contextId }, ...flattenArray([children]));
|
|
1675
1801
|
};
|
|
1676
1802
|
Provider._contextId = contextId;
|
|
@@ -1725,7 +1851,7 @@ const useHash = () => {
|
|
|
1725
1851
|
const useMetadata = (tags = {}, options = {}) => {
|
|
1726
1852
|
const state = getState();
|
|
1727
1853
|
if (state.isServerRendering) {
|
|
1728
|
-
state.ssrMetadata =
|
|
1854
|
+
state.ssrMetadata = mergeRouteMetadata((state.ssrMetadata || {}), tags);
|
|
1729
1855
|
return;
|
|
1730
1856
|
}
|
|
1731
1857
|
useEffect(() => {
|
|
@@ -1756,6 +1882,8 @@ const useMetadata = (tags = {}, options = {}) => {
|
|
|
1756
1882
|
Object.entries(tags).forEach(([key, value]) => {
|
|
1757
1883
|
if (['title', 'pageTitle', 'canonical'].includes(key))
|
|
1758
1884
|
return;
|
|
1885
|
+
if (value == null)
|
|
1886
|
+
return;
|
|
1759
1887
|
const isProperty = key.startsWith('og:') || key.startsWith('twitter:');
|
|
1760
1888
|
const selector = `meta[${isProperty ? 'property' : 'name'}='${key}']`;
|
|
1761
1889
|
let meta = document.head.querySelector(selector);
|
|
@@ -1779,7 +1907,7 @@ const findRoute = (routes, path) => {
|
|
|
1779
1907
|
const pathname = path.split('?')[0].split('#')[0];
|
|
1780
1908
|
const notFoundRoute = routes.find((route) => route.NotFound);
|
|
1781
1909
|
const notFound = notFoundRoute
|
|
1782
|
-
? { route: { component: notFoundRoute.NotFound }, params: {} }
|
|
1910
|
+
? { route: { component: notFoundRoute.NotFound ?? null }, params: {} }
|
|
1783
1911
|
: { route: { component: null }, params: {} };
|
|
1784
1912
|
for (const route of routes) {
|
|
1785
1913
|
if (route.subRoutes) {
|
|
@@ -1815,7 +1943,7 @@ const getSsrPathname = () => {
|
|
|
1815
1943
|
}
|
|
1816
1944
|
return '/';
|
|
1817
1945
|
};
|
|
1818
|
-
const RouterProvider = ({ routes, children }) => {
|
|
1946
|
+
const RouterProvider = ({ routes, children, }) => {
|
|
1819
1947
|
if (typeof window === 'undefined') {
|
|
1820
1948
|
const location = getSsrPathname();
|
|
1821
1949
|
const currentRouteData = findRoute(routes, location);
|
|
@@ -1849,7 +1977,7 @@ const RouterProvider = ({ routes, children }) => {
|
|
|
1849
1977
|
const currentRouteData = findRoute(routes, location);
|
|
1850
1978
|
const query = useQuery();
|
|
1851
1979
|
const contextValue = {
|
|
1852
|
-
location,
|
|
1980
|
+
location: location,
|
|
1853
1981
|
params: currentRouteData.params || {},
|
|
1854
1982
|
query,
|
|
1855
1983
|
navigate,
|
|
@@ -1871,10 +1999,13 @@ const Children = () => {
|
|
|
1871
1999
|
const el = document.getElementById(id);
|
|
1872
2000
|
if (el)
|
|
1873
2001
|
el.scrollIntoView({ block: 'start', behavior: 'smooth' });
|
|
2002
|
+
return;
|
|
2003
|
+
}
|
|
2004
|
+
if (typeof window !== 'undefined') {
|
|
2005
|
+
window.scrollTo(0, 0);
|
|
1874
2006
|
}
|
|
1875
|
-
}, [hash]);
|
|
2007
|
+
}, [location, hash]);
|
|
1876
2008
|
return createElement(route.component, {
|
|
1877
|
-
key: location,
|
|
1878
2009
|
params,
|
|
1879
2010
|
query,
|
|
1880
2011
|
hash,
|
|
@@ -1905,14 +2036,14 @@ const Link = ({ to, prefetch = true, ...props }) => {
|
|
|
1905
2036
|
href: to,
|
|
1906
2037
|
onClick: handleClick,
|
|
1907
2038
|
onMouseEnter: handleMouseEnter,
|
|
1908
|
-
className: props.className || props['ryunix-class'],
|
|
2039
|
+
className: (props.className || props['ryunix-class']),
|
|
1909
2040
|
...cleanedProps,
|
|
1910
2041
|
}, props.children);
|
|
1911
2042
|
};
|
|
1912
2043
|
const NavLink = ({ to, exact = false, ...props }) => {
|
|
1913
2044
|
const { location, navigate } = useRouter();
|
|
1914
2045
|
const isActive = exact ? location === to : location.startsWith(to);
|
|
1915
|
-
const resolveClass = (cls) => typeof cls === 'function' ? cls({ isActive }) : cls || '';
|
|
2046
|
+
const resolveClass = (cls) => (typeof cls === 'function' ? cls({ isActive }) : cls || '');
|
|
1916
2047
|
const handleClick = (e) => {
|
|
1917
2048
|
if (e.button !== 0 || e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) {
|
|
1918
2049
|
return;
|
|
@@ -1921,7 +2052,7 @@ const NavLink = ({ to, exact = false, ...props }) => {
|
|
|
1921
2052
|
navigate(to);
|
|
1922
2053
|
};
|
|
1923
2054
|
const classAttrName = props['ryunix-class'] ? 'ryunix-class' : 'className';
|
|
1924
|
-
const classAttrValue = resolveClass(props['ryunix-class'] || props.className);
|
|
2055
|
+
const classAttrValue = resolveClass((props['ryunix-class'] || props.className));
|
|
1925
2056
|
const { ['ryunix-class']: _omitRyunix, className: _omitClassName, ...cleanedProps } = props;
|
|
1926
2057
|
return createElement('a', {
|
|
1927
2058
|
href: to,
|
|
@@ -1973,7 +2104,7 @@ const usePersistentStore = (key, initialState = '') => {
|
|
|
1973
2104
|
const item = window.localStorage.getItem(key);
|
|
1974
2105
|
return item ? JSON.parse(item) : initialState;
|
|
1975
2106
|
}
|
|
1976
|
-
catch (
|
|
2107
|
+
catch (_error) {
|
|
1977
2108
|
return initialState;
|
|
1978
2109
|
}
|
|
1979
2110
|
});
|
|
@@ -2012,6 +2143,8 @@ const useLayoutEffect = (callback, deps) => {
|
|
|
2012
2143
|
}
|
|
2013
2144
|
const { hookIndex } = state;
|
|
2014
2145
|
const wipFiber = state.wipFiber;
|
|
2146
|
+
if (!wipFiber.hooks)
|
|
2147
|
+
wipFiber.hooks = [];
|
|
2015
2148
|
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
2016
2149
|
const hasChanged = haveDepsChanged(oldHook?.deps, deps);
|
|
2017
2150
|
const hook = {
|
|
@@ -2022,8 +2155,7 @@ const useLayoutEffect = (callback, deps) => {
|
|
|
2022
2155
|
cancel: oldHook?.cancel,
|
|
2023
2156
|
isLayout: true,
|
|
2024
2157
|
};
|
|
2025
|
-
wipFiber.hooks[hookIndex] =
|
|
2026
|
-
hook;
|
|
2158
|
+
wipFiber.hooks[hookIndex] = hook;
|
|
2027
2159
|
state.hookIndex++;
|
|
2028
2160
|
};
|
|
2029
2161
|
let idCounter = 0;
|
|
@@ -2038,16 +2170,15 @@ const useId = () => {
|
|
|
2038
2170
|
validateHookContext();
|
|
2039
2171
|
const { hookIndex } = state;
|
|
2040
2172
|
const wipFiber = state.wipFiber;
|
|
2173
|
+
if (!wipFiber.hooks)
|
|
2174
|
+
wipFiber.hooks = [];
|
|
2041
2175
|
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
2042
2176
|
const hook = {
|
|
2043
2177
|
hookID: hookIndex,
|
|
2044
2178
|
type: RYUNIX_TYPES.RYUNIX_REF,
|
|
2045
|
-
value: oldHook
|
|
2046
|
-
? oldHook.value
|
|
2047
|
-
: `:r${idCounter++}:`,
|
|
2179
|
+
value: oldHook ? oldHook.value : `:r${idCounter++}:`,
|
|
2048
2180
|
};
|
|
2049
|
-
wipFiber.hooks[hookIndex] =
|
|
2050
|
-
hook;
|
|
2181
|
+
wipFiber.hooks[hookIndex] = hook;
|
|
2051
2182
|
state.hookIndex++;
|
|
2052
2183
|
return hook.value;
|
|
2053
2184
|
};
|
|
@@ -2082,6 +2213,7 @@ const useThrottle = (value, interval = 300) => {
|
|
|
2082
2213
|
return throttledValue;
|
|
2083
2214
|
};
|
|
2084
2215
|
|
|
2216
|
+
const ASYNC_RENDER_ERROR = 'Async components require renderToStringAsync or renderToReadableStream, not renderToString';
|
|
2085
2217
|
const escapeHtml = (unsafe) => {
|
|
2086
2218
|
if (typeof unsafe !== 'string')
|
|
2087
2219
|
return String(unsafe);
|
|
@@ -2116,6 +2248,83 @@ const VOID_ELEMENTS = new Set([
|
|
|
2116
2248
|
'track',
|
|
2117
2249
|
'wbr',
|
|
2118
2250
|
]);
|
|
2251
|
+
const normalizeChildren = (children) => {
|
|
2252
|
+
if (children == null)
|
|
2253
|
+
return [];
|
|
2254
|
+
return Array.isArray(children) ? children : [children];
|
|
2255
|
+
};
|
|
2256
|
+
const assertSyncRenderResult = (rendered) => {
|
|
2257
|
+
if (rendered != null &&
|
|
2258
|
+
typeof rendered === 'object' &&
|
|
2259
|
+
'then' in rendered &&
|
|
2260
|
+
typeof rendered.then === 'function') {
|
|
2261
|
+
throw new Error(ASYNC_RENDER_ERROR);
|
|
2262
|
+
}
|
|
2263
|
+
return rendered;
|
|
2264
|
+
};
|
|
2265
|
+
const buildHostProps = (props, renderChild) => {
|
|
2266
|
+
let attributes = '';
|
|
2267
|
+
let htmlChildren = '';
|
|
2268
|
+
let innerHTML = null;
|
|
2269
|
+
Object.entries(props).forEach(([key, value]) => {
|
|
2270
|
+
if (key === 'children') {
|
|
2271
|
+
if (Array.isArray(value)) {
|
|
2272
|
+
htmlChildren = value
|
|
2273
|
+
.map((child) => renderChild(child))
|
|
2274
|
+
.join('');
|
|
2275
|
+
}
|
|
2276
|
+
else {
|
|
2277
|
+
htmlChildren = renderChild(value);
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
else if (key === 'dangerouslySetInnerHTML') {
|
|
2281
|
+
const inner = value;
|
|
2282
|
+
if (inner?.__html) {
|
|
2283
|
+
innerHTML = inner.__html;
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
else if (key === STRINGS.STYLE || key === OLD_STRINGS.STYLE) {
|
|
2287
|
+
const styleString = renderStyle(value);
|
|
2288
|
+
if (styleString) {
|
|
2289
|
+
attributes += ` style="${escapeHtml(styleString)}"`;
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
else if (key === STRINGS.CLASS_NAME || key === OLD_STRINGS.CLASS_NAME) {
|
|
2293
|
+
if (value) {
|
|
2294
|
+
attributes += ` class="${escapeHtml(value)}"`;
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
else if (!key.startsWith('on') &&
|
|
2298
|
+
key !== 'key' &&
|
|
2299
|
+
key !== 'ref' &&
|
|
2300
|
+
key !== '__source' &&
|
|
2301
|
+
key !== '__self') {
|
|
2302
|
+
if (typeof value === 'boolean') {
|
|
2303
|
+
if (value)
|
|
2304
|
+
attributes += ` ${key}=""`;
|
|
2305
|
+
}
|
|
2306
|
+
else if (value != null) {
|
|
2307
|
+
const attrName = toSvgAttrName(key);
|
|
2308
|
+
const validatedValue = validateUri(attrName, value);
|
|
2309
|
+
attributes += ` ${attrName}="${escapeHtml(validatedValue)}"`;
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
});
|
|
2313
|
+
return { attributes, innerHTML, htmlChildren };
|
|
2314
|
+
};
|
|
2315
|
+
const formatHostOpenTag = (hostTag, attributes) => {
|
|
2316
|
+
if (VOID_ELEMENTS.has(hostTag)) {
|
|
2317
|
+
return `<${hostTag}${attributes} />`;
|
|
2318
|
+
}
|
|
2319
|
+
return `<${hostTag}${attributes}>`;
|
|
2320
|
+
};
|
|
2321
|
+
const beginSsrRender = () => {
|
|
2322
|
+
const state = getState();
|
|
2323
|
+
state.isServerRendering = true;
|
|
2324
|
+
state.ssrMetadata = {};
|
|
2325
|
+
state.ssrContexts = {};
|
|
2326
|
+
resetIdCounter();
|
|
2327
|
+
};
|
|
2119
2328
|
const renderToStringImpl = (element) => {
|
|
2120
2329
|
if (element == null || typeof element === 'boolean') {
|
|
2121
2330
|
return '';
|
|
@@ -2128,20 +2337,18 @@ const renderToStringImpl = (element) => {
|
|
|
2128
2337
|
}
|
|
2129
2338
|
const vnode = element;
|
|
2130
2339
|
if (vnode.type === RYUNIX_TYPES.TEXT_ELEMENT) {
|
|
2131
|
-
return escapeHtml(vnode.props
|
|
2132
|
-
.nodeValue);
|
|
2340
|
+
return escapeHtml(vnode.props.nodeValue);
|
|
2133
2341
|
}
|
|
2134
2342
|
if (vnode.type === RYUNIX_TYPES.RYUNIX_FRAGMENT) {
|
|
2135
|
-
const children = vnode.props?.children
|
|
2343
|
+
const children = normalizeChildren(vnode.props?.children);
|
|
2136
2344
|
return children.map((child) => renderToStringImpl(child)).join('');
|
|
2137
2345
|
}
|
|
2138
2346
|
if (vnode.type === RYUNIX_TYPES.RYUNIX_CONTEXT) {
|
|
2139
2347
|
const state = getState();
|
|
2140
2348
|
state.ssrContexts = state.ssrContexts || {};
|
|
2141
|
-
const ctxProps = vnode.props ||
|
|
2142
|
-
{};
|
|
2349
|
+
const ctxProps = (vnode.props || {});
|
|
2143
2350
|
const ctxId = ctxProps._contextId;
|
|
2144
|
-
const prevCtx = state.ssrContexts[ctxId];
|
|
2351
|
+
const prevCtx = ctxId ? state.ssrContexts[ctxId] : undefined;
|
|
2145
2352
|
if (ctxId) {
|
|
2146
2353
|
state.ssrContexts[ctxId] = ctxProps.value;
|
|
2147
2354
|
}
|
|
@@ -2161,61 +2368,17 @@ const renderToStringImpl = (element) => {
|
|
|
2161
2368
|
if (typeof vnode.type === 'function') {
|
|
2162
2369
|
const type = vnode.type;
|
|
2163
2370
|
const props = vnode.props || {};
|
|
2164
|
-
const renderedElement = type(props);
|
|
2371
|
+
const renderedElement = assertSyncRenderResult(type(props));
|
|
2165
2372
|
return renderToStringImpl(renderedElement);
|
|
2166
2373
|
}
|
|
2167
2374
|
const type = String(vnode.type);
|
|
2168
2375
|
const props = vnode.props || {};
|
|
2169
|
-
|
|
2170
|
-
let htmlChildren = '';
|
|
2171
|
-
let innerHTML = null;
|
|
2172
|
-
Object.entries(props).forEach(([key, value]) => {
|
|
2173
|
-
if (key === 'children') {
|
|
2174
|
-
if (Array.isArray(value)) {
|
|
2175
|
-
htmlChildren = value.map((child) => renderToStringImpl(child)).join('');
|
|
2176
|
-
}
|
|
2177
|
-
else {
|
|
2178
|
-
htmlChildren = renderToStringImpl(value);
|
|
2179
|
-
}
|
|
2180
|
-
}
|
|
2181
|
-
else if (key === 'dangerouslySetInnerHTML') {
|
|
2182
|
-
const inner = value;
|
|
2183
|
-
if (inner?.__html) {
|
|
2184
|
-
innerHTML = inner.__html;
|
|
2185
|
-
}
|
|
2186
|
-
}
|
|
2187
|
-
else if (key === STRINGS.STYLE || key === OLD_STRINGS.STYLE) {
|
|
2188
|
-
const styleString = renderStyle(value);
|
|
2189
|
-
if (styleString) {
|
|
2190
|
-
attributes += ` style="${escapeHtml(styleString)}"`;
|
|
2191
|
-
}
|
|
2192
|
-
}
|
|
2193
|
-
else if (key === STRINGS.CLASS_NAME || key === OLD_STRINGS.CLASS_NAME) {
|
|
2194
|
-
if (value) {
|
|
2195
|
-
attributes += ` class="${escapeHtml(value)}"`;
|
|
2196
|
-
}
|
|
2197
|
-
}
|
|
2198
|
-
else if (!key.startsWith('on') &&
|
|
2199
|
-
key !== 'key' &&
|
|
2200
|
-
key !== 'ref' &&
|
|
2201
|
-
key !== '__source' &&
|
|
2202
|
-
key !== '__self') {
|
|
2203
|
-
if (typeof value === 'boolean') {
|
|
2204
|
-
if (value)
|
|
2205
|
-
attributes += ` ${key}=""`;
|
|
2206
|
-
}
|
|
2207
|
-
else if (value != null) {
|
|
2208
|
-
let attrName = toSvgAttrName(key);
|
|
2209
|
-
let validatedValue = validateUri(attrName, value);
|
|
2210
|
-
attributes += ` ${attrName}="${escapeHtml(validatedValue)}"`;
|
|
2211
|
-
}
|
|
2212
|
-
}
|
|
2213
|
-
});
|
|
2376
|
+
const { attributes, innerHTML, htmlChildren } = buildHostProps(props, renderToStringImpl);
|
|
2214
2377
|
if (VOID_ELEMENTS.has(type)) {
|
|
2215
|
-
return
|
|
2378
|
+
return formatHostOpenTag(type, attributes);
|
|
2216
2379
|
}
|
|
2217
2380
|
const finalContent = innerHTML !== null ? innerHTML : htmlChildren;
|
|
2218
|
-
return
|
|
2381
|
+
return `${formatHostOpenTag(type, attributes)}${finalContent}</${type}>`;
|
|
2219
2382
|
};
|
|
2220
2383
|
const RC_SCRIPT = `
|
|
2221
2384
|
function $RC(id, templateId) {
|
|
@@ -2230,13 +2393,11 @@ function $RC(id, templateId) {
|
|
|
2230
2393
|
.replace(/\s+/g, ' ')
|
|
2231
2394
|
.trim();
|
|
2232
2395
|
const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
2233
|
-
if (element == null || typeof element === 'boolean') {
|
|
2234
|
-
return;
|
|
2235
|
-
}
|
|
2236
2396
|
if (element instanceof Promise) {
|
|
2237
2397
|
element = await element;
|
|
2238
|
-
|
|
2239
|
-
|
|
2398
|
+
}
|
|
2399
|
+
if (element == null || typeof element === 'boolean') {
|
|
2400
|
+
return;
|
|
2240
2401
|
}
|
|
2241
2402
|
if (typeof element === 'string' || typeof element === 'number') {
|
|
2242
2403
|
push(escapeHtml(element));
|
|
@@ -2250,12 +2411,11 @@ const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
|
2250
2411
|
}
|
|
2251
2412
|
const vnode = element;
|
|
2252
2413
|
if (vnode.type === RYUNIX_TYPES.TEXT_ELEMENT) {
|
|
2253
|
-
push(escapeHtml(vnode
|
|
2254
|
-
.props.nodeValue));
|
|
2414
|
+
push(escapeHtml(vnode.props.nodeValue));
|
|
2255
2415
|
return;
|
|
2256
2416
|
}
|
|
2257
2417
|
if (vnode.type === RYUNIX_TYPES.RYUNIX_FRAGMENT) {
|
|
2258
|
-
const children = vnode.props?.children
|
|
2418
|
+
const children = normalizeChildren(vnode.props?.children);
|
|
2259
2419
|
for (const child of children) {
|
|
2260
2420
|
await renderToStreamImpl(child, push, suspenseTasks);
|
|
2261
2421
|
}
|
|
@@ -2264,10 +2424,9 @@ const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
|
2264
2424
|
if (vnode.type === RYUNIX_TYPES.RYUNIX_CONTEXT) {
|
|
2265
2425
|
const state = getState();
|
|
2266
2426
|
state.ssrContexts = state.ssrContexts || {};
|
|
2267
|
-
const ctxProps = vnode.props ||
|
|
2268
|
-
{};
|
|
2427
|
+
const ctxProps = (vnode.props || {});
|
|
2269
2428
|
const ctxId = ctxProps._contextId;
|
|
2270
|
-
const prevCtx = state.ssrContexts[ctxId];
|
|
2429
|
+
const prevCtx = ctxId ? state.ssrContexts[ctxId] : undefined;
|
|
2271
2430
|
if (ctxId) {
|
|
2272
2431
|
state.ssrContexts[ctxId] = ctxProps.value;
|
|
2273
2432
|
}
|
|
@@ -2289,11 +2448,9 @@ const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
|
2289
2448
|
const isSuspenseBoundary = vnode.type === RYUNIX_TYPES.RYUNIX_SUSPENSE ||
|
|
2290
2449
|
(typeof suspenseType === 'object' &&
|
|
2291
2450
|
suspenseType != null &&
|
|
2292
|
-
suspenseType.type ===
|
|
2293
|
-
RYUNIX_TYPES.RYUNIX_SUSPENSE);
|
|
2451
|
+
suspenseType.type === RYUNIX_TYPES.RYUNIX_SUSPENSE);
|
|
2294
2452
|
if (isSuspenseBoundary) {
|
|
2295
|
-
const suspenseProps = vnode.props ||
|
|
2296
|
-
{};
|
|
2453
|
+
const suspenseProps = (vnode.props || {});
|
|
2297
2454
|
const { fallback, children } = suspenseProps;
|
|
2298
2455
|
const id = `s-${Math.random().toString(36).slice(2, 9)}`;
|
|
2299
2456
|
push(`<!--$?--><template id="B:${id}"></template><div id="S:${id}">`);
|
|
@@ -2315,14 +2472,14 @@ const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
|
2315
2472
|
finally {
|
|
2316
2473
|
state.isSuspenseBackground = wasBackground;
|
|
2317
2474
|
}
|
|
2318
|
-
})
|
|
2475
|
+
});
|
|
2319
2476
|
suspenseTasks.push(task);
|
|
2320
2477
|
await renderToStreamImpl(fallback, push, suspenseTasks);
|
|
2321
2478
|
push(`</div><!--$/-->`);
|
|
2322
2479
|
return;
|
|
2323
2480
|
}
|
|
2324
|
-
|
|
2325
|
-
|
|
2481
|
+
const type = vnode.type;
|
|
2482
|
+
const props = vnode.props || {};
|
|
2326
2483
|
if (typeof type === 'function') {
|
|
2327
2484
|
if (process.env.RYUNIX_DEBUG) {
|
|
2328
2485
|
console.log('[SSR Debug] Rendering function:', type.name || 'anonymous');
|
|
@@ -2332,49 +2489,13 @@ const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
|
2332
2489
|
return;
|
|
2333
2490
|
}
|
|
2334
2491
|
const hostTag = String(type);
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
Object.entries(props).forEach(([key, value]) => {
|
|
2339
|
-
if (key === 'children') ;
|
|
2340
|
-
else if (key === 'dangerouslySetInnerHTML') {
|
|
2341
|
-
const inner = value;
|
|
2342
|
-
if (inner?.__html) {
|
|
2343
|
-
innerHTML = inner.__html;
|
|
2344
|
-
}
|
|
2345
|
-
}
|
|
2346
|
-
else if (key === STRINGS.STYLE || key === OLD_STRINGS.STYLE) {
|
|
2347
|
-
const styleString = renderStyle(value);
|
|
2348
|
-
if (styleString) {
|
|
2349
|
-
attributes += ` style="${escapeHtml(styleString)}"`;
|
|
2350
|
-
}
|
|
2351
|
-
}
|
|
2352
|
-
else if (key === STRINGS.CLASS_NAME || key === OLD_STRINGS.CLASS_NAME) {
|
|
2353
|
-
if (value) {
|
|
2354
|
-
attributes += ` class="${escapeHtml(value)}"`;
|
|
2355
|
-
}
|
|
2356
|
-
}
|
|
2357
|
-
else if (!key.startsWith('on') &&
|
|
2358
|
-
key !== 'key' &&
|
|
2359
|
-
key !== 'ref' &&
|
|
2360
|
-
key !== '__source' &&
|
|
2361
|
-
key !== '__self') {
|
|
2362
|
-
if (typeof value === 'boolean') {
|
|
2363
|
-
if (value)
|
|
2364
|
-
attributes += ` ${key}=""`;
|
|
2365
|
-
}
|
|
2366
|
-
else if (value != null) {
|
|
2367
|
-
const attrName = toSvgAttrName(key);
|
|
2368
|
-
const validatedValue = validateUri(attrName, value);
|
|
2369
|
-
attributes += ` ${attrName}="${escapeHtml(validatedValue)}"`;
|
|
2370
|
-
}
|
|
2371
|
-
}
|
|
2372
|
-
});
|
|
2373
|
-
push(`<${hostTag}${attributes}>`);
|
|
2492
|
+
const children = props.children || [];
|
|
2493
|
+
const { attributes, innerHTML } = buildHostProps(props, () => '');
|
|
2494
|
+
push(formatHostOpenTag(hostTag, attributes));
|
|
2374
2495
|
if (innerHTML !== null) {
|
|
2375
2496
|
push(innerHTML);
|
|
2376
2497
|
}
|
|
2377
|
-
else {
|
|
2498
|
+
else if (!VOID_ELEMENTS.has(hostTag)) {
|
|
2378
2499
|
if (Array.isArray(children)) {
|
|
2379
2500
|
for (const child of children) {
|
|
2380
2501
|
await renderToStreamImpl(child, push, suspenseTasks);
|
|
@@ -2383,20 +2504,30 @@ const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
|
2383
2504
|
else {
|
|
2384
2505
|
await renderToStreamImpl(children, push, suspenseTasks);
|
|
2385
2506
|
}
|
|
2386
|
-
}
|
|
2387
|
-
if (!VOID_ELEMENTS.has(hostTag)) {
|
|
2388
2507
|
push(`</${hostTag}>`);
|
|
2389
2508
|
}
|
|
2390
2509
|
};
|
|
2510
|
+
const handleSuspenseTaskResult = (res, push, nonceAttr) => {
|
|
2511
|
+
if (res.success) {
|
|
2512
|
+
push(`<template id="P:${res.id}" data-ryunix-ssr>${res.content}</template>`);
|
|
2513
|
+
push(`<script${nonceAttr} data-ryunix-ssr>$RC("S:${res.id}", "P:${res.id}")</script>`);
|
|
2514
|
+
return;
|
|
2515
|
+
}
|
|
2516
|
+
const message = res.error instanceof Error
|
|
2517
|
+
? res.error.message
|
|
2518
|
+
: String(res.error ?? 'Unknown error');
|
|
2519
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
2520
|
+
console.error('[Ryunix SSR] Suspense boundary failed:', res.error);
|
|
2521
|
+
}
|
|
2522
|
+
push(`<!-- Ryunix Suspense error: ${escapeHtml(message)} -->`);
|
|
2523
|
+
};
|
|
2391
2524
|
const renderToReadableStream = (element, options = {}) => {
|
|
2392
2525
|
const state = getState();
|
|
2393
2526
|
const encoder = new TextEncoder();
|
|
2394
|
-
resetIdCounter();
|
|
2395
2527
|
return new ReadableStream({
|
|
2396
2528
|
async start(controller) {
|
|
2397
2529
|
const wasServerRendering = state.isServerRendering;
|
|
2398
|
-
|
|
2399
|
-
state.ssrMetadata = {};
|
|
2530
|
+
beginSsrRender();
|
|
2400
2531
|
const push = (text) => controller.enqueue(encoder.encode(text));
|
|
2401
2532
|
const suspenseTasks = [];
|
|
2402
2533
|
try {
|
|
@@ -2405,11 +2536,10 @@ const renderToReadableStream = (element, options = {}) => {
|
|
|
2405
2536
|
await renderToStreamImpl(element, push, suspenseTasks);
|
|
2406
2537
|
while (suspenseTasks.length > 0) {
|
|
2407
2538
|
const task = suspenseTasks.shift();
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
}
|
|
2539
|
+
if (!task)
|
|
2540
|
+
continue;
|
|
2541
|
+
const res = await task();
|
|
2542
|
+
handleSuspenseTaskResult(res, push, nonceAttr);
|
|
2413
2543
|
}
|
|
2414
2544
|
controller.close();
|
|
2415
2545
|
}
|
|
@@ -2422,12 +2552,10 @@ const renderToReadableStream = (element, options = {}) => {
|
|
|
2422
2552
|
},
|
|
2423
2553
|
});
|
|
2424
2554
|
};
|
|
2425
|
-
const renderToString = (element,
|
|
2555
|
+
const renderToString = (element, _options = {}) => {
|
|
2426
2556
|
const state = getState();
|
|
2427
2557
|
const wasServerRendering = state.isServerRendering;
|
|
2428
|
-
|
|
2429
|
-
state.ssrMetadata = {};
|
|
2430
|
-
resetIdCounter();
|
|
2558
|
+
beginSsrRender();
|
|
2431
2559
|
try {
|
|
2432
2560
|
return renderToStringImpl(element);
|
|
2433
2561
|
}
|
|
@@ -2581,7 +2709,9 @@ const Suspense = ({ fallback, children, }) => {
|
|
|
2581
2709
|
if (anyPending && !getState().isSuspenseBackground) {
|
|
2582
2710
|
return fallback || null;
|
|
2583
2711
|
}
|
|
2584
|
-
return createElement(Fragment, {
|
|
2712
|
+
return createElement(Fragment, {
|
|
2713
|
+
children: children,
|
|
2714
|
+
});
|
|
2585
2715
|
};
|
|
2586
2716
|
Suspense.type = RYUNIX_TYPES.RYUNIX_SUSPENSE;
|
|
2587
2717
|
function preload(importFn) {
|
|
@@ -2591,6 +2721,7 @@ function preload(importFn) {
|
|
|
2591
2721
|
var index = /*#__PURE__*/Object.freeze({
|
|
2592
2722
|
__proto__: null,
|
|
2593
2723
|
Children: Children,
|
|
2724
|
+
INTERNAL_META_KEYS: INTERNAL_META_KEYS,
|
|
2594
2725
|
Link: Link,
|
|
2595
2726
|
NavLink: NavLink,
|
|
2596
2727
|
RouterProvider: RouterProvider,
|
|
@@ -2600,8 +2731,10 @@ var index = /*#__PURE__*/Object.freeze({
|
|
|
2600
2731
|
forwardRef: forwardRef,
|
|
2601
2732
|
lazy: lazy,
|
|
2602
2733
|
memo: memo,
|
|
2734
|
+
mergeRouteMetadata: mergeRouteMetadata,
|
|
2603
2735
|
preload: preload,
|
|
2604
2736
|
resetIdCounter: resetIdCounter,
|
|
2737
|
+
resolvePageMetadata: resolvePageMetadata,
|
|
2605
2738
|
shallowEqual: shallowEqual,
|
|
2606
2739
|
useCallback: useCallback,
|
|
2607
2740
|
useDebounce: useDebounce,
|
|
@@ -2748,11 +2881,11 @@ function withProfiler(Component, name) {
|
|
|
2748
2881
|
return Profiled;
|
|
2749
2882
|
}
|
|
2750
2883
|
|
|
2751
|
-
function ServerBoundary({ children, id }) {
|
|
2884
|
+
function ServerBoundary({ children, id, }) {
|
|
2752
2885
|
return createElement('div', { 'data-ryunix-server': id, style: { display: 'contents' } }, children);
|
|
2753
2886
|
}
|
|
2754
2887
|
ServerBoundary.ryunix_type = 'RYUNIX_SERVER_BOUNDARY';
|
|
2755
|
-
function HydrationBoundary({ children, id }) {
|
|
2888
|
+
function HydrationBoundary({ children, id, }) {
|
|
2756
2889
|
return createElement('div', {
|
|
2757
2890
|
'data-ryunix-hydrate-boundary': id ?? '',
|
|
2758
2891
|
suppressHydrationWarning: true,
|
|
@@ -2787,7 +2920,7 @@ function createActionProxy(actionId) {
|
|
|
2787
2920
|
body: JSON.stringify({ actionId, args }),
|
|
2788
2921
|
});
|
|
2789
2922
|
if (!response.ok) {
|
|
2790
|
-
const errorData = await response.json().catch(() => ({}));
|
|
2923
|
+
const errorData = (await response.json().catch(() => ({})));
|
|
2791
2924
|
throw new Error(errorData.error || 'Server Action failed');
|
|
2792
2925
|
}
|
|
2793
2926
|
return response.json();
|
|
@@ -2813,9 +2946,7 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
2813
2946
|
else if ('error' in rawError) {
|
|
2814
2947
|
const nested = rawError.error;
|
|
2815
2948
|
error =
|
|
2816
|
-
nested && typeof nested === 'object'
|
|
2817
|
-
? nested
|
|
2818
|
-
: null;
|
|
2949
|
+
nested && typeof nested === 'object' ? nested : null;
|
|
2819
2950
|
}
|
|
2820
2951
|
else {
|
|
2821
2952
|
error = rawError;
|
|
@@ -3153,9 +3284,7 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3153
3284
|
if (parts.length >= 2) {
|
|
3154
3285
|
const lastPart = parts[parts.length - 1];
|
|
3155
3286
|
const colonIdx = lastPart.indexOf(':');
|
|
3156
|
-
const fileCandidate = colonIdx > 0
|
|
3157
|
-
? lastPart.slice(0, colonIdx)
|
|
3158
|
-
: lastPart;
|
|
3287
|
+
const fileCandidate = colonIdx > 0 ? lastPart.slice(0, colonIdx) : lastPart;
|
|
3159
3288
|
if (exts.some((ext) => fileCandidate.endsWith(ext))) {
|
|
3160
3289
|
fnName = parts.slice(0, -1).join(' ');
|
|
3161
3290
|
filePath = lastPart;
|
|
@@ -3442,9 +3571,243 @@ function Footer({ image, title, description, href = '/', imageAlt, className = '
|
|
|
3442
3571
|
: null, bottomBar));
|
|
3443
3572
|
}
|
|
3444
3573
|
|
|
3574
|
+
function getRyunixI18nConfig() {
|
|
3575
|
+
try {
|
|
3576
|
+
const value = ryunix.config.i18n;
|
|
3577
|
+
if (value && Array.isArray(value.locales) && value.locales.length > 0) {
|
|
3578
|
+
return value;
|
|
3579
|
+
}
|
|
3580
|
+
}
|
|
3581
|
+
catch {
|
|
3582
|
+
}
|
|
3583
|
+
return null;
|
|
3584
|
+
}
|
|
3585
|
+
|
|
3586
|
+
function defineMessages(messages) {
|
|
3587
|
+
return messages;
|
|
3588
|
+
}
|
|
3589
|
+
function resolveMessageKey(tree, key) {
|
|
3590
|
+
if (!tree)
|
|
3591
|
+
return undefined;
|
|
3592
|
+
const parts = key.split('.');
|
|
3593
|
+
let node = tree;
|
|
3594
|
+
for (const part of parts) {
|
|
3595
|
+
if (node == null || typeof node === 'string')
|
|
3596
|
+
return undefined;
|
|
3597
|
+
node = node[part];
|
|
3598
|
+
}
|
|
3599
|
+
return typeof node === 'string' ? node : undefined;
|
|
3600
|
+
}
|
|
3601
|
+
function formatMessage(template, params) {
|
|
3602
|
+
if (!params)
|
|
3603
|
+
return template;
|
|
3604
|
+
return Object.entries(params).reduce((value, [name, replacement]) => value.replace(new RegExp(`\\{${escapeRegExp$2(name)}\\}`, 'g'), String(replacement)), template);
|
|
3605
|
+
}
|
|
3606
|
+
function escapeRegExp$2(value) {
|
|
3607
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3608
|
+
}
|
|
3609
|
+
|
|
3610
|
+
function normalizeI18nConfig(config) {
|
|
3611
|
+
const locales = [...new Set(config.locales.filter(Boolean))];
|
|
3612
|
+
if (locales.length === 0) {
|
|
3613
|
+
throw new Error('[Ryunix i18n] At least one locale is required.');
|
|
3614
|
+
}
|
|
3615
|
+
const defaultLocale = locales.includes(config.defaultLocale)
|
|
3616
|
+
? config.defaultLocale
|
|
3617
|
+
: locales[0];
|
|
3618
|
+
return { ...config, locales, defaultLocale };
|
|
3619
|
+
}
|
|
3620
|
+
function pickLocale(record, locale, fallback) {
|
|
3621
|
+
return record?.[locale] ?? record?.[fallback];
|
|
3622
|
+
}
|
|
3623
|
+
function getLocaleFromPath(pathname, locales) {
|
|
3624
|
+
if (typeof pathname !== 'string' || locales.length === 0)
|
|
3625
|
+
return null;
|
|
3626
|
+
const pattern = new RegExp(`^/(${locales.map(escapeRegExp$1).join('|')})(/|$)`);
|
|
3627
|
+
const match = pathname.match(pattern);
|
|
3628
|
+
return match ? match[1] : null;
|
|
3629
|
+
}
|
|
3630
|
+
function localePath(locale, path = '', locales) {
|
|
3631
|
+
const normalized = path.startsWith('/') ? path : path ? `/${path}` : '';
|
|
3632
|
+
if (!locales.includes(locale))
|
|
3633
|
+
return normalized || '/';
|
|
3634
|
+
if (!normalized || normalized === '/')
|
|
3635
|
+
return `/${locale}`;
|
|
3636
|
+
return `/${locale}${normalized}`;
|
|
3637
|
+
}
|
|
3638
|
+
function swapLocalePath(pathname, targetLocale, options) {
|
|
3639
|
+
const { locales } = options;
|
|
3640
|
+
if (!locales.includes(targetLocale))
|
|
3641
|
+
return pathname;
|
|
3642
|
+
const current = getLocaleFromPath(pathname, locales);
|
|
3643
|
+
if (current) {
|
|
3644
|
+
const rest = pathname.slice(current.length + 1) || '';
|
|
3645
|
+
return localePath(targetLocale, rest, locales);
|
|
3646
|
+
}
|
|
3647
|
+
if (pathname.startsWith('/')) {
|
|
3648
|
+
return localePath(targetLocale, pathname, locales);
|
|
3649
|
+
}
|
|
3650
|
+
return localePath(targetLocale, '', locales);
|
|
3651
|
+
}
|
|
3652
|
+
function escapeRegExp$1(value) {
|
|
3653
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3654
|
+
}
|
|
3655
|
+
|
|
3656
|
+
const DEFAULT_LOCALE_COOKIE_NAME = 'ryunix_locale';
|
|
3657
|
+
function resolveCookieOptions(cookie, cookieName, maxAgeSeconds) {
|
|
3658
|
+
const enabled = cookie !== false;
|
|
3659
|
+
const cookieConfig = typeof cookie === 'object' ? cookie : {};
|
|
3660
|
+
return {
|
|
3661
|
+
enabled,
|
|
3662
|
+
cookieName: cookieName ?? cookieConfig.name ?? DEFAULT_LOCALE_COOKIE_NAME,
|
|
3663
|
+
maxAgeSeconds: maxAgeSeconds ?? cookieConfig.maxAgeSeconds ?? 365 * 24 * 60 * 60,
|
|
3664
|
+
};
|
|
3665
|
+
}
|
|
3666
|
+
function normalizeOptions(options) {
|
|
3667
|
+
const base = normalizeI18nConfig({
|
|
3668
|
+
locales: options.locales,
|
|
3669
|
+
defaultLocale: options.defaultLocale,
|
|
3670
|
+
});
|
|
3671
|
+
const cookie = resolveCookieOptions(options.cookie, options.cookieName, options.maxAgeSeconds);
|
|
3672
|
+
const localeLabels = { ...options.localeLabels };
|
|
3673
|
+
for (const locale of base.locales) {
|
|
3674
|
+
if (!localeLabels[locale])
|
|
3675
|
+
localeLabels[locale] = locale.toUpperCase();
|
|
3676
|
+
}
|
|
3677
|
+
return {
|
|
3678
|
+
locales: base.locales,
|
|
3679
|
+
defaultLocale: base.defaultLocale,
|
|
3680
|
+
localeLabels,
|
|
3681
|
+
cookieName: cookie.cookieName,
|
|
3682
|
+
maxAgeSeconds: cookie.maxAgeSeconds,
|
|
3683
|
+
cookieEnabled: cookie.enabled,
|
|
3684
|
+
messages: options.messages ?? {},
|
|
3685
|
+
};
|
|
3686
|
+
}
|
|
3687
|
+
function createTranslate(messages, locale, defaultLocale) {
|
|
3688
|
+
return (key, params) => {
|
|
3689
|
+
const value = resolveMessageKey(messages[locale], key) ??
|
|
3690
|
+
resolveMessageKey(messages[defaultLocale], key) ??
|
|
3691
|
+
key;
|
|
3692
|
+
return formatMessage(value, params);
|
|
3693
|
+
};
|
|
3694
|
+
}
|
|
3695
|
+
function createI18n(options) {
|
|
3696
|
+
const config = normalizeOptions(options);
|
|
3697
|
+
const { Provider: I18nContextProvider, useContext } = createContext('ryunix.i18n', {
|
|
3698
|
+
locale: config.defaultLocale,
|
|
3699
|
+
defaultLocale: config.defaultLocale,
|
|
3700
|
+
locales: config.locales,
|
|
3701
|
+
localeLabels: config.localeLabels,
|
|
3702
|
+
t: (key) => key,
|
|
3703
|
+
});
|
|
3704
|
+
const isLocale = (value) => typeof value === 'string' && config.locales.includes(value);
|
|
3705
|
+
const getLocaleCookie = () => {
|
|
3706
|
+
if (!config.cookieEnabled || typeof document === 'undefined')
|
|
3707
|
+
return null;
|
|
3708
|
+
const pattern = new RegExp(`(?:^|;\\s*)${config.cookieName}=(${config.locales.map(escapeRegExp).join('|')})(?:;|$)`);
|
|
3709
|
+
const match = document.cookie.match(pattern);
|
|
3710
|
+
return match ? match[1] : null;
|
|
3711
|
+
};
|
|
3712
|
+
const setLocaleCookie = (locale) => {
|
|
3713
|
+
if (!config.cookieEnabled ||
|
|
3714
|
+
typeof document === 'undefined' ||
|
|
3715
|
+
!isLocale(locale))
|
|
3716
|
+
return;
|
|
3717
|
+
document.cookie = `${config.cookieName}=${locale}; path=/; max-age=${config.maxAgeSeconds}; SameSite=Lax`;
|
|
3718
|
+
};
|
|
3719
|
+
const resolveLocaleFromCookie = () => getLocaleCookie() || config.defaultLocale;
|
|
3720
|
+
const getLocaleRedirectScript = () => {
|
|
3721
|
+
if (!config.cookieEnabled)
|
|
3722
|
+
return '';
|
|
3723
|
+
const localesPattern = config.locales.map(escapeRegExp).join('|');
|
|
3724
|
+
return `(function(){try{var m=document.cookie.match(/(?:^|;\\s*)${config.cookieName}=(${localesPattern})(?:;|$)/);var l=m?m[1]:'${config.defaultLocale}';var p=location.pathname;var r=new RegExp('^/(${localesPattern})(/|$)');if(!r.test(p)){location.replace('/'+l+(p==='/'?'':p));}}catch(e){}})();`;
|
|
3725
|
+
};
|
|
3726
|
+
const Provider = ({ locale, children, }) => {
|
|
3727
|
+
const resolved = isLocale(locale) ? locale : config.defaultLocale;
|
|
3728
|
+
const value = {
|
|
3729
|
+
locale: resolved,
|
|
3730
|
+
defaultLocale: config.defaultLocale,
|
|
3731
|
+
locales: config.locales,
|
|
3732
|
+
localeLabels: config.localeLabels,
|
|
3733
|
+
t: createTranslate(config.messages, resolved, config.defaultLocale),
|
|
3734
|
+
};
|
|
3735
|
+
return createElement(I18nContextProvider, { value, children });
|
|
3736
|
+
};
|
|
3737
|
+
const useI18n = () => useContext();
|
|
3738
|
+
const useLocale = () => useI18n().locale;
|
|
3739
|
+
const useTranslations = () => useI18n().t;
|
|
3740
|
+
const generateStaticParams = () => config.locales.map((locale) => ({ locale }));
|
|
3741
|
+
const LocaleSwitcher = ({ className = '' }) => {
|
|
3742
|
+
const { location, navigate } = useRouter();
|
|
3743
|
+
const { locale: current, localeLabels, locales } = useI18n();
|
|
3744
|
+
const onSelect = (locale) => {
|
|
3745
|
+
if (locale === current)
|
|
3746
|
+
return;
|
|
3747
|
+
setLocaleCookie(locale);
|
|
3748
|
+
const next = swapLocalePath(location, locale, {
|
|
3749
|
+
locales,
|
|
3750
|
+
defaultLocale: config.defaultLocale,
|
|
3751
|
+
});
|
|
3752
|
+
navigate(next.startsWith(`/${locale}`) ? next : `/${locale}`);
|
|
3753
|
+
};
|
|
3754
|
+
return createElement('div', {
|
|
3755
|
+
className: `ryunix-locale-switcher ${className}`.trim(),
|
|
3756
|
+
role: 'group',
|
|
3757
|
+
'aria-label': 'Language',
|
|
3758
|
+
children: locales.map((locale) => createElement('button', {
|
|
3759
|
+
key: locale,
|
|
3760
|
+
type: 'button',
|
|
3761
|
+
onClick: () => onSelect(locale),
|
|
3762
|
+
className: locale === current
|
|
3763
|
+
? 'ryunix-locale-switcher__btn is-active'
|
|
3764
|
+
: 'ryunix-locale-switcher__btn',
|
|
3765
|
+
'aria-current': locale === current ? 'true' : undefined,
|
|
3766
|
+
}, localeLabels[locale] ?? locale)),
|
|
3767
|
+
});
|
|
3768
|
+
};
|
|
3769
|
+
return {
|
|
3770
|
+
config,
|
|
3771
|
+
Provider,
|
|
3772
|
+
useI18n,
|
|
3773
|
+
useLocale,
|
|
3774
|
+
useTranslations,
|
|
3775
|
+
LocaleSwitcher,
|
|
3776
|
+
generateStaticParams,
|
|
3777
|
+
getLocaleFromPath: (pathname) => getLocaleFromPath(pathname, config.locales),
|
|
3778
|
+
localePath: (locale, path = '') => localePath(locale, path, config.locales),
|
|
3779
|
+
swapLocalePath: (pathname, targetLocale) => swapLocalePath(pathname, targetLocale, {
|
|
3780
|
+
locales: config.locales,
|
|
3781
|
+
defaultLocale: config.defaultLocale,
|
|
3782
|
+
}),
|
|
3783
|
+
pickLocale: (record, locale) => pickLocale(record, locale, config.defaultLocale),
|
|
3784
|
+
getLocaleCookie,
|
|
3785
|
+
setLocaleCookie,
|
|
3786
|
+
resolveLocaleFromCookie,
|
|
3787
|
+
getLocaleRedirectScript,
|
|
3788
|
+
};
|
|
3789
|
+
}
|
|
3790
|
+
function createAppI18n(messages, fallback) {
|
|
3791
|
+
const fromConfig = getRyunixI18nConfig() ?? fallback;
|
|
3792
|
+
if (!fromConfig) {
|
|
3793
|
+
throw new Error('[Ryunix i18n] Missing ryunix.config.i18n. Add i18n: { locales, defaultLocale } to ryunix.config.js, pass a fallback to createAppI18n(), or call createI18n() directly.');
|
|
3794
|
+
}
|
|
3795
|
+
return createI18n({ ...fromConfig, messages });
|
|
3796
|
+
}
|
|
3797
|
+
function createI18nFromConfig(config, messages) {
|
|
3798
|
+
if (!config?.locales?.length) {
|
|
3799
|
+
throw new Error('[Ryunix i18n] Invalid i18n config: locales array is required.');
|
|
3800
|
+
}
|
|
3801
|
+
return createI18n({ ...config, messages });
|
|
3802
|
+
}
|
|
3803
|
+
function escapeRegExp(value) {
|
|
3804
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3805
|
+
}
|
|
3806
|
+
|
|
3445
3807
|
var Ryunix = /*#__PURE__*/Object.freeze({
|
|
3446
3808
|
__proto__: null,
|
|
3447
3809
|
Children: Children,
|
|
3810
|
+
DEFAULT_LOCALE_COOKIE_NAME: DEFAULT_LOCALE_COOKIE_NAME,
|
|
3448
3811
|
DEFAULT_THEME_COOKIE_NAME: DEFAULT_THEME_COOKIE_NAME,
|
|
3449
3812
|
ErrorBoundary: ErrorBoundary,
|
|
3450
3813
|
Footer: Footer,
|
|
@@ -3452,6 +3815,7 @@ var Ryunix = /*#__PURE__*/Object.freeze({
|
|
|
3452
3815
|
Header: Header,
|
|
3453
3816
|
Hooks: index,
|
|
3454
3817
|
HydrationBoundary: HydrationBoundary,
|
|
3818
|
+
INTERNAL_META_KEYS: INTERNAL_META_KEYS,
|
|
3455
3819
|
Link: Link,
|
|
3456
3820
|
Main: Main,
|
|
3457
3821
|
NavLink: NavLink,
|
|
@@ -3467,13 +3831,19 @@ var Ryunix = /*#__PURE__*/Object.freeze({
|
|
|
3467
3831
|
batchUpdates: batchUpdates,
|
|
3468
3832
|
cloneElement: cloneElement,
|
|
3469
3833
|
createActionProxy: createActionProxy,
|
|
3834
|
+
createAppI18n: createAppI18n,
|
|
3470
3835
|
createContext: createContext,
|
|
3471
3836
|
createElement: createElement,
|
|
3837
|
+
createI18n: createI18n,
|
|
3838
|
+
createI18nFromConfig: createI18nFromConfig,
|
|
3472
3839
|
createPortal: createPortal,
|
|
3473
3840
|
createThemeController: createThemeController,
|
|
3474
3841
|
deepEqual: deepEqual,
|
|
3842
|
+
defineMessages: defineMessages,
|
|
3475
3843
|
escapeHtml: escapeHtml,
|
|
3476
3844
|
forwardRef: forwardRef,
|
|
3845
|
+
getLocaleFromPath: getLocaleFromPath,
|
|
3846
|
+
getRyunixI18nConfig: getRyunixI18nConfig,
|
|
3477
3847
|
getState: getState,
|
|
3478
3848
|
getSystemColorScheme: getSystemColorScheme,
|
|
3479
3849
|
getThemeCookie: getThemeCookie,
|
|
@@ -3481,12 +3851,16 @@ var Ryunix = /*#__PURE__*/Object.freeze({
|
|
|
3481
3851
|
init: init,
|
|
3482
3852
|
isValidElement: isValidElement,
|
|
3483
3853
|
lazy: lazy,
|
|
3854
|
+
localePath: localePath,
|
|
3484
3855
|
logHydrationBoundaryMismatch: logHydrationBoundaryMismatch,
|
|
3485
3856
|
logHydrationBoundaryRecovery: logHydrationBoundaryRecovery,
|
|
3486
3857
|
logHydrationFatal: logHydrationFatal,
|
|
3487
3858
|
logHydrationInfo: logHydrationInfo,
|
|
3488
3859
|
logHydrationRecoverable: logHydrationRecoverable,
|
|
3489
3860
|
memo: memo,
|
|
3861
|
+
mergeRouteMetadata: mergeRouteMetadata,
|
|
3862
|
+
normalizeI18nConfig: normalizeI18nConfig,
|
|
3863
|
+
pickLocale: pickLocale,
|
|
3490
3864
|
preload: preload,
|
|
3491
3865
|
profiler: profiler,
|
|
3492
3866
|
render: render,
|
|
@@ -3495,10 +3869,12 @@ var Ryunix = /*#__PURE__*/Object.freeze({
|
|
|
3495
3869
|
renderToStringAsync: renderToStringAsync,
|
|
3496
3870
|
resetIdCounter: resetIdCounter,
|
|
3497
3871
|
resolveEffectiveTheme: resolveEffectiveTheme,
|
|
3872
|
+
resolvePageMetadata: resolvePageMetadata,
|
|
3498
3873
|
resolveThemeFromCookie: resolveThemeFromCookie,
|
|
3499
3874
|
safeRender: safeRender,
|
|
3500
3875
|
setThemeCookie: setThemeCookie,
|
|
3501
3876
|
shallowEqual: shallowEqual,
|
|
3877
|
+
swapLocalePath: swapLocalePath,
|
|
3502
3878
|
themeController: themeController,
|
|
3503
3879
|
themeInitScript: themeInitScript,
|
|
3504
3880
|
useCallback: useCallback,
|
|
@@ -3590,7 +3966,7 @@ const defaultComponents = {
|
|
|
3590
3966
|
const Image = ({ src, ...props }) => {
|
|
3591
3967
|
return createElement('img', { ...props, src });
|
|
3592
3968
|
};
|
|
3593
|
-
const MDXContent = ({ children, components = {} }) => {
|
|
3969
|
+
const MDXContent = ({ children, components = {}, }) => {
|
|
3594
3970
|
const mergedComponents = getMDXComponents(components);
|
|
3595
3971
|
return createElement(MDXProvider, { value: mergedComponents }, createElement('div', null, children));
|
|
3596
3972
|
};
|
|
@@ -3598,5 +3974,5 @@ const MDXContent = ({ children, components = {} }) => {
|
|
|
3598
3974
|
if (typeof window !== 'undefined')
|
|
3599
3975
|
window.Ryunix = Ryunix;
|
|
3600
3976
|
|
|
3601
|
-
export { Children, DEFAULT_THEME_COOKIE_NAME, ErrorBoundary, Footer, Fragment, Header, index as Hooks, HydrationBoundary, Image, Link, MDXContent, MDXProvider, Main, NavLink, Priority, RouterProvider, RyunixDevOverlay, ServerBoundary, Suspense, THEME_PREFERENCES, ThemeInitScript, ThemeToggle, applyTheme, batchUpdates, cloneElement, createActionProxy, createContext, createElement, createPortal, createThemeController, deepEqual, Ryunix as default, defaultComponents, escapeHtml, forwardRef, getMDXComponents, getState, getSystemColorScheme, getThemeCookie, hydrate, init, isValidElement, lazy, logHydrationBoundaryMismatch, logHydrationBoundaryRecovery, logHydrationFatal, logHydrationInfo, logHydrationRecoverable, memo, preload, profiler, render, renderToReadableStream, renderToString, renderToStringAsync, resetIdCounter, resolveEffectiveTheme, resolveThemeFromCookie, ryxProps, safeRender, setThemeCookie, shallowEqual, themeController, themeInitScript, useCallback, useDebounce, useDeferredValue, useEffect, useHash, useId, useLayoutEffect, useMDXComponents, useMemo, useMetadata, usePathname, usePersistentStore, usePersistentStore as usePersitentStore, useProfiler, useQuery, useReducer, useRef, useRouter, useSearchParams, useStore, useStorePriority, useSwitch, useThrottle, useTransition, watchSystemTheme, withProfiler };
|
|
3977
|
+
export { Children, DEFAULT_LOCALE_COOKIE_NAME, DEFAULT_THEME_COOKIE_NAME, ErrorBoundary, Footer, Fragment, Header, index as Hooks, HydrationBoundary, INTERNAL_META_KEYS, Image, Link, MDXContent, MDXProvider, Main, NavLink, Priority, RouterProvider, RyunixDevOverlay, ServerBoundary, Suspense, THEME_PREFERENCES, ThemeInitScript, ThemeToggle, applyTheme, batchUpdates, cloneElement, createActionProxy, createAppI18n, createContext, createElement, createI18n, createI18nFromConfig, createPortal, createThemeController, deepEqual, Ryunix as default, defaultComponents, defineMessages, escapeHtml, forwardRef, getLocaleFromPath, getMDXComponents, getRyunixI18nConfig, getState, getSystemColorScheme, getThemeCookie, hydrate, init, isValidElement, lazy, localePath, logHydrationBoundaryMismatch, logHydrationBoundaryRecovery, logHydrationFatal, logHydrationInfo, logHydrationRecoverable, memo, mergeRouteMetadata, normalizeI18nConfig, pickLocale, preload, profiler, render, renderToReadableStream, renderToString, renderToStringAsync, resetIdCounter, resolveEffectiveTheme, resolvePageMetadata, resolveThemeFromCookie, ryxProps, safeRender, setThemeCookie, shallowEqual, swapLocalePath, themeController, themeInitScript, useCallback, useDebounce, useDeferredValue, useEffect, useHash, useId, useLayoutEffect, useMDXComponents, useMemo, useMetadata, usePathname, usePersistentStore, usePersistentStore as usePersitentStore, useProfiler, useQuery, useReducer, useRef, useRouter, useSearchParams, useStore, useStorePriority, useSwitch, useThrottle, useTransition, watchSystemTheme, withProfiler };
|
|
3602
3978
|
//# sourceMappingURL=Ryunix.esm.js.map
|