@unsetsoft/ryunixjs 1.2.5-canary.11 → 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 +1955 -2019
- package/dist/Ryunix.esm.js.map +1 -1
- package/dist/Ryunix.umd.js +1985 -2017
- 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 +13 -6
- package/styles/layout-footer.css +313 -0
- package/styles/layout-header.css +239 -0
- package/styles/layout.css +76 -0
- package/styles/ryunix-style.css +834 -0
- package/styles/theme-toggle.css +101 -0
- package/types/index.d.ts +138 -1
package/dist/Ryunix.esm.js
CHANGED
|
@@ -83,10 +83,6 @@ function nextValidSibling$1(node) {
|
|
|
83
83
|
return next;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
/**
|
|
87
|
-
* @param {string | number | boolean} text
|
|
88
|
-
* @returns {RyunixTextElement}
|
|
89
|
-
*/
|
|
90
86
|
const createTextElement = (text) => {
|
|
91
87
|
return {
|
|
92
88
|
type: RYUNIX_TYPES.TEXT_ELEMENT,
|
|
@@ -96,13 +92,6 @@ const createTextElement = (text) => {
|
|
|
96
92
|
},
|
|
97
93
|
};
|
|
98
94
|
};
|
|
99
|
-
/**
|
|
100
|
-
* Create a virtual DOM element.
|
|
101
|
-
* @param {string | symbol | RyunixComponent} type
|
|
102
|
-
* @param {RyunixProps | null} [props]
|
|
103
|
-
* @param {...RyunixNode} children
|
|
104
|
-
* @returns {RyunixElement}
|
|
105
|
-
*/
|
|
106
95
|
const createElement = (type, props, ...children) => {
|
|
107
96
|
const safeProps = props || {};
|
|
108
97
|
let rawChildren = children;
|
|
@@ -114,7 +103,6 @@ const createElement = (type, props, ...children) => {
|
|
|
114
103
|
rawChildren = rawChildren
|
|
115
104
|
.flat()
|
|
116
105
|
.filter((child) => child != null && child !== false && child !== true);
|
|
117
|
-
/** @type {RyunixNode[]} */
|
|
118
106
|
const normalizedChildren = [];
|
|
119
107
|
let currentText = '';
|
|
120
108
|
for (const child of rawChildren) {
|
|
@@ -140,22 +128,12 @@ const createElement = (type, props, ...children) => {
|
|
|
140
128
|
},
|
|
141
129
|
};
|
|
142
130
|
};
|
|
143
|
-
/**
|
|
144
|
-
* @param {{ children?: RyunixNode | RyunixNode[] }} props
|
|
145
|
-
* @returns {RyunixElement}
|
|
146
|
-
*/
|
|
147
131
|
const Fragment = (props) => {
|
|
148
132
|
const children = Array.isArray(props.children)
|
|
149
133
|
? props.children
|
|
150
134
|
: [props.children];
|
|
151
135
|
return createElement(RYUNIX_TYPES.RYUNIX_FRAGMENT, {}, ...children);
|
|
152
136
|
};
|
|
153
|
-
/**
|
|
154
|
-
* @param {RyunixElement} element
|
|
155
|
-
* @param {RyunixProps} [props]
|
|
156
|
-
* @param {...RyunixNode} children
|
|
157
|
-
* @returns {RyunixElement}
|
|
158
|
-
*/
|
|
159
137
|
const cloneElement = (element, props = {}, ...children) => {
|
|
160
138
|
if (!element || !is.object(element)) {
|
|
161
139
|
throw new Error('cloneElement requires a valid element');
|
|
@@ -169,32 +147,13 @@ const isValidElement = (object) => {
|
|
|
169
147
|
object.props !== undefined);
|
|
170
148
|
};
|
|
171
149
|
|
|
172
|
-
/** @typedef {import('../types/internal.js').RyunixFiber} RyunixFiber */
|
|
173
|
-
/** @typedef {import('../types/internal.js').RyunixHook} RyunixHook */
|
|
174
|
-
/**
|
|
175
|
-
* @param {string} key
|
|
176
|
-
* @returns {boolean}
|
|
177
|
-
*/
|
|
178
150
|
const isEvent = (key) => key.startsWith('on');
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
*/
|
|
183
|
-
const isProperty = (key) => key !== STRINGS.CHILDREN && !isEvent(key);
|
|
184
|
-
/**
|
|
185
|
-
* @param {Record<string, unknown>} prev
|
|
186
|
-
* @param {Record<string, unknown>} next
|
|
187
|
-
*/
|
|
188
|
-
const isNew = (prev, next) => /** @param {string} key */ (key) => {
|
|
151
|
+
const RESERVED_DOM_PROPS = new Set(['key', 'ref', STRINGS.CHILDREN]);
|
|
152
|
+
const isProperty = (key) => !RESERVED_DOM_PROPS.has(key) && !isEvent(key);
|
|
153
|
+
const isNew = (prev, next) => (key) => {
|
|
189
154
|
return !Object.is(prev[key], next[key]);
|
|
190
155
|
};
|
|
191
|
-
|
|
192
|
-
* @param {Record<string, unknown>} next
|
|
193
|
-
*/
|
|
194
|
-
const isGone = (next) => /** @param {string} key */ (key) => !(key in next);
|
|
195
|
-
/**
|
|
196
|
-
* @param {RyunixFiber} fiber
|
|
197
|
-
*/
|
|
156
|
+
const isGone = (next) => (key) => !(key in next);
|
|
198
157
|
const cancelEffects = (fiber) => {
|
|
199
158
|
if (!fiber?.hooks?.length)
|
|
200
159
|
return;
|
|
@@ -213,9 +172,6 @@ const cancelEffects = (fiber) => {
|
|
|
213
172
|
}
|
|
214
173
|
});
|
|
215
174
|
};
|
|
216
|
-
/**
|
|
217
|
-
* @param {RyunixFiber} fiber
|
|
218
|
-
*/
|
|
219
175
|
const cancelEffectsDeep = (fiber) => {
|
|
220
176
|
if (!fiber)
|
|
221
177
|
return;
|
|
@@ -327,23 +283,9 @@ function getCurrentPriority() {
|
|
|
327
283
|
return currentPriority;
|
|
328
284
|
}
|
|
329
285
|
|
|
330
|
-
/**
|
|
331
|
-
* @typedef {import('../types/internal.js').RyunixFiber} RyunixFiber
|
|
332
|
-
* @typedef {import('../types/internal.js').RyunixDomElement} RyunixDomElement
|
|
333
|
-
*/
|
|
334
|
-
/**
|
|
335
|
-
* Convert camelCase to kebab-case for CSS properties
|
|
336
|
-
* @param {string} camelCase - CamelCase string
|
|
337
|
-
* @returns {string} Kebab-case string
|
|
338
|
-
*/
|
|
339
286
|
const camelToKebab = (camelCase) => {
|
|
340
287
|
return camelCase.replace(CAMEL_TO_KEBAB_REGEX, (match) => `-${match.toLowerCase()}`);
|
|
341
288
|
};
|
|
342
|
-
/**
|
|
343
|
-
* Apply styles to DOM element
|
|
344
|
-
* @param {HTMLElement} dom - DOM element
|
|
345
|
-
* @param {Object} styleObj - Style object
|
|
346
|
-
*/
|
|
347
289
|
const applyStyles = (dom, styleObj) => {
|
|
348
290
|
if (!is.object(styleObj) || is.null(styleObj)) {
|
|
349
291
|
dom.style.cssText = '';
|
|
@@ -351,7 +293,7 @@ const applyStyles = (dom, styleObj) => {
|
|
|
351
293
|
}
|
|
352
294
|
try {
|
|
353
295
|
const cssText = Object.entries(styleObj)
|
|
354
|
-
.filter(([_, value]) => value != null)
|
|
296
|
+
.filter(([_, value]) => value != null)
|
|
355
297
|
.map(([key, value]) => {
|
|
356
298
|
const kebabKey = camelToKebab(key);
|
|
357
299
|
return `${kebabKey}: ${value}`;
|
|
@@ -365,14 +307,7 @@ const applyStyles = (dom, styleObj) => {
|
|
|
365
307
|
}
|
|
366
308
|
}
|
|
367
309
|
};
|
|
368
|
-
/**
|
|
369
|
-
* Apply CSS classes to DOM element
|
|
370
|
-
* @param {HTMLElement} dom - DOM element
|
|
371
|
-
* @param {string} prevClasses - Previous class string
|
|
372
|
-
* @param {string} nextClasses - Next class string
|
|
373
|
-
*/
|
|
374
310
|
const applyClasses = (dom, prevClasses, nextClasses) => {
|
|
375
|
-
// Allow empty/undefined - just remove classes
|
|
376
311
|
if (!nextClasses || nextClasses.trim() === '') {
|
|
377
312
|
if (prevClasses) {
|
|
378
313
|
const oldClasses = prevClasses.split(/\s+/).filter(Boolean);
|
|
@@ -380,24 +315,16 @@ const applyClasses = (dom, prevClasses, nextClasses) => {
|
|
|
380
315
|
}
|
|
381
316
|
return;
|
|
382
317
|
}
|
|
383
|
-
// Remove old classes
|
|
384
318
|
if (prevClasses) {
|
|
385
319
|
const oldClasses = prevClasses.split(/\s+/).filter(Boolean);
|
|
386
320
|
dom.classList.remove(...oldClasses);
|
|
387
321
|
}
|
|
388
|
-
// Add new classes
|
|
389
322
|
const newClasses = nextClasses.split(/\s+/).filter(Boolean);
|
|
390
323
|
if (newClasses.length > 0) {
|
|
391
324
|
dom.classList.add(...newClasses);
|
|
392
325
|
}
|
|
393
326
|
};
|
|
394
|
-
/**
|
|
395
|
-
* Create a DOM element from fiber
|
|
396
|
-
* @param {RyunixFiber} fiber - Fiber node
|
|
397
|
-
* @returns {HTMLElement | SVGElement | Text | null}
|
|
398
|
-
*/
|
|
399
327
|
const createDom = (fiber) => {
|
|
400
|
-
// Fragments and Context Providers don't create real DOM nodes
|
|
401
328
|
if (fiber.type === RYUNIX_TYPES.RYUNIX_FRAGMENT ||
|
|
402
329
|
fiber.type === RYUNIX_TYPES.RYUNIX_CONTEXT ||
|
|
403
330
|
fiber.type === Symbol.for('ryunix.portal')) {
|
|
@@ -409,7 +336,7 @@ const createDom = (fiber) => {
|
|
|
409
336
|
dom = document.createTextNode('');
|
|
410
337
|
}
|
|
411
338
|
else if (is.string(fiber.type)) {
|
|
412
|
-
const hostType =
|
|
339
|
+
const hostType = fiber.type;
|
|
413
340
|
const isSvg = [
|
|
414
341
|
'svg',
|
|
415
342
|
'path',
|
|
@@ -457,8 +384,7 @@ const createDom = (fiber) => {
|
|
|
457
384
|
}
|
|
458
385
|
return null;
|
|
459
386
|
}
|
|
460
|
-
updateDom(
|
|
461
|
-
/** @type {HTMLElement | Text} */ /** @type {HTMLElement | SVGElement | Text} */ dom, {}, fiber.props);
|
|
387
|
+
updateDom(dom, {}, fiber.props);
|
|
462
388
|
return dom;
|
|
463
389
|
}
|
|
464
390
|
catch (error) {
|
|
@@ -468,12 +394,6 @@ const createDom = (fiber) => {
|
|
|
468
394
|
return null;
|
|
469
395
|
}
|
|
470
396
|
};
|
|
471
|
-
/**
|
|
472
|
-
* @param {string} attrName
|
|
473
|
-
* @param {unknown} value
|
|
474
|
-
* @returns {unknown}
|
|
475
|
-
*/
|
|
476
|
-
/** @type {(attrName: string, value: unknown) => unknown} */
|
|
477
397
|
const checkAttributeUri = (attrName, value) => {
|
|
478
398
|
if (typeof value !== 'string')
|
|
479
399
|
return value;
|
|
@@ -496,12 +416,6 @@ const checkAttributeUri = (attrName, value) => {
|
|
|
496
416
|
return value;
|
|
497
417
|
};
|
|
498
418
|
const validateUri = checkAttributeUri;
|
|
499
|
-
/**
|
|
500
|
-
* Update DOM element with new props
|
|
501
|
-
* @param {HTMLElement|Text} dom - DOM element
|
|
502
|
-
* @param {Record<string, unknown>} [prevProps] - Previous props
|
|
503
|
-
* @param {Record<string, unknown>} [nextProps] - Next props
|
|
504
|
-
*/
|
|
505
419
|
const updateDom = (dom, prevProps = {}, nextProps = {}) => {
|
|
506
420
|
if (dom.nodeType === 3) {
|
|
507
421
|
if (prevProps.nodeValue !== nextProps.nodeValue) {
|
|
@@ -512,7 +426,7 @@ const updateDom = (dom, prevProps = {}, nextProps = {}) => {
|
|
|
512
426
|
const el = dom;
|
|
513
427
|
const domEl = el;
|
|
514
428
|
const handlerMap = domEl._ryunixHandlers;
|
|
515
|
-
|
|
429
|
+
const elRecord = el;
|
|
516
430
|
Object.keys(prevProps)
|
|
517
431
|
.filter(isEvent)
|
|
518
432
|
.filter((key) => isGone(nextProps)(key) || isNew(prevProps, nextProps)(key))
|
|
@@ -532,12 +446,10 @@ const updateDom = (dom, prevProps = {}, nextProps = {}) => {
|
|
|
532
446
|
}
|
|
533
447
|
}
|
|
534
448
|
});
|
|
535
|
-
// Remove old properties
|
|
536
449
|
Object.keys(prevProps)
|
|
537
450
|
.filter(isProperty)
|
|
538
451
|
.filter(isGone(nextProps))
|
|
539
452
|
.forEach((propKey) => {
|
|
540
|
-
// Skip special properties
|
|
541
453
|
if (propKey === STRINGS.STYLE ||
|
|
542
454
|
propKey === OLD_STRINGS.STYLE ||
|
|
543
455
|
propKey === STRINGS.CLASS_NAME ||
|
|
@@ -549,59 +461,45 @@ const updateDom = (dom, prevProps = {}, nextProps = {}) => {
|
|
|
549
461
|
el.removeAttribute(attrName);
|
|
550
462
|
}
|
|
551
463
|
else {
|
|
552
|
-
|
|
464
|
+
elRecord[propKey] = '';
|
|
553
465
|
el.removeAttribute(propKey);
|
|
554
466
|
}
|
|
555
467
|
});
|
|
556
|
-
// Set new properties
|
|
557
468
|
Object.keys(nextProps)
|
|
558
469
|
.filter(isProperty)
|
|
559
470
|
.filter(isNew(prevProps, nextProps))
|
|
560
471
|
.forEach((propKey) => {
|
|
561
472
|
try {
|
|
562
|
-
// Handle style properties
|
|
563
473
|
if (propKey === STRINGS.STYLE || propKey === OLD_STRINGS.STYLE) {
|
|
564
474
|
const styleValue = nextProps[propKey];
|
|
565
|
-
applyStyles(el,
|
|
475
|
+
applyStyles(el, styleValue);
|
|
566
476
|
}
|
|
567
|
-
// Handle className properties
|
|
568
477
|
else if (propKey === STRINGS.CLASS_NAME) {
|
|
569
|
-
applyClasses(el,
|
|
570
|
-
/** @type {string} */ prevProps[STRINGS.CLASS_NAME],
|
|
571
|
-
/** @type {string} */ nextProps[STRINGS.CLASS_NAME]);
|
|
478
|
+
applyClasses(el, prevProps[STRINGS.CLASS_NAME], nextProps[STRINGS.CLASS_NAME]);
|
|
572
479
|
}
|
|
573
480
|
else if (propKey === OLD_STRINGS.CLASS_NAME) {
|
|
574
|
-
applyClasses(el,
|
|
575
|
-
/** @type {string} */ prevProps[OLD_STRINGS.CLASS_NAME],
|
|
576
|
-
/** @type {string} */ nextProps[OLD_STRINGS.CLASS_NAME]);
|
|
481
|
+
applyClasses(el, prevProps[OLD_STRINGS.CLASS_NAME], nextProps[OLD_STRINGS.CLASS_NAME]);
|
|
577
482
|
}
|
|
578
|
-
// Handle other properties
|
|
579
483
|
else {
|
|
580
|
-
// Special handling for value and checked (controlled components)
|
|
581
484
|
if (propKey === 'value' || propKey === 'checked') {
|
|
582
|
-
if (
|
|
583
|
-
|
|
584
|
-
/** @type {Record<string, unknown>} */ /** @type {unknown} */ el[propKey] = nextProps[propKey];
|
|
485
|
+
if (elRecord[propKey] !== nextProps[propKey]) {
|
|
486
|
+
elRecord[propKey] = nextProps[propKey];
|
|
585
487
|
}
|
|
586
488
|
}
|
|
587
489
|
else {
|
|
588
490
|
const isSvgNode = el instanceof SVGElement;
|
|
589
491
|
if (isSvgNode) {
|
|
590
492
|
const attrName = toSvgAttrName(propKey);
|
|
591
|
-
/** @type {unknown} */
|
|
592
493
|
const svgValidated = checkAttributeUri(attrName, nextProps[propKey]);
|
|
593
|
-
|
|
594
|
-
el.setAttribute(attrName, /** @type {string} */ svgValidated);
|
|
494
|
+
el.setAttribute(attrName, String(svgValidated));
|
|
595
495
|
}
|
|
596
496
|
else {
|
|
597
497
|
const attrVal = nextProps[propKey];
|
|
598
|
-
/** @type {unknown} */
|
|
599
498
|
const safeValue = checkAttributeUri(propKey, attrVal);
|
|
600
|
-
|
|
601
|
-
// Best effort: set html attributes if it's not a primitive component property
|
|
499
|
+
elRecord[propKey] = safeValue;
|
|
602
500
|
if (typeof attrVal !== 'object' &&
|
|
603
501
|
typeof attrVal !== 'function') {
|
|
604
|
-
el.setAttribute(propKey,
|
|
502
|
+
el.setAttribute(propKey, String(safeValue));
|
|
605
503
|
}
|
|
606
504
|
}
|
|
607
505
|
}
|
|
@@ -613,7 +511,6 @@ const updateDom = (dom, prevProps = {}, nextProps = {}) => {
|
|
|
613
511
|
}
|
|
614
512
|
}
|
|
615
513
|
});
|
|
616
|
-
// Add new event listeners
|
|
617
514
|
Object.keys(nextProps)
|
|
618
515
|
.filter(isEvent)
|
|
619
516
|
.filter(isNew(prevProps, nextProps))
|
|
@@ -623,15 +520,6 @@ const updateDom = (dom, prevProps = {}, nextProps = {}) => {
|
|
|
623
520
|
const handler = (e) => {
|
|
624
521
|
runWithPriority(Priority.IMMEDIATE, () => nextProps[propKey](e));
|
|
625
522
|
};
|
|
626
|
-
// Store the wrapped handler so it can be removed later
|
|
627
|
-
// Note: For simplicity, we could also just wrap it on the fly,
|
|
628
|
-
// but we need the exact reference for removeEventListener.
|
|
629
|
-
// Actually, the current removeDom logic uses prevProps[name],
|
|
630
|
-
// which won't work if we wrap it here and don't store it.
|
|
631
|
-
// Wait, the current removeEventListener call in dom.js:177 is:
|
|
632
|
-
// dom.removeEventListener(eventType, prevProps[name])
|
|
633
|
-
// If we wrap it, we MUST store the wrapper.
|
|
634
|
-
// Let's use a weakMap or a property on the DOM node to store the wrappers.
|
|
635
523
|
if (!domEl._ryunixHandlers) {
|
|
636
524
|
domEl._ryunixHandlers = new Map();
|
|
637
525
|
}
|
|
@@ -645,10 +533,6 @@ const updateDom = (dom, prevProps = {}, nextProps = {}) => {
|
|
|
645
533
|
}
|
|
646
534
|
});
|
|
647
535
|
};
|
|
648
|
-
/**
|
|
649
|
-
* Clear all children from a DOM element
|
|
650
|
-
* @param {HTMLElement} container - DOM element to clear
|
|
651
|
-
*/
|
|
652
536
|
const clearContainer = (container) => {
|
|
653
537
|
if (!container)
|
|
654
538
|
return;
|
|
@@ -676,10 +560,6 @@ function createPortal(children, container) {
|
|
|
676
560
|
}
|
|
677
561
|
|
|
678
562
|
const PREFIX = '[Ryunix Hydration]';
|
|
679
|
-
/**
|
|
680
|
-
* @param {'warn' | 'error'} level
|
|
681
|
-
* @param {string} message
|
|
682
|
-
*/
|
|
683
563
|
const emit = (level, message) => {
|
|
684
564
|
const line = `${PREFIX} ${message}`;
|
|
685
565
|
if (level === 'error') {
|
|
@@ -691,16 +571,11 @@ const emit = (level, message) => {
|
|
|
691
571
|
};
|
|
692
572
|
const shouldReportStrict = () => process.env.NODE_ENV !== 'production' &&
|
|
693
573
|
process.env.RYUNIX_HYDRATION_STRICT === 'true';
|
|
694
|
-
/** @param {string} message */
|
|
695
574
|
const logHydrationInfo = (message) => {
|
|
696
575
|
if (!shouldReportStrict())
|
|
697
576
|
return;
|
|
698
577
|
emit('warn', message);
|
|
699
578
|
};
|
|
700
|
-
/**
|
|
701
|
-
* Log the first hydration DOM mismatch (tag/text vs client tree).
|
|
702
|
-
* @param {string} detail
|
|
703
|
-
*/
|
|
704
579
|
const logHydrationMismatch = (detail) => {
|
|
705
580
|
const state = getState();
|
|
706
581
|
if (state.hydrationMismatchReported)
|
|
@@ -709,9 +584,6 @@ const logHydrationMismatch = (detail) => {
|
|
|
709
584
|
const level = process.env.NODE_ENV === 'production' ? 'error' : 'warn';
|
|
710
585
|
emit(level, `${detail} Server HTML did not match the client render. Falling back to client-side render.`);
|
|
711
586
|
};
|
|
712
|
-
/**
|
|
713
|
-
* @param {string} detail
|
|
714
|
-
*/
|
|
715
587
|
const logHydrationBoundaryMismatch = (detail) => {
|
|
716
588
|
const state = getState();
|
|
717
589
|
if (state.hydrationBoundaryMismatchReported)
|
|
@@ -720,18 +592,11 @@ const logHydrationBoundaryMismatch = (detail) => {
|
|
|
720
592
|
const level = process.env.NODE_ENV === 'production' ? 'error' : 'warn';
|
|
721
593
|
emit(level, `${detail} Recovering the nearest hydration boundary with a scoped client render.`);
|
|
722
594
|
};
|
|
723
|
-
/**
|
|
724
|
-
* @param {string} detail
|
|
725
|
-
*/
|
|
726
595
|
const logHydrationRecoverable = (detail) => {
|
|
727
596
|
if (!shouldReportStrict())
|
|
728
597
|
return;
|
|
729
598
|
emit('warn', `Recovered a hydration mismatch (${detail}) without root fallback.`);
|
|
730
599
|
};
|
|
731
|
-
/**
|
|
732
|
-
* Log when hydration failed and the SSR container is being cleared.
|
|
733
|
-
* @param {string} [reason]
|
|
734
|
-
*/
|
|
735
600
|
const logHydrationFailure = (reason = '') => {
|
|
736
601
|
const state = getState();
|
|
737
602
|
if (state.hydrationFailureReported)
|
|
@@ -745,10 +610,6 @@ const logHydrationFailure = (reason = '') => {
|
|
|
745
610
|
const level = process.env.NODE_ENV === 'production' ? 'error' : 'warn';
|
|
746
611
|
emit(level, `${detail}Clearing #__ryunix and remounting on the client.`);
|
|
747
612
|
};
|
|
748
|
-
/**
|
|
749
|
-
* Log when leftover SSR nodes are removed after hydration (soft mismatch).
|
|
750
|
-
* @param {number} count
|
|
751
|
-
*/
|
|
752
613
|
const logHydrationUnmatchedNodes = (count) => {
|
|
753
614
|
if (!count)
|
|
754
615
|
return;
|
|
@@ -758,7 +619,6 @@ const logHydrationUnmatchedNodes = (count) => {
|
|
|
758
619
|
state.hydrationUnmatchedReported = true;
|
|
759
620
|
emit('warn', `Removed ${count} server-rendered DOM node(s) that were not used by the client tree. This can indicate an SSR/client markup mismatch.`);
|
|
760
621
|
};
|
|
761
|
-
/** Log CSR recovery after a failed hydration pass. */
|
|
762
622
|
const logHydrationRecovery = () => {
|
|
763
623
|
const state = getState();
|
|
764
624
|
if (state.hydrationRecoveryReported)
|
|
@@ -766,7 +626,6 @@ const logHydrationRecovery = () => {
|
|
|
766
626
|
state.hydrationRecoveryReported = true;
|
|
767
627
|
emit('warn', 'Remounting the application on the client after hydration failure.');
|
|
768
628
|
};
|
|
769
|
-
/** Log scoped boundary recovery after a local mismatch. */
|
|
770
629
|
const logHydrationBoundaryRecovery = () => {
|
|
771
630
|
const state = getState();
|
|
772
631
|
if (state.hydrationBoundaryRecoveryReported)
|
|
@@ -774,13 +633,9 @@ const logHydrationBoundaryRecovery = () => {
|
|
|
774
633
|
state.hydrationBoundaryRecoveryReported = true;
|
|
775
634
|
emit('warn', 'Remounting a hydration boundary after local mismatch.');
|
|
776
635
|
};
|
|
777
|
-
/**
|
|
778
|
-
* @param {string} reason
|
|
779
|
-
*/
|
|
780
636
|
const logHydrationFatal = (reason) => {
|
|
781
637
|
emit('error', reason);
|
|
782
638
|
};
|
|
783
|
-
/** Reset per-mount hydration log flags (call from init). */
|
|
784
639
|
const resetHydrationLogFlags = () => {
|
|
785
640
|
const state = getState();
|
|
786
641
|
state.hydrationMismatchReported = false;
|
|
@@ -791,25 +646,14 @@ const resetHydrationLogFlags = () => {
|
|
|
791
646
|
state.hydrationBoundaryRecoveryReported = false;
|
|
792
647
|
};
|
|
793
648
|
|
|
794
|
-
/**
|
|
795
|
-
* @typedef {import('../types/internal.js').RyunixFiber} RyunixFiber
|
|
796
|
-
* @typedef {import('../types/internal.js').RyunixRootFiber} RyunixRootFiber
|
|
797
|
-
*/
|
|
798
|
-
/**
|
|
799
|
-
* Run layout effects (useLayoutEffect) synchronously during commit.
|
|
800
|
-
* These run after DOM mutations but before the browser paints.
|
|
801
|
-
* @param {RyunixFiber} fiber
|
|
802
|
-
*/
|
|
803
649
|
const runLayoutEffects = (fiber) => {
|
|
804
650
|
if (!fiber?.hooks?.length)
|
|
805
651
|
return;
|
|
806
652
|
for (let i = 0; i < fiber.hooks.length; i++) {
|
|
807
|
-
/** @type {import('../types/internal.js').RyunixHook & { isLayout?: boolean }} */
|
|
808
653
|
const hook = fiber.hooks[i];
|
|
809
654
|
if (hook.type === RYUNIX_TYPES.RYUNIX_EFFECT &&
|
|
810
655
|
hook.isLayout &&
|
|
811
656
|
is.function(hook.effect)) {
|
|
812
|
-
// Cancel previous layout cleanup if exists
|
|
813
657
|
if (is.function(hook.cancel)) {
|
|
814
658
|
try {
|
|
815
659
|
hook.cancel();
|
|
@@ -820,12 +664,9 @@ const runLayoutEffects = (fiber) => {
|
|
|
820
664
|
}
|
|
821
665
|
}
|
|
822
666
|
}
|
|
823
|
-
// Run new layout effect synchronously
|
|
824
667
|
try {
|
|
825
668
|
const cleanup = hook.effect();
|
|
826
|
-
hook.cancel = is.function(cleanup)
|
|
827
|
-
? /** @type {() => void} */ cleanup
|
|
828
|
-
: null;
|
|
669
|
+
hook.cancel = is.function(cleanup) ? cleanup : null;
|
|
829
670
|
}
|
|
830
671
|
catch (error) {
|
|
831
672
|
if (process.env.NODE_ENV !== 'production') {
|
|
@@ -837,20 +678,14 @@ const runLayoutEffects = (fiber) => {
|
|
|
837
678
|
}
|
|
838
679
|
}
|
|
839
680
|
};
|
|
840
|
-
/**
|
|
841
|
-
* Run normal (non-layout) effects asynchronously after paint.
|
|
842
|
-
* @param {RyunixFiber} fiber
|
|
843
|
-
*/
|
|
844
681
|
const runNormalEffects = (fiber) => {
|
|
845
682
|
if (!fiber?.hooks?.length)
|
|
846
683
|
return;
|
|
847
684
|
for (let i = 0; i < fiber.hooks.length; i++) {
|
|
848
|
-
/** @type {import('../types/internal.js').RyunixHook & { isLayout?: boolean }} */
|
|
849
685
|
const hook = fiber.hooks[i];
|
|
850
686
|
if (hook.type === RYUNIX_TYPES.RYUNIX_EFFECT &&
|
|
851
687
|
!hook.isLayout &&
|
|
852
688
|
is.function(hook.effect)) {
|
|
853
|
-
// Cancel previous cleanup if exists
|
|
854
689
|
if (is.function(hook.cancel)) {
|
|
855
690
|
try {
|
|
856
691
|
hook.cancel();
|
|
@@ -861,12 +696,9 @@ const runNormalEffects = (fiber) => {
|
|
|
861
696
|
}
|
|
862
697
|
}
|
|
863
698
|
}
|
|
864
|
-
// Run new effect
|
|
865
699
|
try {
|
|
866
700
|
const cleanup = hook.effect();
|
|
867
|
-
hook.cancel = is.function(cleanup)
|
|
868
|
-
? /** @type {() => void} */ cleanup
|
|
869
|
-
: null;
|
|
701
|
+
hook.cancel = is.function(cleanup) ? cleanup : null;
|
|
870
702
|
}
|
|
871
703
|
catch (error) {
|
|
872
704
|
if (process.env.NODE_ENV !== 'production') {
|
|
@@ -878,25 +710,19 @@ const runNormalEffects = (fiber) => {
|
|
|
878
710
|
}
|
|
879
711
|
}
|
|
880
712
|
};
|
|
881
|
-
/**
|
|
882
|
-
* The `commitRoot` function commits the changes made to the virtual DOM by updating the actual DOM.
|
|
883
|
-
*/
|
|
884
713
|
function commitRoot() {
|
|
885
714
|
const state = getState();
|
|
886
715
|
state.deletions.forEach(commitWork);
|
|
887
|
-
const finishedWork =
|
|
888
|
-
|
|
889
|
-
|
|
716
|
+
const finishedWork = state.wipRoot;
|
|
717
|
+
if (!finishedWork)
|
|
718
|
+
return;
|
|
890
719
|
state.currentRoot = finishedWork;
|
|
891
|
-
// After hydration is done, reset the flag and cleanup unconsumed nodes
|
|
892
720
|
if (state.isHydrating || state.hydrationFailed) {
|
|
893
721
|
if (process.env.NODE_ENV !== 'production' && process.env.RYUNIX_DEBUG) {
|
|
894
722
|
console.log(`[Ryunix Debug] commitRoot - isHydrating: ${state.isHydrating}, hydrationFailed: ${state.hydrationFailed}`);
|
|
895
723
|
}
|
|
896
724
|
if (state.hydrationFailed) ;
|
|
897
725
|
else {
|
|
898
|
-
// If there is a cursor left, it means these are SSR nodes that weren't matched
|
|
899
|
-
// by any client fiber. We must remove them to avoid duplication.
|
|
900
726
|
let cursor = state.hydrateCursor;
|
|
901
727
|
let removed = 0;
|
|
902
728
|
if (cursor &&
|
|
@@ -919,23 +745,17 @@ function commitRoot() {
|
|
|
919
745
|
state.hydrateCursor = null;
|
|
920
746
|
}
|
|
921
747
|
commitWork(finishedWork.child);
|
|
922
|
-
// If wipRoot was not reassigned by a synchronous dispatch during effects, clear it
|
|
923
748
|
if (state.wipRoot === finishedWork) {
|
|
924
749
|
state.wipRoot = null;
|
|
925
750
|
}
|
|
926
751
|
}
|
|
927
|
-
/**
|
|
928
|
-
* @param {RyunixFiber | null | undefined} fiber
|
|
929
|
-
*/
|
|
930
752
|
function commitWork(fiber) {
|
|
931
753
|
if (!fiber) {
|
|
932
754
|
return;
|
|
933
755
|
}
|
|
934
|
-
// Handle portal fibers — they render into a different container
|
|
935
756
|
if (fiber.type === RYUNIX_PORTAL || fiber._isPortal) {
|
|
936
757
|
const portalContainer = fiber.containerInfo;
|
|
937
758
|
if (portalContainer) {
|
|
938
|
-
// Process portal children into the portal container
|
|
939
759
|
const portalFiber = fiber.child;
|
|
940
760
|
if (portalFiber) {
|
|
941
761
|
commitPortalWork(portalFiber, portalContainer);
|
|
@@ -952,6 +772,8 @@ function commitWork(fiber) {
|
|
|
952
772
|
return;
|
|
953
773
|
}
|
|
954
774
|
const domParent = domParentFiber.dom;
|
|
775
|
+
if (!domParent)
|
|
776
|
+
return;
|
|
955
777
|
if (fiber.effectTag === EFFECT_TAGS.PLACEMENT) {
|
|
956
778
|
if (fiber.dom != null) {
|
|
957
779
|
if (process.env.NODE_ENV !== 'production' && process.env.RYUNIX_DEBUG) {
|
|
@@ -959,15 +781,13 @@ function commitWork(fiber) {
|
|
|
959
781
|
}
|
|
960
782
|
domParent.appendChild(fiber.dom);
|
|
961
783
|
}
|
|
962
|
-
// Layout effects run synchronously during commit
|
|
963
784
|
runLayoutEffects(fiber);
|
|
964
|
-
// Normal effects run after paint
|
|
965
785
|
runNormalEffects(fiber);
|
|
966
786
|
}
|
|
967
787
|
else if (fiber.effectTag === EFFECT_TAGS.UPDATE) {
|
|
968
788
|
cancelEffects(fiber);
|
|
969
789
|
if (fiber.dom != null) {
|
|
970
|
-
updateDom(fiber.dom, fiber.alternate
|
|
790
|
+
updateDom(fiber.dom, fiber.alternate?.props, fiber.props);
|
|
971
791
|
}
|
|
972
792
|
runLayoutEffects(fiber);
|
|
973
793
|
runNormalEffects(fiber);
|
|
@@ -978,8 +798,6 @@ function commitWork(fiber) {
|
|
|
978
798
|
if (process.env.NODE_ENV !== 'production' && process.env.RYUNIX_DEBUG) {
|
|
979
799
|
console.log('[Ryunix Debug] Hydration fallback PLACEMENT:', fiber.type);
|
|
980
800
|
}
|
|
981
|
-
// Since container is cleared on fallback, treat as normal placement
|
|
982
|
-
// No need to check fiber.dom.parentNode !== domParent because the container was cleared.
|
|
983
801
|
if (fiber.dom != null) {
|
|
984
802
|
domParent.appendChild(fiber.dom);
|
|
985
803
|
}
|
|
@@ -998,7 +816,6 @@ function commitWork(fiber) {
|
|
|
998
816
|
}
|
|
999
817
|
}
|
|
1000
818
|
else if (fiber.effectTag === EFFECT_TAGS.DELETION) {
|
|
1001
|
-
// Run cleanups BEFORE removing DOM to allow cleanup functions to read DOM state
|
|
1002
819
|
cancelEffectsDeep(fiber);
|
|
1003
820
|
commitDeletion(fiber);
|
|
1004
821
|
return;
|
|
@@ -1006,11 +823,6 @@ function commitWork(fiber) {
|
|
|
1006
823
|
commitWork(fiber.child);
|
|
1007
824
|
commitWork(fiber.sibling);
|
|
1008
825
|
}
|
|
1009
|
-
/**
|
|
1010
|
-
* Commit work for portal children into a specific container
|
|
1011
|
-
* @param {RyunixFiber | null | undefined} fiber
|
|
1012
|
-
* @param {Element | DocumentFragment} portalContainer
|
|
1013
|
-
*/
|
|
1014
826
|
const commitPortalWork = (fiber, portalContainer) => {
|
|
1015
827
|
if (!fiber)
|
|
1016
828
|
return;
|
|
@@ -1024,7 +836,7 @@ const commitPortalWork = (fiber, portalContainer) => {
|
|
|
1024
836
|
else if (fiber.effectTag === EFFECT_TAGS.UPDATE) {
|
|
1025
837
|
cancelEffects(fiber);
|
|
1026
838
|
if (fiber.dom != null) {
|
|
1027
|
-
updateDom(fiber.dom, fiber.alternate
|
|
839
|
+
updateDom(fiber.dom, fiber.alternate?.props, fiber.props);
|
|
1028
840
|
}
|
|
1029
841
|
runLayoutEffects(fiber);
|
|
1030
842
|
runNormalEffects(fiber);
|
|
@@ -1037,10 +849,6 @@ const commitPortalWork = (fiber, portalContainer) => {
|
|
|
1037
849
|
commitPortalWork(fiber.child, portalContainer);
|
|
1038
850
|
commitPortalWork(fiber.sibling, portalContainer);
|
|
1039
851
|
};
|
|
1040
|
-
/**
|
|
1041
|
-
* @param {RyunixFiber} fiber
|
|
1042
|
-
* @param {Node} domParent
|
|
1043
|
-
*/
|
|
1044
852
|
const commitDeletion = (fiber, domParent) => {
|
|
1045
853
|
if (fiber.dom) {
|
|
1046
854
|
if (fiber.dom.parentNode) {
|
|
@@ -1081,6 +889,12 @@ const reconcileChildren = (wipFiber, elements) => {
|
|
|
1081
889
|
let newFiber;
|
|
1082
890
|
const sameType = matchedFiber && element.type === matchedFiber.type;
|
|
1083
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;
|
|
1084
898
|
newFiber = {
|
|
1085
899
|
type: matchedFiber.type,
|
|
1086
900
|
props: element.props,
|
|
@@ -1089,7 +903,7 @@ const reconcileChildren = (wipFiber, elements) => {
|
|
|
1089
903
|
alternate: matchedFiber,
|
|
1090
904
|
effectTag: EFFECT_TAGS.UPDATE,
|
|
1091
905
|
hooks: matchedFiber.hooks,
|
|
1092
|
-
stateError: matchedFiber.stateError,
|
|
906
|
+
stateError: preserveBoundaryError ? matchedFiber.stateError : undefined,
|
|
1093
907
|
key: element.key,
|
|
1094
908
|
index,
|
|
1095
909
|
};
|
|
@@ -1130,852 +944,751 @@ const reconcileChildren = (wipFiber, elements) => {
|
|
|
1130
944
|
});
|
|
1131
945
|
};
|
|
1132
946
|
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
947
|
+
const getHydrationPolicy = () => {
|
|
948
|
+
const env = (globalThis.process && globalThis.process.env) || {};
|
|
949
|
+
const recoverRaw = env.RYUNIX_HYDRATION_RECOVER || 'boundary';
|
|
950
|
+
const boundariesRaw = env.RYUNIX_HYDRATION_BOUNDARIES || 'route';
|
|
951
|
+
const strict = env.RYUNIX_HYDRATION_STRICT === 'true';
|
|
952
|
+
const recover = recoverRaw === 'none' || recoverRaw === 'root' ? recoverRaw : 'boundary';
|
|
953
|
+
const boundaries = boundariesRaw === 'server-only' || boundariesRaw === 'all-layouts'
|
|
954
|
+
? boundariesRaw
|
|
955
|
+
: 'route';
|
|
956
|
+
return {
|
|
957
|
+
recover,
|
|
958
|
+
boundaries,
|
|
959
|
+
strict,
|
|
960
|
+
};
|
|
1143
961
|
};
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
962
|
+
const findNearestHydrationBoundary = (fiber) => {
|
|
963
|
+
let current = fiber || null;
|
|
964
|
+
while (current) {
|
|
965
|
+
const props = current.props;
|
|
966
|
+
if (props &&
|
|
967
|
+
Object.prototype.hasOwnProperty.call(props, 'data-ryunix-hydrate-boundary')) {
|
|
968
|
+
return current;
|
|
969
|
+
}
|
|
970
|
+
const type = current.type;
|
|
971
|
+
const maybeTyped = type;
|
|
972
|
+
if (type &&
|
|
973
|
+
typeof type === 'function' &&
|
|
974
|
+
maybeTyped?.ryunix_type === 'RYUNIX_HYDRATION_BOUNDARY') {
|
|
975
|
+
return current;
|
|
976
|
+
}
|
|
977
|
+
current = current.parent || null;
|
|
1154
978
|
}
|
|
979
|
+
return null;
|
|
1155
980
|
};
|
|
1156
|
-
|
|
1157
|
-
let
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
try {
|
|
1163
|
-
callback();
|
|
1164
|
-
}
|
|
1165
|
-
finally {
|
|
1166
|
-
isBatching = wasBatching;
|
|
1167
|
-
if (!isBatching && pendingUpdates.length > 0) {
|
|
1168
|
-
flushUpdates();
|
|
981
|
+
const findBoundaryDomFromNode = (node) => {
|
|
982
|
+
let current = node ?? null;
|
|
983
|
+
while (current) {
|
|
984
|
+
if (current.nodeType === 1 &&
|
|
985
|
+
current.hasAttribute('data-ryunix-hydrate-boundary')) {
|
|
986
|
+
return current;
|
|
1169
987
|
}
|
|
988
|
+
current = current.parentNode;
|
|
1170
989
|
}
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
if (!
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
return;
|
|
1181
|
-
const updates = pendingUpdates;
|
|
1182
|
-
pendingUpdates = [];
|
|
1183
|
-
updates.forEach((update) => update());
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
/**
|
|
1187
|
-
* @typedef {import('../types/internal.js').RyunixComponent} RyunixComponent
|
|
1188
|
-
* @typedef {import('../types/internal.js').RyunixFiber} RyunixFiber
|
|
1189
|
-
*/
|
|
1190
|
-
/**
|
|
1191
|
-
* Development warnings
|
|
1192
|
-
*/
|
|
1193
|
-
process.env.NODE_ENV !== 'production';
|
|
1194
|
-
/**
|
|
1195
|
-
* Hook call validation
|
|
1196
|
-
*/
|
|
1197
|
-
const validateHookContext = (hookName = 'A hook') => {
|
|
1198
|
-
const state = getState();
|
|
1199
|
-
if (!state.wipFiber) {
|
|
1200
|
-
throw new Error(`${hookName} can only be called inside function components. ` +
|
|
1201
|
-
'Make sure you are calling hooks at the top level of your component.');
|
|
990
|
+
return null;
|
|
991
|
+
};
|
|
992
|
+
const getBoundaryDom = (fiber) => {
|
|
993
|
+
if (!fiber)
|
|
994
|
+
return null;
|
|
995
|
+
if (fiber.dom && fiber.dom.nodeType === 1) {
|
|
996
|
+
const el = fiber.dom;
|
|
997
|
+
if (el.hasAttribute('data-ryunix-hydrate-boundary'))
|
|
998
|
+
return el;
|
|
1202
999
|
}
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1000
|
+
let child = fiber.child || null;
|
|
1001
|
+
while (child) {
|
|
1002
|
+
if (child.dom && child.dom.nodeType === 1) {
|
|
1003
|
+
const el = child.dom;
|
|
1004
|
+
if (el.hasAttribute('data-ryunix-hydrate-boundary'))
|
|
1005
|
+
return el;
|
|
1006
|
+
}
|
|
1007
|
+
child = child.child || child.sibling || null;
|
|
1206
1008
|
}
|
|
1009
|
+
return null;
|
|
1207
1010
|
};
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
* @typedef {import('../types/internal.js').RyunixRootFiber} RyunixRootFiber
|
|
1217
|
-
* @typedef {import('../types/internal.js').RyunixComponent} RyunixComponent
|
|
1218
|
-
*/
|
|
1219
|
-
/**
|
|
1220
|
-
* @typedef {{ route: RyunixRoute | { component: RyunixComponent | null }, params: Record<string, string | string[]> }} RouteMatch
|
|
1221
|
-
*/
|
|
1222
|
-
/**
|
|
1223
|
-
* @param {unknown[] | undefined} oldDeps
|
|
1224
|
-
* @param {unknown[] | undefined} newDeps
|
|
1225
|
-
* @returns {boolean}
|
|
1226
|
-
*/
|
|
1227
|
-
const haveDepsChanged = (oldDeps, newDeps) => {
|
|
1228
|
-
if (!oldDeps || !newDeps)
|
|
1229
|
-
return true;
|
|
1230
|
-
if (oldDeps.length !== newDeps.length)
|
|
1231
|
-
return true;
|
|
1232
|
-
return oldDeps.some((dep, i) => !Object.is(dep, newDeps[i]));
|
|
1011
|
+
const skipHydrationSubtree = (cursor, boundaryRoot) => {
|
|
1012
|
+
if (!cursor || !boundaryRoot)
|
|
1013
|
+
return cursor;
|
|
1014
|
+
if (cursor === boundaryRoot)
|
|
1015
|
+
return boundaryRoot.nextSibling;
|
|
1016
|
+
if (boundaryRoot.contains(cursor))
|
|
1017
|
+
return boundaryRoot.nextSibling;
|
|
1018
|
+
return cursor;
|
|
1233
1019
|
};
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
* @returns {[unknown, (action: unknown, priority?: number) => void]}
|
|
1238
|
-
*/
|
|
1239
|
-
const useStore = (initialState, priority = getCurrentPriority()) => {
|
|
1240
|
-
// SSR safety check - more reliable than state.isServerRendering
|
|
1241
|
-
if (typeof window === 'undefined') {
|
|
1242
|
-
return [
|
|
1243
|
-
is.function(initialState)
|
|
1244
|
-
? /** @type {() => unknown} */ initialState()
|
|
1245
|
-
: initialState,
|
|
1246
|
-
() => { },
|
|
1247
|
-
];
|
|
1248
|
-
}
|
|
1020
|
+
const enqueueScopedRecovery = (boundaryFiber, boundaryDom, resumeCursor) => {
|
|
1021
|
+
if (!boundaryFiber || !boundaryDom)
|
|
1022
|
+
return;
|
|
1249
1023
|
const state = getState();
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
* @param {unknown} action
|
|
1261
|
-
*/
|
|
1262
|
-
const reducer = (state, action) => is.function(action) ? action(state) : action;
|
|
1263
|
-
return useReducer(reducer, initialState, undefined, priority);
|
|
1024
|
+
const queue = state.scopedRecoveryQueue || [];
|
|
1025
|
+
queue.push({
|
|
1026
|
+
boundaryFiber,
|
|
1027
|
+
boundaryDom,
|
|
1028
|
+
resumeCursor,
|
|
1029
|
+
element: (Array.isArray(boundaryFiber.props?.children)
|
|
1030
|
+
? boundaryFiber.props.children[0]
|
|
1031
|
+
: boundaryFiber.props?.children),
|
|
1032
|
+
});
|
|
1033
|
+
state.scopedRecoveryQueue = queue;
|
|
1264
1034
|
};
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
* @param {unknown} initialState
|
|
1268
|
-
* @param {((initial: unknown) => unknown)=} [init]
|
|
1269
|
-
* @param {number} [defaultPriority]
|
|
1270
|
-
* @returns {[unknown, (action: unknown, priority?: number) => void]}
|
|
1271
|
-
*/
|
|
1272
|
-
const useReducer = (reducer, initialState, init, defaultPriority = getCurrentPriority()) => {
|
|
1273
|
-
// SSR safety check - more reliable than state.isServerRendering
|
|
1274
|
-
if (typeof window === 'undefined') {
|
|
1275
|
-
return [init ? init(initialState) : initialState, () => { }];
|
|
1276
|
-
}
|
|
1035
|
+
|
|
1036
|
+
const updateFunctionComponent = (fiber) => {
|
|
1277
1037
|
const state = getState();
|
|
1278
|
-
|
|
1279
|
-
|
|
1038
|
+
state.wipFiber = fiber;
|
|
1039
|
+
state.hookIndex = 0;
|
|
1040
|
+
fiber.hooks = [];
|
|
1041
|
+
const componentType = fiber.type;
|
|
1042
|
+
if (state.hydrationRecover &&
|
|
1043
|
+
componentType.ryunix_type === 'RYUNIX_ERROR_BOUNDARY') {
|
|
1044
|
+
fiber.stateError = undefined;
|
|
1280
1045
|
}
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
const wipFiber = /** @type {RyunixFiber} */ state.wipFiber;
|
|
1284
|
-
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
1285
|
-
const hook = {
|
|
1286
|
-
hookID: hookIndex,
|
|
1287
|
-
type: RYUNIX_TYPES.RYUNIX_STORE,
|
|
1288
|
-
state: oldHook ? oldHook.state : init ? init(initialState) : initialState,
|
|
1289
|
-
queue: /** @type {unknown[]} */ [],
|
|
1290
|
-
};
|
|
1291
|
-
if (oldHook?.queue) {
|
|
1292
|
-
oldHook.queue.forEach(
|
|
1293
|
-
/** @param {unknown} action */ (action) => {
|
|
1294
|
-
try {
|
|
1295
|
-
hook.state = reducer(hook.state, action);
|
|
1296
|
-
}
|
|
1297
|
-
catch (error) {
|
|
1298
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
1299
|
-
console.error('Error in reducer:', error);
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
});
|
|
1046
|
+
if (state.isHydrating) {
|
|
1047
|
+
fiber.effectTag = EFFECT_TAGS.HYDRATE;
|
|
1303
1048
|
}
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1049
|
+
if (componentType._isMemo && fiber.alternate) {
|
|
1050
|
+
const { children: _pc, ...prevRest } = fiber.alternate.props || {};
|
|
1051
|
+
const { children: _nc, ...nextRest } = fiber.props || {};
|
|
1052
|
+
if (componentType._arePropsEqual?.(prevRest, nextRest)) {
|
|
1053
|
+
fiber.hooks = fiber.alternate.hooks;
|
|
1054
|
+
const oldChild = fiber.alternate.child;
|
|
1055
|
+
if (oldChild) {
|
|
1056
|
+
oldChild.parent = fiber;
|
|
1057
|
+
fiber.child = oldChild;
|
|
1309
1058
|
}
|
|
1310
1059
|
return;
|
|
1311
1060
|
}
|
|
1312
|
-
hook.queue.push(action);
|
|
1313
|
-
const currentState = getState();
|
|
1314
|
-
const activeRoot =
|
|
1315
|
-
/** @type {RyunixRootFiber | null | undefined} */ currentState.currentRoot ||
|
|
1316
|
-
currentState.wipRoot;
|
|
1317
|
-
if (!activeRoot)
|
|
1318
|
-
return;
|
|
1319
|
-
const newRoot = /** @type {RyunixRootFiber} */ {
|
|
1320
|
-
dom: activeRoot.dom,
|
|
1321
|
-
props: activeRoot.props,
|
|
1322
|
-
alternate:
|
|
1323
|
-
/** @type {RyunixRootFiber | null} */ currentState.currentRoot || null,
|
|
1324
|
-
};
|
|
1325
|
-
queueUpdate(() => scheduleWork$1(newRoot, priority));
|
|
1326
|
-
};
|
|
1327
|
-
wipFiber.hooks[hookIndex] = hook;
|
|
1328
|
-
state.hookIndex++;
|
|
1329
|
-
return [hook.state, dispatch];
|
|
1330
|
-
};
|
|
1331
|
-
/**
|
|
1332
|
-
* The `useEffect` function in JavaScript is used to manage side effects in functional components by
|
|
1333
|
-
* comparing dependencies and executing a callback function when dependencies change.
|
|
1334
|
-
* @param callback - The `callback` parameter in the `useEffect` function is a function that will be
|
|
1335
|
-
* executed as the effect. This function can perform side effects like data fetching, subscriptions, or
|
|
1336
|
-
* DOM manipulations.
|
|
1337
|
-
* @param deps - The `deps` parameter in the `useEffect` function stands for dependencies. It is an
|
|
1338
|
-
* optional array that contains values that the effect depends on. The effect will only re-run if any
|
|
1339
|
-
* of the values in the `deps` array have changed since the last render. If the `deps` array
|
|
1340
|
-
* @param {() => void | (() => void)} callback
|
|
1341
|
-
* @param {unknown[] | undefined} deps
|
|
1342
|
-
* @returns {void}
|
|
1343
|
-
*/
|
|
1344
|
-
const useEffect = (callback, deps) => {
|
|
1345
|
-
// SSR safety check - more reliable than state.isServerRendering
|
|
1346
|
-
if (typeof window === 'undefined') {
|
|
1347
|
-
return;
|
|
1348
|
-
}
|
|
1349
|
-
const state = getState();
|
|
1350
|
-
if (state.isServerRendering) {
|
|
1351
|
-
return;
|
|
1352
1061
|
}
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1062
|
+
const children = [
|
|
1063
|
+
componentType(fiber.props),
|
|
1064
|
+
];
|
|
1065
|
+
if (componentType._contextId && fiber.props?.value !== undefined) {
|
|
1066
|
+
fiber._contextId = componentType._contextId;
|
|
1067
|
+
fiber._contextValue = fiber.props.value;
|
|
1356
1068
|
}
|
|
1357
|
-
|
|
1358
|
-
|
|
1069
|
+
reconcileChildren(fiber, children);
|
|
1070
|
+
};
|
|
1071
|
+
const isUnderClientOnlyBoundary = (fiber) => {
|
|
1072
|
+
let current = fiber?.parent || null;
|
|
1073
|
+
while (current) {
|
|
1074
|
+
if (current._hydrateClientOnly)
|
|
1075
|
+
return true;
|
|
1076
|
+
current = current.parent || null;
|
|
1359
1077
|
}
|
|
1360
|
-
|
|
1361
|
-
const wipFiber = /** @type {RyunixFiber} */ state.wipFiber;
|
|
1362
|
-
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
1363
|
-
const hasChanged = haveDepsChanged(oldHook?.deps, deps);
|
|
1364
|
-
const hook = {
|
|
1365
|
-
hookID: hookIndex,
|
|
1366
|
-
type: RYUNIX_TYPES.RYUNIX_EFFECT,
|
|
1367
|
-
deps,
|
|
1368
|
-
effect: hasChanged ? callback : null,
|
|
1369
|
-
cancel: oldHook?.cancel,
|
|
1370
|
-
};
|
|
1371
|
-
wipFiber.hooks[hookIndex] = hook;
|
|
1372
|
-
state.hookIndex++;
|
|
1078
|
+
return false;
|
|
1373
1079
|
};
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
* @returns {{ current: unknown }}
|
|
1381
|
-
*/
|
|
1382
|
-
const useRef = (initialValue) => {
|
|
1383
|
-
// SSR safety check - more reliable than state.isServerRendering
|
|
1384
|
-
if (typeof window === 'undefined') {
|
|
1385
|
-
return { current: initialValue };
|
|
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;
|
|
1386
1086
|
}
|
|
1087
|
+
return false;
|
|
1088
|
+
};
|
|
1089
|
+
const normalizeChildNodes = (children) => {
|
|
1090
|
+
if (children == null)
|
|
1091
|
+
return [];
|
|
1092
|
+
return Array.isArray(children) ? children : [children];
|
|
1093
|
+
};
|
|
1094
|
+
const updateHostComponent = (fiber) => {
|
|
1387
1095
|
const state = getState();
|
|
1388
|
-
if (
|
|
1389
|
-
|
|
1096
|
+
if (fiber.type === RYUNIX_TYPES.RYUNIX_CONTEXT) {
|
|
1097
|
+
fiber._contextId = fiber.props?._contextId;
|
|
1098
|
+
fiber._contextValue = fiber.props?.value;
|
|
1390
1099
|
}
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
hookID: hookIndex,
|
|
1397
|
-
type: RYUNIX_TYPES.RYUNIX_REF,
|
|
1398
|
-
value: oldHook
|
|
1399
|
-
? /** @type {{ value: { current: unknown } }} */ oldHook.value
|
|
1400
|
-
: { current: initialValue },
|
|
1401
|
-
};
|
|
1402
|
-
wipFiber.hooks[hookIndex] = hook;
|
|
1403
|
-
state.hookIndex++;
|
|
1404
|
-
return /** @type {{ current: unknown }} */ hook.value;
|
|
1405
|
-
};
|
|
1406
|
-
/**
|
|
1407
|
-
* The useMemo function in JavaScript is used to memoize the result of a computation based on
|
|
1408
|
-
* dependencies.
|
|
1409
|
-
* @param compute - The `compute` parameter in the `useMemo` function is a callback function that
|
|
1410
|
-
* calculates the value that `useMemo` will memoize and return. This function will be called to compute
|
|
1411
|
-
* the memoized value when necessary.
|
|
1412
|
-
* @param deps - The `deps` parameter in the `useMemo` function refers to an array of dependencies.
|
|
1413
|
-
* These dependencies are used to determine whether the memoized value needs to be recalculated or if
|
|
1414
|
-
* the previously calculated value can be reused. The `useMemo` hook will recompute the memoized value
|
|
1415
|
-
* only if
|
|
1416
|
-
* @param {() => unknown} compute
|
|
1417
|
-
* @param {unknown[]} deps
|
|
1418
|
-
* @returns {unknown}
|
|
1419
|
-
*/
|
|
1420
|
-
const useMemo = (compute, deps) => {
|
|
1421
|
-
// SSR safety check - more reliable than state.isServerRendering
|
|
1422
|
-
if (typeof window === 'undefined') {
|
|
1423
|
-
return compute();
|
|
1424
|
-
}
|
|
1425
|
-
const state = getState();
|
|
1426
|
-
if (state.isServerRendering) {
|
|
1427
|
-
return compute();
|
|
1428
|
-
}
|
|
1429
|
-
validateHookContext();
|
|
1430
|
-
if (!is.function(compute)) {
|
|
1431
|
-
throw new Error('useMemo callback must be a function');
|
|
1432
|
-
}
|
|
1433
|
-
if (!Array.isArray(deps)) {
|
|
1434
|
-
throw new Error('useMemo requires a dependencies array');
|
|
1100
|
+
const isPassthrough = fiber.type === RYUNIX_TYPES.RYUNIX_FRAGMENT ||
|
|
1101
|
+
fiber.type === RYUNIX_TYPES.RYUNIX_CONTEXT ||
|
|
1102
|
+
fiber.type === Symbol.for('ryunix.portal');
|
|
1103
|
+
if (state.isHydrating && isPassthrough) {
|
|
1104
|
+
fiber.effectTag = EFFECT_TAGS.HYDRATE;
|
|
1435
1105
|
}
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
1439
|
-
let value;
|
|
1440
|
-
if (oldHook && !haveDepsChanged(oldHook.deps, deps)) {
|
|
1441
|
-
value = /** @type {{ value?: unknown }} */ oldHook.value;
|
|
1106
|
+
else if (state.isHydrating && isUnderServerPreserveBoundary(fiber)) {
|
|
1107
|
+
return;
|
|
1442
1108
|
}
|
|
1443
|
-
else {
|
|
1444
|
-
|
|
1445
|
-
|
|
1109
|
+
else if (state.isHydrating && isUnderClientOnlyBoundary(fiber)) {
|
|
1110
|
+
if (!fiber.dom) {
|
|
1111
|
+
fiber.dom = createDom(fiber);
|
|
1112
|
+
fiber.effectTag = EFFECT_TAGS.PLACEMENT;
|
|
1446
1113
|
}
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1114
|
+
}
|
|
1115
|
+
else if (!fiber.dom) {
|
|
1116
|
+
if (state.isHydrating && state.hydrateCursor) {
|
|
1117
|
+
const domNode = state.hydrateCursor;
|
|
1118
|
+
const isText = fiber.type === RYUNIX_TYPES.TEXT_ELEMENT && domNode.nodeType === 3;
|
|
1119
|
+
const isElement = typeof fiber.type === 'string' &&
|
|
1120
|
+
domNode.nodeType === 1 &&
|
|
1121
|
+
domNode.tagName.toLowerCase() === fiber.type.toLowerCase();
|
|
1122
|
+
if (isText || isElement) {
|
|
1123
|
+
fiber.dom = domNode;
|
|
1124
|
+
fiber.effectTag = EFFECT_TAGS.HYDRATE;
|
|
1125
|
+
if (isText &&
|
|
1126
|
+
fiber.props?.nodeValue != null &&
|
|
1127
|
+
domNode.nodeValue !== String(fiber.props.nodeValue)) {
|
|
1128
|
+
domNode.nodeValue = String(fiber.props.nodeValue);
|
|
1129
|
+
logHydrationRecoverable('text');
|
|
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
|
+
}
|
|
1138
|
+
if (isElement &&
|
|
1139
|
+
domNode.hasAttribute('data-ryunix-hydrate-boundary')) {
|
|
1140
|
+
fiber._hydrateClientOnly = true;
|
|
1141
|
+
}
|
|
1142
|
+
state.hydrateCursor = nextValidSibling$1(domNode.firstChild);
|
|
1450
1143
|
}
|
|
1451
|
-
|
|
1144
|
+
else {
|
|
1145
|
+
const policy = getHydrationPolicy();
|
|
1146
|
+
const detail = `Mismatch at ${getTypeLabel(fiber.type ?? 'unknown')}. Expected ${domNode.nodeType === 1 ? domNode.tagName : 'text'} but got ${String(fiber.type)}.`;
|
|
1147
|
+
const boundaryFiber = findNearestHydrationBoundary(fiber);
|
|
1148
|
+
const boundaryDom = (boundaryFiber ? getBoundaryDom(boundaryFiber) : null) ??
|
|
1149
|
+
findBoundaryDomFromNode(state.hydrateCursor);
|
|
1150
|
+
if (policy.recover === 'boundary' && boundaryFiber && boundaryDom) {
|
|
1151
|
+
logHydrationBoundaryMismatch(detail);
|
|
1152
|
+
enqueueScopedRecovery(boundaryFiber, boundaryDom, state.hydrateCursor ?? null);
|
|
1153
|
+
state.hydrateCursor = skipHydrationSubtree(state.hydrateCursor ?? null, boundaryDom);
|
|
1154
|
+
fiber.dom = createDom(fiber);
|
|
1155
|
+
fiber.effectTag = EFFECT_TAGS.PLACEMENT;
|
|
1156
|
+
}
|
|
1157
|
+
else if (policy.recover === 'none') {
|
|
1158
|
+
logHydrationFatal(detail);
|
|
1159
|
+
state.isHydrating = false;
|
|
1160
|
+
state.hydrateCursor = null;
|
|
1161
|
+
fiber.dom = createDom(fiber);
|
|
1162
|
+
fiber.effectTag = EFFECT_TAGS.PLACEMENT;
|
|
1163
|
+
}
|
|
1164
|
+
else {
|
|
1165
|
+
logHydrationMismatch(detail);
|
|
1166
|
+
state.isHydrating = false;
|
|
1167
|
+
state.hydrationFailed = true;
|
|
1168
|
+
state.hydrateCursor = null;
|
|
1169
|
+
fiber.dom = createDom(fiber);
|
|
1170
|
+
fiber.effectTag = EFFECT_TAGS.PLACEMENT;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
else {
|
|
1175
|
+
fiber.dom = createDom(fiber);
|
|
1452
1176
|
}
|
|
1453
1177
|
}
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
};
|
|
1460
|
-
wipFiber.hooks[hookIndex] = hook;
|
|
1461
|
-
state.hookIndex++;
|
|
1462
|
-
return value;
|
|
1178
|
+
if (fiber._hydratePreserveServer) {
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
const children = normalizeChildNodes(fiber.props?.children);
|
|
1182
|
+
reconcileChildren(fiber, children);
|
|
1463
1183
|
};
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
const
|
|
1477
|
-
if (
|
|
1478
|
-
|
|
1184
|
+
const getTypeLabel = (type) => {
|
|
1185
|
+
if (typeof type === 'symbol')
|
|
1186
|
+
return type.description || type.toString();
|
|
1187
|
+
if (typeof type === 'function')
|
|
1188
|
+
return type.name || 'anonymous';
|
|
1189
|
+
return String(type);
|
|
1190
|
+
};
|
|
1191
|
+
|
|
1192
|
+
let scheduleWorkFn = null;
|
|
1193
|
+
const setScheduleWork = (fn) => {
|
|
1194
|
+
scheduleWorkFn = fn;
|
|
1195
|
+
};
|
|
1196
|
+
const scheduleWork$1 = (root, priority) => {
|
|
1197
|
+
if (scheduleWorkFn) {
|
|
1198
|
+
return scheduleWorkFn(root, priority);
|
|
1479
1199
|
}
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
const Provider = ({ value, children }) => {
|
|
1498
|
-
return createElement(RYUNIX_TYPES.RYUNIX_CONTEXT, { value, children, _contextId: contextId }, ...flattenArray([children]));
|
|
1499
|
-
};
|
|
1500
|
-
Provider._contextId = contextId;
|
|
1501
|
-
/** @param {string | symbol} [ctxID] */
|
|
1502
|
-
const useContext = (ctxID = contextId) => {
|
|
1503
|
-
const state = getState();
|
|
1504
|
-
if (state.isServerRendering) {
|
|
1505
|
-
const ssrContexts =
|
|
1506
|
-
/** @type {Record<string | symbol, unknown> | undefined} */ state.ssrContexts;
|
|
1507
|
-
return ssrContexts && ssrContexts[ctxID] !== undefined
|
|
1508
|
-
? ssrContexts[ctxID]
|
|
1509
|
-
: defaultValue;
|
|
1510
|
-
}
|
|
1511
|
-
validateHookContext();
|
|
1512
|
-
/** @type {RyunixFiber | null | undefined} */
|
|
1513
|
-
let fiber = /** @type {RyunixFiber} */ state.wipFiber;
|
|
1514
|
-
while (fiber) {
|
|
1515
|
-
if (fiber._contextId === ctxID && fiber._contextValue !== undefined) {
|
|
1516
|
-
return fiber._contextValue;
|
|
1517
|
-
}
|
|
1518
|
-
const fiberType = fiber.type;
|
|
1519
|
-
if (fiberType?._contextId === ctxID && fiber.props?.value !== undefined) {
|
|
1520
|
-
return fiber.props.value;
|
|
1521
|
-
}
|
|
1522
|
-
fiber = fiber.parent;
|
|
1523
|
-
}
|
|
1524
|
-
return defaultValue;
|
|
1525
|
-
};
|
|
1526
|
-
return {
|
|
1527
|
-
Provider:
|
|
1528
|
-
/** @type {RyunixComponent & { _contextId?: string | symbol }} */ Provider,
|
|
1529
|
-
useContext,
|
|
1200
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1201
|
+
console.warn('[Ryunix] scheduleWork called before being initialized.');
|
|
1202
|
+
}
|
|
1203
|
+
};
|
|
1204
|
+
|
|
1205
|
+
const getRootChild = (children) => {
|
|
1206
|
+
if (children == null)
|
|
1207
|
+
return undefined;
|
|
1208
|
+
return Array.isArray(children) ? children[0] : children;
|
|
1209
|
+
};
|
|
1210
|
+
const renderSubtree = (element, container) => {
|
|
1211
|
+
clearContainer(container);
|
|
1212
|
+
const root = {
|
|
1213
|
+
dom: container,
|
|
1214
|
+
props: { children: [element] },
|
|
1215
|
+
isHydrating: false,
|
|
1216
|
+
hydrateCursor: null,
|
|
1530
1217
|
};
|
|
1218
|
+
scheduleWork$1(root, undefined);
|
|
1531
1219
|
};
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
const query = {};
|
|
1542
|
-
for (const [key, value] of searchParams.entries()) {
|
|
1543
|
-
query[key] = value;
|
|
1220
|
+
const recoverScopedHydrationFailures = () => {
|
|
1221
|
+
const state = getState();
|
|
1222
|
+
const queue = state.scopedRecoveryQueue;
|
|
1223
|
+
if (!queue?.length)
|
|
1224
|
+
return;
|
|
1225
|
+
state.scopedRecoveryQueue = [];
|
|
1226
|
+
for (const item of queue) {
|
|
1227
|
+
logHydrationBoundaryRecovery();
|
|
1228
|
+
renderSubtree(item.element, item.boundaryDom);
|
|
1544
1229
|
}
|
|
1545
|
-
return query;
|
|
1546
1230
|
};
|
|
1547
|
-
|
|
1548
|
-
* The function `useHash` in JavaScript is used to manage and update the hash portion of the URL in a
|
|
1549
|
-
* web application.
|
|
1550
|
-
* @returns {string}
|
|
1551
|
-
*/
|
|
1552
|
-
const useHash = () => {
|
|
1553
|
-
if (typeof window === 'undefined')
|
|
1554
|
-
return '';
|
|
1555
|
-
const [hash, setHash] = useStore(window.location.hash);
|
|
1556
|
-
useEffect(() => {
|
|
1557
|
-
const onHashChange = () => setHash(window.location.hash);
|
|
1558
|
-
window.addEventListener('hashchange', onHashChange);
|
|
1559
|
-
return () => window.removeEventListener('hashchange', onHashChange);
|
|
1560
|
-
}, []);
|
|
1561
|
-
return /** @type {string} */ hash;
|
|
1562
|
-
};
|
|
1563
|
-
/**
|
|
1564
|
-
* The `useMetadata` function in JavaScript is used to dynamically update metadata tags in the document
|
|
1565
|
-
* head based on provided tags and options.
|
|
1566
|
-
* @param [tags] - The `tags` parameter in the `useMetadata` function is an object that contains
|
|
1567
|
-
* metadata information for the webpage. It can include properties like `pageTitle`, `canonical`, and
|
|
1568
|
-
* other custom metadata tags like `og:title`, `og:description`, `twitter:title`,
|
|
1569
|
-
* `twitter:description`, etc. These tags
|
|
1570
|
-
* @param [options] - The `options` parameter in the `useMetadata` function is an object that can
|
|
1571
|
-
* contain the following properties:
|
|
1572
|
-
* - `title`: An object that can have the following properties:
|
|
1573
|
-
* - `template`: A string that defines the template for the page title. It can include a placeholder
|
|
1574
|
-
* `%s` that will be replaced with the actual page title.
|
|
1575
|
-
* - `prefix`: A string that will be used as the default title if no specific page title is provided.
|
|
1576
|
-
* This hook can't be reached by google crawler.
|
|
1577
|
-
* @param {RyunixMetadataTags} [tags]
|
|
1578
|
-
* @param {RyunixMetadataOptions} [options]
|
|
1579
|
-
* @returns {void}
|
|
1580
|
-
*/
|
|
1581
|
-
const useMetadata = (tags = {}, options = {}) => {
|
|
1231
|
+
const recoverHydrationFailureIfNeeded = () => {
|
|
1582
1232
|
const state = getState();
|
|
1583
|
-
if (state.
|
|
1584
|
-
state.ssrMetadata = { ...state.ssrMetadata, ...tags };
|
|
1233
|
+
if (!state.hydrationFailed || state.hydrationRecover)
|
|
1585
1234
|
return;
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1235
|
+
const policy = getHydrationPolicy();
|
|
1236
|
+
if (policy.recover === 'none')
|
|
1237
|
+
return;
|
|
1238
|
+
const container = state.containerRoot || state.currentRoot?.dom;
|
|
1239
|
+
const element = getRootChild(state.currentRoot?.props?.children);
|
|
1240
|
+
if (!container || element == null)
|
|
1241
|
+
return;
|
|
1242
|
+
state.hydrationRecover = true;
|
|
1243
|
+
state.hydrationFailed = false;
|
|
1244
|
+
logHydrationFailure('');
|
|
1245
|
+
logHydrationRecovery();
|
|
1246
|
+
renderSubtree(element, container);
|
|
1247
|
+
};
|
|
1248
|
+
const runHydrationRecovery = () => {
|
|
1249
|
+
const state = getState();
|
|
1250
|
+
recoverScopedHydrationFailures();
|
|
1251
|
+
recoverHydrationFailureIfNeeded();
|
|
1252
|
+
state.hydrationRecover = false;
|
|
1253
|
+
};
|
|
1254
|
+
|
|
1255
|
+
let workQueue = [];
|
|
1256
|
+
let isWorkLoopScheduled = false;
|
|
1257
|
+
function performUnitOfWork(fiber) {
|
|
1258
|
+
const state = getState();
|
|
1259
|
+
const isFunctionComponent = fiber.type instanceof Function || typeof fiber.type === 'function';
|
|
1260
|
+
try {
|
|
1261
|
+
if (isFunctionComponent) {
|
|
1262
|
+
updateFunctionComponent(fiber);
|
|
1599
1263
|
}
|
|
1600
1264
|
else {
|
|
1601
|
-
|
|
1265
|
+
updateHostComponent(fiber);
|
|
1602
1266
|
}
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1267
|
+
}
|
|
1268
|
+
catch (error) {
|
|
1269
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1270
|
+
console.error('[Ryunix ErrorBoundary] Caught error during render:', error);
|
|
1271
|
+
try {
|
|
1272
|
+
const fiberProps = fiber.props;
|
|
1273
|
+
const src = fiberProps?.__source;
|
|
1274
|
+
if (src && error && typeof error === 'object') {
|
|
1275
|
+
;
|
|
1276
|
+
error.__ryunix_source = src;
|
|
1277
|
+
}
|
|
1278
|
+
let targetFiber = fiber;
|
|
1279
|
+
while (!error.__ryunix_source && targetFiber) {
|
|
1280
|
+
const targetProps = targetFiber.props;
|
|
1281
|
+
if (targetProps?.__source) {
|
|
1282
|
+
;
|
|
1283
|
+
error.__ryunix_source = targetProps.__source;
|
|
1284
|
+
}
|
|
1285
|
+
targetFiber = targetFiber.parent;
|
|
1286
|
+
}
|
|
1610
1287
|
}
|
|
1611
|
-
|
|
1288
|
+
catch (_e) { }
|
|
1612
1289
|
}
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
meta.setAttribute(isProperty ? 'property' : 'name', key);
|
|
1622
|
-
document.head.appendChild(meta);
|
|
1290
|
+
let boundaryFiber = fiber.parent;
|
|
1291
|
+
let foundBoundary = false;
|
|
1292
|
+
while (boundaryFiber) {
|
|
1293
|
+
if (boundaryFiber.type &&
|
|
1294
|
+
boundaryFiber.type.ryunix_type ===
|
|
1295
|
+
'RYUNIX_ERROR_BOUNDARY') {
|
|
1296
|
+
foundBoundary = true;
|
|
1297
|
+
break;
|
|
1623
1298
|
}
|
|
1624
|
-
|
|
1625
|
-
});
|
|
1626
|
-
}, [JSON.stringify(tags), JSON.stringify(options)]);
|
|
1627
|
-
};
|
|
1628
|
-
// Router Context
|
|
1629
|
-
/** @type {ReturnType<typeof createContext>} */
|
|
1630
|
-
const RouterContext = createContext('ryunix.navigation',
|
|
1631
|
-
/** @type {RyunixRouterContextValue} */ {
|
|
1632
|
-
location: '/',
|
|
1633
|
-
params: {},
|
|
1634
|
-
query: {},
|
|
1635
|
-
/** @param {string} _path */
|
|
1636
|
-
navigate: (_path) => { },
|
|
1637
|
-
route: null,
|
|
1638
|
-
});
|
|
1639
|
-
/**
|
|
1640
|
-
* @param {RyunixRoute[]} routes
|
|
1641
|
-
* @param {string} path
|
|
1642
|
-
* @returns {RouteMatch}
|
|
1643
|
-
*/
|
|
1644
|
-
const findRoute = (routes, path) => {
|
|
1645
|
-
const pathname = path.split('?')[0].split('#')[0];
|
|
1646
|
-
const notFoundRoute = routes.find((route) => route.NotFound);
|
|
1647
|
-
const notFound = notFoundRoute
|
|
1648
|
-
? { route: { component: notFoundRoute.NotFound }, params: {} }
|
|
1649
|
-
: { route: { component: null }, params: {} };
|
|
1650
|
-
for (const route of routes) {
|
|
1651
|
-
if (route.subRoutes) {
|
|
1652
|
-
const childRoute = findRoute(route.subRoutes, path);
|
|
1653
|
-
if (childRoute)
|
|
1654
|
-
return childRoute;
|
|
1299
|
+
boundaryFiber = boundaryFiber.parent;
|
|
1655
1300
|
}
|
|
1656
|
-
if (
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
return
|
|
1668
|
-
})}$`);
|
|
1669
|
-
const matchPath = pathname.match(pattern);
|
|
1670
|
-
if (matchPath) {
|
|
1671
|
-
const params = keys.reduce((acc, keyObj, index) => {
|
|
1672
|
-
const val = matchPath[index + 1];
|
|
1673
|
-
acc[keyObj.key] = keyObj.isCatchAll && val ? val.split('/') : val;
|
|
1674
|
-
return acc;
|
|
1675
|
-
},
|
|
1676
|
-
/** @type {Record<string, string | string[]>} */ {});
|
|
1677
|
-
return { route, params };
|
|
1301
|
+
if (foundBoundary && boundaryFiber) {
|
|
1302
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1303
|
+
console.warn('[Ryunix ErrorBoundary] Recovering tree at nearest boundary.');
|
|
1304
|
+
}
|
|
1305
|
+
boundaryFiber.stateError = error;
|
|
1306
|
+
fiber.child = null;
|
|
1307
|
+
return boundaryFiber;
|
|
1308
|
+
}
|
|
1309
|
+
else {
|
|
1310
|
+
console.error('[Ryunix] Fatal Uncaught Error. No ErrorBoundary was found in the tree to handle this exception:\n', error);
|
|
1311
|
+
state.nextUnitOfWork = null;
|
|
1312
|
+
return null;
|
|
1678
1313
|
}
|
|
1679
1314
|
}
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
/**
|
|
1683
|
-
* @returns {string}
|
|
1684
|
-
*/
|
|
1685
|
-
const getSsrPathname = () => {
|
|
1686
|
-
const pathname = globalThis?.window?.location?.pathname;
|
|
1687
|
-
if (typeof pathname === 'string' && pathname) {
|
|
1688
|
-
return pathname.split('?')[0].split('#')[0];
|
|
1315
|
+
if (fiber.child) {
|
|
1316
|
+
return fiber.child;
|
|
1689
1317
|
}
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
// SSR: Return server-safe version without hooks
|
|
1700
|
-
if (typeof window === 'undefined') {
|
|
1701
|
-
const location = getSsrPathname();
|
|
1702
|
-
const currentRouteData = findRoute(routes, location);
|
|
1703
|
-
/** @type {RyunixRouterContextValue} */
|
|
1704
|
-
const contextValue = {
|
|
1705
|
-
location,
|
|
1706
|
-
params: currentRouteData.params || {},
|
|
1707
|
-
query: {},
|
|
1708
|
-
navigate: () => { },
|
|
1709
|
-
route: currentRouteData.route,
|
|
1710
|
-
};
|
|
1711
|
-
return createElement(
|
|
1712
|
-
/** @type {string | symbol | Function} */ RouterContext.Provider, { value: contextValue }, Fragment({ children }));
|
|
1318
|
+
let nextFiber = fiber;
|
|
1319
|
+
while (nextFiber) {
|
|
1320
|
+
if (state.isHydrating && nextFiber.dom) {
|
|
1321
|
+
state.hydrateCursor = nextValidSibling$1(nextFiber.dom.nextSibling);
|
|
1322
|
+
}
|
|
1323
|
+
if (nextFiber.sibling) {
|
|
1324
|
+
return nextFiber.sibling;
|
|
1325
|
+
}
|
|
1326
|
+
nextFiber = nextFiber.parent;
|
|
1713
1327
|
}
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1328
|
+
return null;
|
|
1329
|
+
}
|
|
1330
|
+
const workLoop = (deadline) => {
|
|
1331
|
+
const state = getState();
|
|
1332
|
+
let shouldYield = false;
|
|
1333
|
+
while ((state.nextUnitOfWork || workQueue.length > 0) && !shouldYield) {
|
|
1334
|
+
if (!state.nextUnitOfWork && workQueue.length > 0) {
|
|
1335
|
+
const nextRoot = workQueue.shift();
|
|
1336
|
+
if (!nextRoot)
|
|
1337
|
+
continue;
|
|
1338
|
+
state.wipRoot = nextRoot;
|
|
1339
|
+
state.nextUnitOfWork = nextRoot;
|
|
1340
|
+
state.deletions = [];
|
|
1341
|
+
if (nextRoot.isHydrating !== undefined) {
|
|
1342
|
+
state.isHydrating = nextRoot.isHydrating;
|
|
1343
|
+
state.hydrateCursor = nextRoot.hydrateCursor;
|
|
1344
|
+
}
|
|
1730
1345
|
}
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
};
|
|
1747
|
-
/**
|
|
1748
|
-
* The function `useRouter` returns the context of the Router for navigation in a Ryunix application.
|
|
1749
|
-
* @returns {RyunixRouterContextValue}
|
|
1750
|
-
*/
|
|
1751
|
-
const useRouter = () => {
|
|
1752
|
-
return RouterContext.useContext('ryunix.navigation');
|
|
1346
|
+
if (state.nextUnitOfWork) {
|
|
1347
|
+
state.nextUnitOfWork = performUnitOfWork(state.nextUnitOfWork);
|
|
1348
|
+
}
|
|
1349
|
+
shouldYield = deadline.timeRemaining() < 1;
|
|
1350
|
+
}
|
|
1351
|
+
if (!state.nextUnitOfWork && state.wipRoot) {
|
|
1352
|
+
commitRoot();
|
|
1353
|
+
runHydrationRecovery();
|
|
1354
|
+
}
|
|
1355
|
+
if (state.nextUnitOfWork || workQueue.length > 0) {
|
|
1356
|
+
rIC(workLoop);
|
|
1357
|
+
}
|
|
1358
|
+
else {
|
|
1359
|
+
isWorkLoopScheduled = false;
|
|
1360
|
+
}
|
|
1753
1361
|
};
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
const id = /** @type {string} */ hash.slice(1);
|
|
1767
|
-
const el = document.getElementById(id);
|
|
1768
|
-
if (el)
|
|
1769
|
-
el.scrollIntoView({ block: 'start', behavior: 'smooth' });
|
|
1362
|
+
const scheduleWork = (root, priority = getCurrentPriority()) => {
|
|
1363
|
+
const state = getState();
|
|
1364
|
+
if (state.wipRoot) {
|
|
1365
|
+
workQueue.push(root);
|
|
1366
|
+
}
|
|
1367
|
+
else {
|
|
1368
|
+
state.nextUnitOfWork = root;
|
|
1369
|
+
state.wipRoot = root;
|
|
1370
|
+
state.deletions = [];
|
|
1371
|
+
if (root.isHydrating !== undefined) {
|
|
1372
|
+
state.isHydrating = root.isHydrating;
|
|
1373
|
+
state.hydrateCursor = root.hydrateCursor;
|
|
1770
1374
|
}
|
|
1771
|
-
}
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1375
|
+
}
|
|
1376
|
+
state.hookIndex = 0;
|
|
1377
|
+
state.effects = [];
|
|
1378
|
+
if (!isWorkLoopScheduled) {
|
|
1379
|
+
isWorkLoopScheduled = true;
|
|
1380
|
+
if (priority <= Priority.USER_BLOCKING) {
|
|
1381
|
+
Promise.resolve().then(() => {
|
|
1382
|
+
workLoop({ timeRemaining: () => 10, didTimeout: true });
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
else {
|
|
1386
|
+
rIC(workLoop);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1780
1389
|
};
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
const
|
|
1787
|
-
|
|
1390
|
+
setScheduleWork(scheduleWork);
|
|
1391
|
+
|
|
1392
|
+
const render = (element, container) => {
|
|
1393
|
+
const state = getState();
|
|
1394
|
+
clearContainer(container);
|
|
1395
|
+
const root = {
|
|
1396
|
+
dom: container,
|
|
1397
|
+
props: {
|
|
1398
|
+
children: [element],
|
|
1399
|
+
},
|
|
1400
|
+
alternate: state.currentRoot,
|
|
1401
|
+
isHydrating: false,
|
|
1402
|
+
hydrateCursor: null,
|
|
1403
|
+
};
|
|
1404
|
+
scheduleWork(root);
|
|
1405
|
+
return root;
|
|
1788
1406
|
};
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1407
|
+
const SSR_ROOT_ATTR = 'data-ryunix-ssr-root';
|
|
1408
|
+
const nextValidSibling = (node) => {
|
|
1409
|
+
let next = node;
|
|
1410
|
+
while (next &&
|
|
1411
|
+
((next.nodeType === 3 && !next.nodeValue?.trim()) ||
|
|
1412
|
+
next.nodeType === 8 ||
|
|
1413
|
+
(next.nodeType === 1 &&
|
|
1414
|
+
next.hasAttribute('data-ryunix-ssr')))) {
|
|
1415
|
+
next = next.nextSibling;
|
|
1416
|
+
}
|
|
1417
|
+
return next;
|
|
1796
1418
|
};
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
return;
|
|
1809
|
-
}
|
|
1810
|
-
e.preventDefault();
|
|
1811
|
-
navigate(to);
|
|
1812
|
-
};
|
|
1813
|
-
const handleMouseEnter = () => {
|
|
1419
|
+
const hydrate = (element, container) => {
|
|
1420
|
+
const state = getState();
|
|
1421
|
+
state.containerRoot = container;
|
|
1422
|
+
const root = {
|
|
1423
|
+
dom: container,
|
|
1424
|
+
props: {
|
|
1425
|
+
children: [element],
|
|
1426
|
+
},
|
|
1427
|
+
alternate: state.currentRoot,
|
|
1428
|
+
isHydrating: true,
|
|
1429
|
+
hydrateCursor: nextValidSibling(container.firstChild),
|
|
1814
1430
|
};
|
|
1815
|
-
|
|
1816
|
-
return
|
|
1817
|
-
href: to,
|
|
1818
|
-
onClick: handleClick,
|
|
1819
|
-
onMouseEnter: handleMouseEnter,
|
|
1820
|
-
className: props.className || props['ryunix-class'],
|
|
1821
|
-
...cleanedProps,
|
|
1822
|
-
}, props.children);
|
|
1431
|
+
scheduleWork(root);
|
|
1432
|
+
return root;
|
|
1823
1433
|
};
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
const { location, navigate } = useRouter();
|
|
1832
|
-
const isActive = exact ? location === to : location.startsWith(to);
|
|
1833
|
-
/** @param {string | ((args: { isActive: boolean }) => string) | undefined} cls */
|
|
1834
|
-
const resolveClass = (cls) => typeof cls === 'function' ? cls({ isActive }) : cls || '';
|
|
1835
|
-
/** @param {MouseEvent} e */
|
|
1836
|
-
const handleClick = (e) => {
|
|
1837
|
-
if (e.button !== 0 || e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) {
|
|
1838
|
-
return;
|
|
1434
|
+
const init = (MainElement, root = '__ryunix', _components = {}) => {
|
|
1435
|
+
const state = getState();
|
|
1436
|
+
const container = document.getElementById(root);
|
|
1437
|
+
state.containerRoot = container;
|
|
1438
|
+
if (!container) {
|
|
1439
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1440
|
+
console.warn(`[Ryunix] init: container #${root} not found.`);
|
|
1839
1441
|
}
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
}
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
};
|
|
1873
|
-
return [state, dispatch];
|
|
1442
|
+
return undefined;
|
|
1443
|
+
}
|
|
1444
|
+
resetHydrationLogFlags();
|
|
1445
|
+
state.hydrationPolicy = getHydrationPolicy();
|
|
1446
|
+
state.scopedRecoveryQueue = [];
|
|
1447
|
+
state.hydrationRecover = false;
|
|
1448
|
+
state.isHydrating = false;
|
|
1449
|
+
state.hydrationFailed = false;
|
|
1450
|
+
if (state.currentRoot) {
|
|
1451
|
+
if (process.env.NODE_ENV !== 'production' && process.env.RYUNIX_DEBUG) {
|
|
1452
|
+
console.log(`[Ryunix Debug] init: existing root detected. Client render on #${root}`);
|
|
1453
|
+
}
|
|
1454
|
+
return render(MainElement, container);
|
|
1455
|
+
}
|
|
1456
|
+
const ssrEnabled = process.env.RYUNIX_SSR !== 'false';
|
|
1457
|
+
const isSsrPayload = ssrEnabled &&
|
|
1458
|
+
container.hasAttribute(SSR_ROOT_ATTR) &&
|
|
1459
|
+
container.hasChildNodes();
|
|
1460
|
+
if (process.env.NODE_ENV !== 'production' && process.env.RYUNIX_DEBUG) {
|
|
1461
|
+
console.log(`[Ryunix Debug] init: isSsrPayload=${isSsrPayload}, hasChildNodes=${container.hasChildNodes()}`);
|
|
1462
|
+
}
|
|
1463
|
+
if (isSsrPayload) {
|
|
1464
|
+
if (process.env.NODE_ENV !== 'production' && process.env.RYUNIX_DEBUG) {
|
|
1465
|
+
console.log(`[Ryunix Debug] init: hydrating SSR markup on #${root}`);
|
|
1466
|
+
}
|
|
1467
|
+
container.removeAttribute(SSR_ROOT_ATTR);
|
|
1468
|
+
return hydrate(MainElement, container);
|
|
1469
|
+
}
|
|
1470
|
+
if (process.env.NODE_ENV !== 'production' && process.env.RYUNIX_DEBUG) {
|
|
1471
|
+
console.log(`[Ryunix Debug] init: client render on #${root}`);
|
|
1472
|
+
}
|
|
1473
|
+
return render(MainElement, container);
|
|
1874
1474
|
};
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
setIsPending(false, Priority.LOW);
|
|
1888
|
-
});
|
|
1889
|
-
}, 0);
|
|
1890
|
-
};
|
|
1891
|
-
return [/** @type {boolean} */ isPending, startTransition];
|
|
1475
|
+
const safeRender = (component, props, onError) => {
|
|
1476
|
+
try {
|
|
1477
|
+
return component(props);
|
|
1478
|
+
}
|
|
1479
|
+
catch (error) {
|
|
1480
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1481
|
+
console.error('Component error:', error);
|
|
1482
|
+
}
|
|
1483
|
+
if (onError)
|
|
1484
|
+
onError(error);
|
|
1485
|
+
return null;
|
|
1486
|
+
}
|
|
1892
1487
|
};
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1488
|
+
|
|
1489
|
+
let isBatching = false;
|
|
1490
|
+
let pendingUpdates = [];
|
|
1491
|
+
function batchUpdates(callback) {
|
|
1492
|
+
const wasBatching = isBatching;
|
|
1493
|
+
isBatching = true;
|
|
1494
|
+
try {
|
|
1495
|
+
callback();
|
|
1496
|
+
}
|
|
1497
|
+
finally {
|
|
1498
|
+
isBatching = wasBatching;
|
|
1499
|
+
if (!isBatching && pendingUpdates.length > 0) {
|
|
1500
|
+
flushUpdates();
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
function queueUpdate(update) {
|
|
1505
|
+
pendingUpdates.push(update);
|
|
1506
|
+
if (!isBatching) {
|
|
1507
|
+
flushUpdates();
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
function flushUpdates() {
|
|
1511
|
+
if (pendingUpdates.length === 0)
|
|
1512
|
+
return;
|
|
1513
|
+
const updates = pendingUpdates;
|
|
1514
|
+
pendingUpdates = [];
|
|
1515
|
+
updates.forEach((update) => update());
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
process.env.NODE_ENV !== 'production';
|
|
1519
|
+
const validateHookContext = (hookName = 'A hook') => {
|
|
1520
|
+
const state = getState();
|
|
1521
|
+
if (!state.wipFiber) {
|
|
1522
|
+
throw new Error(`${hookName} can only be called inside function components. ` +
|
|
1523
|
+
'Make sure you are calling hooks at the top level of your component.');
|
|
1524
|
+
}
|
|
1525
|
+
const wipFiber = state.wipFiber;
|
|
1526
|
+
if (!Array.isArray(wipFiber.hooks)) {
|
|
1527
|
+
wipFiber.hooks = [];
|
|
1528
|
+
}
|
|
1907
1529
|
};
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
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;
|
|
1926
1578
|
}
|
|
1927
|
-
|
|
1928
|
-
|
|
1579
|
+
if (typeof value === 'string' && value.trim()) {
|
|
1580
|
+
tags[key] = value.trim();
|
|
1929
1581
|
}
|
|
1930
|
-
}
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
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;
|
|
1943
1599
|
}
|
|
1944
|
-
|
|
1945
|
-
|
|
1600
|
+
if (!pickString(merged.titleDefault) && pickString(base.title.default)) {
|
|
1601
|
+
merged.titleDefault = base.title.default;
|
|
1946
1602
|
}
|
|
1947
|
-
}
|
|
1948
|
-
return
|
|
1603
|
+
}
|
|
1604
|
+
return merged;
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
const haveDepsChanged = (oldDeps, newDeps) => {
|
|
1608
|
+
if (!oldDeps || !newDeps)
|
|
1609
|
+
return true;
|
|
1610
|
+
if (oldDeps.length !== newDeps.length)
|
|
1611
|
+
return true;
|
|
1612
|
+
return oldDeps.some((dep, i) => !Object.is(dep, newDeps[i]));
|
|
1949
1613
|
};
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
const
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1614
|
+
const useStore = (initialState, priority = getCurrentPriority()) => {
|
|
1615
|
+
if (typeof window === 'undefined') {
|
|
1616
|
+
return [
|
|
1617
|
+
is.function(initialState)
|
|
1618
|
+
? initialState()
|
|
1619
|
+
: initialState,
|
|
1620
|
+
() => { },
|
|
1621
|
+
];
|
|
1622
|
+
}
|
|
1623
|
+
const state = getState();
|
|
1624
|
+
if (state.isServerRendering) {
|
|
1625
|
+
return [
|
|
1626
|
+
is.function(initialState)
|
|
1627
|
+
? initialState()
|
|
1628
|
+
: initialState,
|
|
1629
|
+
() => { },
|
|
1630
|
+
];
|
|
1631
|
+
}
|
|
1632
|
+
const reducer = (state, action) => is.function(action) ? action(state) : action;
|
|
1633
|
+
return useReducer(reducer, initialState, undefined, priority);
|
|
1634
|
+
};
|
|
1635
|
+
const useReducer = (reducer, initialState, init, defaultPriority = getCurrentPriority()) => {
|
|
1636
|
+
if (typeof window === 'undefined') {
|
|
1637
|
+
return [init ? init(initialState) : initialState, () => { }];
|
|
1638
|
+
}
|
|
1639
|
+
const state = getState();
|
|
1640
|
+
if (state.isServerRendering) {
|
|
1641
|
+
return [init ? init(initialState) : initialState, () => { }];
|
|
1642
|
+
}
|
|
1643
|
+
validateHookContext();
|
|
1644
|
+
const { hookIndex } = state;
|
|
1645
|
+
const wipFiber = state.wipFiber;
|
|
1646
|
+
if (!wipFiber.hooks)
|
|
1647
|
+
wipFiber.hooks = [];
|
|
1648
|
+
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
1649
|
+
const hook = {
|
|
1650
|
+
hookID: hookIndex,
|
|
1651
|
+
type: RYUNIX_TYPES.RYUNIX_STORE,
|
|
1652
|
+
state: oldHook ? oldHook.state : init ? init(initialState) : initialState,
|
|
1653
|
+
queue: [],
|
|
1967
1654
|
};
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1655
|
+
if (oldHook?.queue) {
|
|
1656
|
+
oldHook.queue.forEach((action) => {
|
|
1657
|
+
try {
|
|
1658
|
+
hook.state = reducer(hook.state, action);
|
|
1659
|
+
}
|
|
1660
|
+
catch (error) {
|
|
1661
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1662
|
+
console.error('Error in reducer:', error);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
});
|
|
1666
|
+
}
|
|
1667
|
+
const dispatch = (action, priority = defaultPriority) => {
|
|
1668
|
+
if (action === undefined) {
|
|
1669
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1670
|
+
console.warn('dispatch called with undefined action');
|
|
1671
|
+
}
|
|
1672
|
+
return;
|
|
1673
|
+
}
|
|
1674
|
+
hook.queue.push(action);
|
|
1675
|
+
const currentState = getState();
|
|
1676
|
+
const activeRoot = currentState.currentRoot ||
|
|
1677
|
+
currentState.wipRoot;
|
|
1678
|
+
if (!activeRoot)
|
|
1679
|
+
return;
|
|
1680
|
+
const newRoot = {
|
|
1681
|
+
dom: activeRoot.dom,
|
|
1682
|
+
props: activeRoot.props,
|
|
1683
|
+
alternate: currentState.currentRoot || null,
|
|
1684
|
+
};
|
|
1685
|
+
queueUpdate(() => scheduleWork$1(newRoot, priority));
|
|
1686
|
+
};
|
|
1687
|
+
wipFiber.hooks[hookIndex] = hook;
|
|
1688
|
+
state.hookIndex++;
|
|
1689
|
+
return [hook.state, dispatch];
|
|
1690
|
+
};
|
|
1691
|
+
const useEffect = (callback, deps) => {
|
|
1979
1692
|
if (typeof window === 'undefined') {
|
|
1980
1693
|
return;
|
|
1981
1694
|
}
|
|
@@ -1985,13 +1698,15 @@ const useLayoutEffect = (callback, deps) => {
|
|
|
1985
1698
|
}
|
|
1986
1699
|
validateHookContext();
|
|
1987
1700
|
if (!is.function(callback)) {
|
|
1988
|
-
throw new Error('
|
|
1701
|
+
throw new Error('useEffect callback must be a function');
|
|
1989
1702
|
}
|
|
1990
1703
|
if (deps !== undefined && !Array.isArray(deps)) {
|
|
1991
|
-
throw new Error('
|
|
1704
|
+
throw new Error('useEffect dependencies must be an array or undefined');
|
|
1992
1705
|
}
|
|
1993
1706
|
const { hookIndex } = state;
|
|
1994
|
-
const wipFiber =
|
|
1707
|
+
const wipFiber = state.wipFiber;
|
|
1708
|
+
if (!wipFiber.hooks)
|
|
1709
|
+
wipFiber.hooks = [];
|
|
1995
1710
|
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
1996
1711
|
const hasChanged = haveDepsChanged(oldHook?.deps, deps);
|
|
1997
1712
|
const hook = {
|
|
@@ -2000,789 +1715,505 @@ const useLayoutEffect = (callback, deps) => {
|
|
|
2000
1715
|
deps,
|
|
2001
1716
|
effect: hasChanged ? callback : null,
|
|
2002
1717
|
cancel: oldHook?.cancel,
|
|
2003
|
-
isLayout: true, // Flag to run synchronously during commit
|
|
2004
1718
|
};
|
|
2005
1719
|
wipFiber.hooks[hookIndex] = hook;
|
|
2006
1720
|
state.hookIndex++;
|
|
2007
1721
|
};
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
* to ensure deterministic IDs across multiple renders
|
|
2013
|
-
* @returns {void}
|
|
2014
|
-
*/
|
|
2015
|
-
const resetIdCounter = () => {
|
|
2016
|
-
idCounter = 0;
|
|
2017
|
-
};
|
|
2018
|
-
/**
|
|
2019
|
-
* useId - Generate a deterministic, unique ID that is stable across SSR and hydration.
|
|
2020
|
-
* @returns {string} A unique ID string
|
|
2021
|
-
*/
|
|
2022
|
-
const useId = () => {
|
|
1722
|
+
const useRef = (initialValue) => {
|
|
1723
|
+
if (typeof window === 'undefined') {
|
|
1724
|
+
return { current: initialValue };
|
|
1725
|
+
}
|
|
2023
1726
|
const state = getState();
|
|
2024
1727
|
if (state.isServerRendering) {
|
|
2025
|
-
|
|
2026
|
-
return `:r${idCounter++}:`;
|
|
1728
|
+
return { current: initialValue };
|
|
2027
1729
|
}
|
|
2028
1730
|
validateHookContext();
|
|
2029
1731
|
const { hookIndex } = state;
|
|
2030
|
-
const wipFiber =
|
|
1732
|
+
const wipFiber = state.wipFiber;
|
|
1733
|
+
if (!wipFiber.hooks)
|
|
1734
|
+
wipFiber.hooks = [];
|
|
2031
1735
|
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
2032
1736
|
const hook = {
|
|
2033
1737
|
hookID: hookIndex,
|
|
2034
1738
|
type: RYUNIX_TYPES.RYUNIX_REF,
|
|
2035
1739
|
value: oldHook
|
|
2036
|
-
?
|
|
2037
|
-
:
|
|
1740
|
+
? oldHook.value
|
|
1741
|
+
: { current: initialValue },
|
|
2038
1742
|
};
|
|
2039
1743
|
wipFiber.hooks[hookIndex] = hook;
|
|
2040
1744
|
state.hookIndex++;
|
|
2041
|
-
return
|
|
2042
|
-
};
|
|
2043
|
-
/**
|
|
2044
|
-
* useDebounce - Returns a debounced version of the value that only updates
|
|
2045
|
-
* after the specified delay has passed since the last change.
|
|
2046
|
-
* @param {unknown} value - Value to debounce
|
|
2047
|
-
* @param {number} delay - Delay in milliseconds (default: 300)
|
|
2048
|
-
* @returns {unknown} Debounced value
|
|
2049
|
-
*/
|
|
2050
|
-
const useDebounce = (value, delay = 300) => {
|
|
2051
|
-
const [debouncedValue, setDebouncedValue] = useStore(value);
|
|
2052
|
-
useEffect(() => {
|
|
2053
|
-
const timer = setTimeout(() => {
|
|
2054
|
-
setDebouncedValue(value);
|
|
2055
|
-
}, delay);
|
|
2056
|
-
return () => clearTimeout(timer);
|
|
2057
|
-
}, [value, delay]);
|
|
2058
|
-
return debouncedValue;
|
|
1745
|
+
return hook.value;
|
|
2059
1746
|
};
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
1747
|
+
const useMemo = (compute, deps) => {
|
|
1748
|
+
if (typeof window === 'undefined') {
|
|
1749
|
+
return compute();
|
|
1750
|
+
}
|
|
1751
|
+
const state = getState();
|
|
1752
|
+
if (state.isServerRendering) {
|
|
1753
|
+
return compute();
|
|
1754
|
+
}
|
|
1755
|
+
validateHookContext();
|
|
1756
|
+
if (!is.function(compute)) {
|
|
1757
|
+
throw new Error('useMemo callback must be a function');
|
|
1758
|
+
}
|
|
1759
|
+
if (!Array.isArray(deps)) {
|
|
1760
|
+
throw new Error('useMemo requires a dependencies array');
|
|
1761
|
+
}
|
|
1762
|
+
const { hookIndex } = state;
|
|
1763
|
+
const wipFiber = state.wipFiber;
|
|
1764
|
+
if (!wipFiber.hooks)
|
|
1765
|
+
wipFiber.hooks = [];
|
|
1766
|
+
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
1767
|
+
let value;
|
|
1768
|
+
if (oldHook && !haveDepsChanged(oldHook.deps, deps)) {
|
|
1769
|
+
value = oldHook.value;
|
|
1770
|
+
}
|
|
1771
|
+
else {
|
|
1772
|
+
try {
|
|
1773
|
+
value = compute();
|
|
2076
1774
|
}
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
return () => clearTimeout(timer);
|
|
1775
|
+
catch (error) {
|
|
1776
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1777
|
+
console.error('Error in useMemo computation:', error);
|
|
1778
|
+
}
|
|
1779
|
+
throw error;
|
|
2083
1780
|
}
|
|
2084
|
-
}
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
Children: Children,
|
|
2091
|
-
Link: Link,
|
|
2092
|
-
NavLink: NavLink,
|
|
2093
|
-
RouterProvider: RouterProvider,
|
|
2094
|
-
createContext: createContext,
|
|
2095
|
-
resetIdCounter: resetIdCounter,
|
|
2096
|
-
useCallback: useCallback,
|
|
2097
|
-
useDebounce: useDebounce,
|
|
2098
|
-
useDeferredValue: useDeferredValue,
|
|
2099
|
-
useEffect: useEffect,
|
|
2100
|
-
useHash: useHash,
|
|
2101
|
-
useId: useId,
|
|
2102
|
-
useLayoutEffect: useLayoutEffect,
|
|
2103
|
-
useMemo: useMemo,
|
|
2104
|
-
useMetadata: useMetadata,
|
|
2105
|
-
usePathname: usePathname,
|
|
2106
|
-
usePersistentStore: usePersistentStore,
|
|
2107
|
-
usePersitentStore: usePersistentStore,
|
|
2108
|
-
useQuery: useQuery,
|
|
2109
|
-
useReducer: useReducer,
|
|
2110
|
-
useRef: useRef,
|
|
2111
|
-
useRouter: useRouter,
|
|
2112
|
-
useSearchParams: useSearchParams,
|
|
2113
|
-
useStore: useStore,
|
|
2114
|
-
useStorePriority: useStorePriority,
|
|
2115
|
-
useSwitch: useSwitch,
|
|
2116
|
-
useThrottle: useThrottle,
|
|
2117
|
-
useTransition: useTransition
|
|
2118
|
-
});
|
|
2119
|
-
|
|
2120
|
-
const getHydrationPolicy = () => {
|
|
2121
|
-
const env = typeof process !== 'undefined' && process.env ? process.env : {};
|
|
2122
|
-
const recoverRaw = env.RYUNIX_HYDRATION_RECOVER || 'boundary';
|
|
2123
|
-
const boundariesRaw = env.RYUNIX_HYDRATION_BOUNDARIES || 'route';
|
|
2124
|
-
const strict = env.RYUNIX_HYDRATION_STRICT === 'true';
|
|
2125
|
-
const recover = recoverRaw === 'none' || recoverRaw === 'root' ? recoverRaw : 'boundary';
|
|
2126
|
-
const boundaries = boundariesRaw === 'server-only' || boundariesRaw === 'all-layouts'
|
|
2127
|
-
? boundariesRaw
|
|
2128
|
-
: 'route';
|
|
2129
|
-
return {
|
|
2130
|
-
recover,
|
|
2131
|
-
boundaries,
|
|
2132
|
-
strict,
|
|
1781
|
+
}
|
|
1782
|
+
const hook = {
|
|
1783
|
+
hookID: hookIndex,
|
|
1784
|
+
type: RYUNIX_TYPES.RYUNIX_MEMO,
|
|
1785
|
+
value,
|
|
1786
|
+
deps,
|
|
2133
1787
|
};
|
|
1788
|
+
wipFiber.hooks[hookIndex] = hook;
|
|
1789
|
+
state.hookIndex++;
|
|
1790
|
+
return value;
|
|
2134
1791
|
};
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
let current = fiber || null;
|
|
2139
|
-
while (current) {
|
|
2140
|
-
const props = current.props;
|
|
2141
|
-
if (props &&
|
|
2142
|
-
Object.prototype.hasOwnProperty.call(props, 'data-ryunix-hydrate-boundary')) {
|
|
2143
|
-
return current;
|
|
2144
|
-
}
|
|
2145
|
-
const type = current.type;
|
|
2146
|
-
const maybeTyped = type;
|
|
2147
|
-
if (type &&
|
|
2148
|
-
typeof type === 'function' &&
|
|
2149
|
-
(maybeTyped?.ryunix_type === 'RYUNIX_HYDRATION_BOUNDARY' ||
|
|
2150
|
-
maybeTyped?.ryunix_type === 'RYUNIX_SERVER_BOUNDARY')) {
|
|
2151
|
-
return current;
|
|
2152
|
-
}
|
|
2153
|
-
current = current.parent || null;
|
|
1792
|
+
const useCallback = (callback, deps) => {
|
|
1793
|
+
if (!is.function(callback)) {
|
|
1794
|
+
throw new Error('useCallback requires a function as first argument');
|
|
2154
1795
|
}
|
|
2155
|
-
return
|
|
1796
|
+
return useMemo(() => callback, deps);
|
|
2156
1797
|
};
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
const
|
|
2164
|
-
if (
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
if (child.dom && child.dom.nodeType === 1) {
|
|
2170
|
-
const el = child.dom;
|
|
2171
|
-
if (el.hasAttribute('data-ryunix-hydrate-boundary'))
|
|
2172
|
-
return el;
|
|
1798
|
+
const createContext = (contextId = RYUNIX_TYPES.RYUNIX_CONTEXT, defaultValue = {}) => {
|
|
1799
|
+
const Provider = ({ value, children, }) => {
|
|
1800
|
+
return createElement(RYUNIX_TYPES.RYUNIX_CONTEXT, { value, children, _contextId: contextId }, ...flattenArray([children]));
|
|
1801
|
+
};
|
|
1802
|
+
Provider._contextId = contextId;
|
|
1803
|
+
const useContext = (ctxID = contextId) => {
|
|
1804
|
+
const state = getState();
|
|
1805
|
+
if (state.isServerRendering) {
|
|
1806
|
+
const ssrContexts = state.ssrContexts;
|
|
1807
|
+
return ssrContexts && ssrContexts[ctxID] !== undefined
|
|
1808
|
+
? ssrContexts[ctxID]
|
|
1809
|
+
: defaultValue;
|
|
2173
1810
|
}
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
*/
|
|
2180
|
-
const skipHydrationSubtree = (cursor, boundaryRoot) => {
|
|
2181
|
-
if (!cursor || !boundaryRoot)
|
|
2182
|
-
return cursor;
|
|
2183
|
-
if (cursor === boundaryRoot)
|
|
2184
|
-
return boundaryRoot.nextSibling;
|
|
2185
|
-
if (boundaryRoot.contains(cursor))
|
|
2186
|
-
return boundaryRoot.nextSibling;
|
|
2187
|
-
return cursor;
|
|
2188
|
-
};
|
|
2189
|
-
/**
|
|
2190
|
-
*/
|
|
2191
|
-
const enqueueScopedRecovery = (boundaryFiber, boundaryDom, resumeCursor) => {
|
|
2192
|
-
if (!boundaryFiber || !boundaryDom)
|
|
2193
|
-
return;
|
|
2194
|
-
const state = getState();
|
|
2195
|
-
const queue = state.scopedRecoveryQueue || [];
|
|
2196
|
-
queue.push({
|
|
2197
|
-
boundaryFiber,
|
|
2198
|
-
boundaryDom,
|
|
2199
|
-
resumeCursor,
|
|
2200
|
-
element: (Array.isArray(boundaryFiber.props?.children)
|
|
2201
|
-
? boundaryFiber.props.children[0]
|
|
2202
|
-
: boundaryFiber.props?.children),
|
|
2203
|
-
});
|
|
2204
|
-
state.scopedRecoveryQueue = queue;
|
|
2205
|
-
};
|
|
2206
|
-
|
|
2207
|
-
/**
|
|
2208
|
-
* @typedef {import('../types/internal.js').RyunixFiber} RyunixFiber
|
|
2209
|
-
* @typedef {import('../types/internal.js').RyunixComponent} RyunixComponent
|
|
2210
|
-
* @typedef {import('../types/internal.js').RyunixNode} RyunixNode
|
|
2211
|
-
*/
|
|
2212
|
-
/**
|
|
2213
|
-
* @param {RyunixFiber} fiber
|
|
2214
|
-
*/
|
|
2215
|
-
const updateFunctionComponent = (fiber) => {
|
|
2216
|
-
const state = getState();
|
|
2217
|
-
state.wipFiber = fiber;
|
|
2218
|
-
state.hookIndex = 0;
|
|
2219
|
-
/** @type {RyunixFiber} */ state.wipFiber.hooks = [];
|
|
2220
|
-
if (state.isHydrating) {
|
|
2221
|
-
fiber.effectTag = EFFECT_TAGS.HYDRATE;
|
|
2222
|
-
}
|
|
2223
|
-
// Memo bailout: skip re-render if props haven't changed
|
|
2224
|
-
const componentType =
|
|
2225
|
-
/** @type {RyunixComponent & { _arePropsEqual?: (prev: Record<string, unknown>, next: Record<string, unknown>) => boolean }} */ fiber.type;
|
|
2226
|
-
if (componentType._isMemo && fiber.alternate) {
|
|
2227
|
-
const { children: _pc, ...prevRest } = fiber.alternate.props || {};
|
|
2228
|
-
const { children: _nc, ...nextRest } = fiber.props || {};
|
|
2229
|
-
if (componentType._arePropsEqual?.(prevRest, nextRest)) {
|
|
2230
|
-
fiber.hooks = fiber.alternate.hooks;
|
|
2231
|
-
const oldChild = fiber.alternate.child;
|
|
2232
|
-
if (oldChild) {
|
|
2233
|
-
oldChild.parent = fiber;
|
|
2234
|
-
fiber.child = oldChild;
|
|
1811
|
+
validateHookContext();
|
|
1812
|
+
let fiber = state.wipFiber;
|
|
1813
|
+
while (fiber) {
|
|
1814
|
+
if (fiber._contextId === ctxID && fiber._contextValue !== undefined) {
|
|
1815
|
+
return fiber._contextValue;
|
|
2235
1816
|
}
|
|
2236
|
-
|
|
1817
|
+
const fiberType = fiber.type;
|
|
1818
|
+
if (fiberType?._contextId === ctxID && fiber.props?.value !== undefined) {
|
|
1819
|
+
return fiber.props.value;
|
|
1820
|
+
}
|
|
1821
|
+
fiber = fiber.parent;
|
|
2237
1822
|
}
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
fiber._contextValue = fiber.props.value;
|
|
2245
|
-
}
|
|
2246
|
-
reconcileChildren(fiber, children);
|
|
1823
|
+
return defaultValue;
|
|
1824
|
+
};
|
|
1825
|
+
return {
|
|
1826
|
+
Provider: Provider,
|
|
1827
|
+
useContext,
|
|
1828
|
+
};
|
|
2247
1829
|
};
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
const
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
if (current._hydrateClientOnly)
|
|
2256
|
-
return true;
|
|
2257
|
-
current = current.parent || null;
|
|
1830
|
+
const useQuery = () => {
|
|
1831
|
+
if (typeof window === 'undefined')
|
|
1832
|
+
return {};
|
|
1833
|
+
const searchParams = new URLSearchParams(window.location.search);
|
|
1834
|
+
const query = {};
|
|
1835
|
+
for (const [key, value] of searchParams.entries()) {
|
|
1836
|
+
query[key] = value;
|
|
2258
1837
|
}
|
|
2259
|
-
return
|
|
1838
|
+
return query;
|
|
2260
1839
|
};
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
const
|
|
1840
|
+
const useHash = () => {
|
|
1841
|
+
if (typeof window === 'undefined')
|
|
1842
|
+
return '';
|
|
1843
|
+
const [hash, setHash] = useStore(window.location.hash);
|
|
1844
|
+
useEffect(() => {
|
|
1845
|
+
const onHashChange = () => setHash(window.location.hash);
|
|
1846
|
+
window.addEventListener('hashchange', onHashChange);
|
|
1847
|
+
return () => window.removeEventListener('hashchange', onHashChange);
|
|
1848
|
+
}, []);
|
|
1849
|
+
return hash;
|
|
1850
|
+
};
|
|
1851
|
+
const useMetadata = (tags = {}, options = {}) => {
|
|
2265
1852
|
const state = getState();
|
|
2266
|
-
if (
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
fiber._contextValue = fiber.props?.value;
|
|
2270
|
-
}
|
|
2271
|
-
const isPassthrough = fiber.type === RYUNIX_TYPES.RYUNIX_FRAGMENT ||
|
|
2272
|
-
fiber.type === RYUNIX_TYPES.RYUNIX_CONTEXT ||
|
|
2273
|
-
fiber.type === Symbol.for('ryunix.portal');
|
|
2274
|
-
if (state.isHydrating && isPassthrough) {
|
|
2275
|
-
fiber.effectTag = EFFECT_TAGS.HYDRATE;
|
|
1853
|
+
if (state.isServerRendering) {
|
|
1854
|
+
state.ssrMetadata = mergeRouteMetadata((state.ssrMetadata || {}), tags);
|
|
1855
|
+
return;
|
|
2276
1856
|
}
|
|
2277
|
-
|
|
2278
|
-
if (
|
|
2279
|
-
|
|
2280
|
-
|
|
1857
|
+
useEffect(() => {
|
|
1858
|
+
if (typeof document === 'undefined')
|
|
1859
|
+
return;
|
|
1860
|
+
let finalTitle = 'Ryunix App';
|
|
1861
|
+
const template = options.title?.template;
|
|
1862
|
+
const defaultTitle = options.title?.prefix || 'Ryunix App';
|
|
1863
|
+
const pageTitle = tags.pageTitle || tags.title;
|
|
1864
|
+
if (is.string(pageTitle) && pageTitle.trim()) {
|
|
1865
|
+
finalTitle = template?.includes('%s')
|
|
1866
|
+
? template.replace('%s', pageTitle)
|
|
1867
|
+
: pageTitle;
|
|
2281
1868
|
}
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
fiber.effectTag = EFFECT_TAGS.HYDRATE;
|
|
2293
|
-
if (isText &&
|
|
2294
|
-
fiber.props?.nodeValue != null &&
|
|
2295
|
-
domNode.nodeValue !== String(fiber.props.nodeValue)) {
|
|
2296
|
-
domNode.nodeValue = String(fiber.props.nodeValue);
|
|
2297
|
-
logHydrationRecoverable('text');
|
|
2298
|
-
}
|
|
2299
|
-
if (isElement &&
|
|
2300
|
-
domNode.hasAttribute('data-ryunix-hydrate-boundary')) {
|
|
2301
|
-
fiber._hydrateClientOnly = true;
|
|
2302
|
-
}
|
|
2303
|
-
state.hydrateCursor = nextValidSibling$1(domNode.firstChild);
|
|
1869
|
+
else {
|
|
1870
|
+
finalTitle = defaultTitle;
|
|
1871
|
+
}
|
|
1872
|
+
document.title = finalTitle;
|
|
1873
|
+
if (tags.canonical) {
|
|
1874
|
+
let link = document.querySelector('link[rel="canonical"]');
|
|
1875
|
+
if (!link) {
|
|
1876
|
+
link = document.createElement('link');
|
|
1877
|
+
link.setAttribute('rel', 'canonical');
|
|
1878
|
+
document.head.appendChild(link);
|
|
2304
1879
|
}
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
state.isHydrating = false;
|
|
2320
|
-
state.hydrateCursor = null;
|
|
2321
|
-
fiber.dom = /** @type {HTMLElement | Text | null} */ createDom(fiber);
|
|
2322
|
-
fiber.effectTag = EFFECT_TAGS.PLACEMENT;
|
|
2323
|
-
}
|
|
2324
|
-
else {
|
|
2325
|
-
logHydrationMismatch(detail);
|
|
2326
|
-
state.isHydrating = false;
|
|
2327
|
-
state.hydrationFailed = true;
|
|
2328
|
-
state.hydrateCursor = null;
|
|
2329
|
-
fiber.dom = /** @type {HTMLElement | Text | null} */ createDom(fiber);
|
|
2330
|
-
fiber.effectTag = EFFECT_TAGS.PLACEMENT;
|
|
2331
|
-
}
|
|
1880
|
+
link.setAttribute('href', tags.canonical);
|
|
1881
|
+
}
|
|
1882
|
+
Object.entries(tags).forEach(([key, value]) => {
|
|
1883
|
+
if (['title', 'pageTitle', 'canonical'].includes(key))
|
|
1884
|
+
return;
|
|
1885
|
+
if (value == null)
|
|
1886
|
+
return;
|
|
1887
|
+
const isProperty = key.startsWith('og:') || key.startsWith('twitter:');
|
|
1888
|
+
const selector = `meta[${isProperty ? 'property' : 'name'}='${key}']`;
|
|
1889
|
+
let meta = document.head.querySelector(selector);
|
|
1890
|
+
if (!meta) {
|
|
1891
|
+
meta = document.createElement('meta');
|
|
1892
|
+
meta.setAttribute(isProperty ? 'property' : 'name', key);
|
|
1893
|
+
document.head.appendChild(meta);
|
|
2332
1894
|
}
|
|
1895
|
+
meta.setAttribute('content', value);
|
|
1896
|
+
});
|
|
1897
|
+
}, [JSON.stringify(tags), JSON.stringify(options)]);
|
|
1898
|
+
};
|
|
1899
|
+
const RouterContext = createContext('ryunix.navigation', {
|
|
1900
|
+
location: '/',
|
|
1901
|
+
params: {},
|
|
1902
|
+
query: {},
|
|
1903
|
+
navigate: (_path) => { },
|
|
1904
|
+
route: null,
|
|
1905
|
+
});
|
|
1906
|
+
const findRoute = (routes, path) => {
|
|
1907
|
+
const pathname = path.split('?')[0].split('#')[0];
|
|
1908
|
+
const notFoundRoute = routes.find((route) => route.NotFound);
|
|
1909
|
+
const notFound = notFoundRoute
|
|
1910
|
+
? { route: { component: notFoundRoute.NotFound ?? null }, params: {} }
|
|
1911
|
+
: { route: { component: null }, params: {} };
|
|
1912
|
+
for (const route of routes) {
|
|
1913
|
+
if (route.subRoutes) {
|
|
1914
|
+
const childRoute = findRoute(route.subRoutes, path);
|
|
1915
|
+
if (childRoute)
|
|
1916
|
+
return childRoute;
|
|
2333
1917
|
}
|
|
2334
|
-
|
|
2335
|
-
|
|
1918
|
+
if (route.path === '*')
|
|
1919
|
+
return notFound;
|
|
1920
|
+
if (!route.path || typeof route.path !== 'string')
|
|
1921
|
+
continue;
|
|
1922
|
+
const keys = [];
|
|
1923
|
+
const pattern = new RegExp(`^${route.path.replace(/:(\.\.\.)?(\w+)/g, (match, isCatchAll, key) => {
|
|
1924
|
+
keys.push({ key, isCatchAll: !!isCatchAll });
|
|
1925
|
+
return isCatchAll ? '(.+)' : '([^/]+)';
|
|
1926
|
+
})}$`);
|
|
1927
|
+
const matchPath = pathname.match(pattern);
|
|
1928
|
+
if (matchPath) {
|
|
1929
|
+
const params = keys.reduce((acc, keyObj, index) => {
|
|
1930
|
+
const val = matchPath[index + 1];
|
|
1931
|
+
acc[keyObj.key] = keyObj.isCatchAll && val ? val.split('/') : val;
|
|
1932
|
+
return acc;
|
|
1933
|
+
}, {});
|
|
1934
|
+
return { route, params };
|
|
2336
1935
|
}
|
|
2337
1936
|
}
|
|
2338
|
-
|
|
2339
|
-
reconcileChildren(fiber, children);
|
|
2340
|
-
};
|
|
2341
|
-
/**
|
|
2342
|
-
* @param {string | symbol | RyunixComponent | object} type
|
|
2343
|
-
* @returns {string}
|
|
2344
|
-
*/
|
|
2345
|
-
const getTypeLabel = (type) => {
|
|
2346
|
-
if (typeof type === 'symbol')
|
|
2347
|
-
return type.description || type.toString();
|
|
2348
|
-
if (typeof type === 'function')
|
|
2349
|
-
return type.name || 'anonymous';
|
|
2350
|
-
return String(type);
|
|
1937
|
+
return notFound;
|
|
2351
1938
|
};
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
/**
|
|
2359
|
-
* @param {{ src: string } & Record<string, unknown>} props
|
|
2360
|
-
* @returns {import('./createElement.js').RyunixElement}
|
|
2361
|
-
*/
|
|
2362
|
-
const Image = ({ src, ...props }) => {
|
|
2363
|
-
return createElement('img', { ...props, src });
|
|
1939
|
+
const getSsrPathname = () => {
|
|
1940
|
+
const pathname = globalThis?.window?.location?.pathname;
|
|
1941
|
+
if (typeof pathname === 'string' && pathname) {
|
|
1942
|
+
return pathname.split('?')[0].split('#')[0];
|
|
1943
|
+
}
|
|
1944
|
+
return '/';
|
|
2364
1945
|
};
|
|
2365
|
-
const {
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
1946
|
+
const RouterProvider = ({ routes, children, }) => {
|
|
1947
|
+
if (typeof window === 'undefined') {
|
|
1948
|
+
const location = getSsrPathname();
|
|
1949
|
+
const currentRouteData = findRoute(routes, location);
|
|
1950
|
+
const contextValue = {
|
|
1951
|
+
location,
|
|
1952
|
+
params: currentRouteData.params || {},
|
|
1953
|
+
query: {},
|
|
1954
|
+
navigate: () => { },
|
|
1955
|
+
route: currentRouteData.route,
|
|
1956
|
+
};
|
|
1957
|
+
return createElement(RouterContext.Provider, { value: contextValue }, Fragment({ children }));
|
|
1958
|
+
}
|
|
1959
|
+
const [location, setLocation] = useStore(window.location.pathname);
|
|
1960
|
+
useEffect(() => {
|
|
1961
|
+
const update = () => setLocation(window.location.pathname);
|
|
1962
|
+
window.addEventListener('popstate', update);
|
|
1963
|
+
window.addEventListener('hashchange', update);
|
|
1964
|
+
return () => {
|
|
1965
|
+
window.removeEventListener('popstate', update);
|
|
1966
|
+
window.removeEventListener('hashchange', update);
|
|
1967
|
+
};
|
|
1968
|
+
}, []);
|
|
1969
|
+
const navigate = (path) => {
|
|
1970
|
+
if (typeof window !== 'undefined' && window.__RYUNIX_MPA__) {
|
|
1971
|
+
window.location.assign(path);
|
|
1972
|
+
return;
|
|
1973
|
+
}
|
|
1974
|
+
window.history.pushState({}, '', path);
|
|
1975
|
+
setLocation(path);
|
|
2377
1976
|
};
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
/**
|
|
2387
|
-
* Default MDX components with Ryunix-optimized rendering
|
|
2388
|
-
* @type {Record<string, (props: Record<string, unknown>) => RyunixNode>}
|
|
2389
|
-
*/
|
|
2390
|
-
const defaultComponents = {
|
|
2391
|
-
// Headings
|
|
2392
|
-
h1: (props) => mdxHost('h1', props),
|
|
2393
|
-
h2: (props) => mdxHost('h2', props),
|
|
2394
|
-
h3: (props) => mdxHost('h3', props),
|
|
2395
|
-
h4: (props) => mdxHost('h4', props),
|
|
2396
|
-
h5: (props) => mdxHost('h5', props),
|
|
2397
|
-
h6: (props) => mdxHost('h6', props),
|
|
2398
|
-
// Text
|
|
2399
|
-
p: (props) => mdxHost('p', props),
|
|
2400
|
-
a: (props) => mdxHost('a', props),
|
|
2401
|
-
strong: (props) => mdxHost('strong', props),
|
|
2402
|
-
em: (props) => mdxHost('em', props),
|
|
2403
|
-
code: (props) => mdxHost('code', props),
|
|
2404
|
-
// Lists
|
|
2405
|
-
ul: (props) => mdxHost('ul', props),
|
|
2406
|
-
ol: (props) => mdxHost('ol', props),
|
|
2407
|
-
li: (props) => mdxHost('li', props),
|
|
2408
|
-
// Blocks
|
|
2409
|
-
blockquote: (props) => mdxHost('blockquote', props),
|
|
2410
|
-
pre: (props) => mdxHost('pre', props),
|
|
2411
|
-
hr: (props) => mdxHost('hr', props),
|
|
2412
|
-
// Tables
|
|
2413
|
-
table: (props) => mdxHost('table', props),
|
|
2414
|
-
thead: (props) => mdxHost('thead', props),
|
|
2415
|
-
tbody: (props) => mdxHost('tbody', props),
|
|
2416
|
-
tr: (props) => mdxHost('tr', props),
|
|
2417
|
-
th: (props) => mdxHost('th', props),
|
|
2418
|
-
td: (props) => mdxHost('td', props),
|
|
2419
|
-
// Media
|
|
2420
|
-
img: (props) => mdxHost('img', props),
|
|
2421
|
-
};
|
|
2422
|
-
/**
|
|
2423
|
-
* MDX Wrapper component
|
|
2424
|
-
* Provides default styling and components for MDX content
|
|
2425
|
-
*/
|
|
2426
|
-
/**
|
|
2427
|
-
* @param {{ children?: RyunixNode, components?: Record<string, RyunixComponent> }} props
|
|
2428
|
-
* @returns {import('./createElement.js').RyunixElement}
|
|
2429
|
-
*/
|
|
2430
|
-
const MDXContent = ({ children, components = {} }) => {
|
|
2431
|
-
const mergedComponents = getMDXComponents(components);
|
|
2432
|
-
return createElement(
|
|
2433
|
-
/** @type {string | symbol | Function} */ MDXProvider, { value: mergedComponents }, createElement('div', null, children));
|
|
2434
|
-
};
|
|
2435
|
-
|
|
2436
|
-
/**
|
|
2437
|
-
* @param {RyunixNode} element
|
|
2438
|
-
* @param {Element | DocumentFragment} container
|
|
2439
|
-
*/
|
|
2440
|
-
const renderSubtree = (element, container) => {
|
|
2441
|
-
clearContainer(/** @type {HTMLElement} */ container);
|
|
2442
|
-
/** @type {RyunixRootFiber} */
|
|
2443
|
-
const root = {
|
|
2444
|
-
dom: container,
|
|
2445
|
-
props: { children: [element] },
|
|
2446
|
-
isHydrating: false,
|
|
2447
|
-
hydrateCursor: null,
|
|
1977
|
+
const currentRouteData = findRoute(routes, location);
|
|
1978
|
+
const query = useQuery();
|
|
1979
|
+
const contextValue = {
|
|
1980
|
+
location: location,
|
|
1981
|
+
params: currentRouteData.params || {},
|
|
1982
|
+
query,
|
|
1983
|
+
navigate,
|
|
1984
|
+
route: currentRouteData.route,
|
|
2448
1985
|
};
|
|
2449
|
-
|
|
2450
|
-
};
|
|
2451
|
-
const recoverScopedHydrationFailures = () => {
|
|
2452
|
-
const state = getState();
|
|
2453
|
-
const queue = state.scopedRecoveryQueue;
|
|
2454
|
-
if (!queue?.length)
|
|
2455
|
-
return;
|
|
2456
|
-
state.scopedRecoveryQueue = [];
|
|
2457
|
-
for (const item of queue) {
|
|
2458
|
-
logHydrationBoundaryRecovery();
|
|
2459
|
-
renderSubtree(item.element, item.boundaryDom);
|
|
2460
|
-
}
|
|
2461
|
-
};
|
|
2462
|
-
const recoverHydrationFailureIfNeeded = () => {
|
|
2463
|
-
const state = getState();
|
|
2464
|
-
if (!state.hydrationFailed || state.hydrationRecover)
|
|
2465
|
-
return;
|
|
2466
|
-
const policy = getHydrationPolicy();
|
|
2467
|
-
if (policy.recover === 'none')
|
|
2468
|
-
return;
|
|
2469
|
-
const container = state.containerRoot || state.currentRoot?.dom;
|
|
2470
|
-
const element = state.currentRoot?.props?.children?.[0];
|
|
2471
|
-
if (!container || element == null)
|
|
2472
|
-
return;
|
|
2473
|
-
state.hydrationRecover = true;
|
|
2474
|
-
state.hydrationFailed = false;
|
|
2475
|
-
logHydrationFailure('');
|
|
2476
|
-
logHydrationRecovery();
|
|
2477
|
-
renderSubtree(/** @type {RyunixNode} */ element, container);
|
|
1986
|
+
return createElement(RouterContext.Provider, { value: contextValue }, Fragment({ children }));
|
|
2478
1987
|
};
|
|
2479
|
-
const
|
|
2480
|
-
|
|
2481
|
-
recoverHydrationFailureIfNeeded();
|
|
1988
|
+
const useRouter = () => {
|
|
1989
|
+
return RouterContext.useContext('ryunix.navigation');
|
|
2482
1990
|
};
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
*/
|
|
2496
|
-
function performUnitOfWork(fiber) {
|
|
2497
|
-
const state = getState();
|
|
2498
|
-
const isFunctionComponent = fiber.type instanceof Function || typeof fiber.type === 'function';
|
|
2499
|
-
try {
|
|
2500
|
-
if (isFunctionComponent) {
|
|
2501
|
-
updateFunctionComponent(fiber);
|
|
2502
|
-
}
|
|
2503
|
-
else {
|
|
2504
|
-
updateHostComponent(fiber);
|
|
2505
|
-
}
|
|
2506
|
-
}
|
|
2507
|
-
catch (error) {
|
|
2508
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
2509
|
-
console.error('[Ryunix ErrorBoundary] Caught error during render:', error);
|
|
2510
|
-
try {
|
|
2511
|
-
// Attempt to attach original JSX source map for DevOverlay lookup
|
|
2512
|
-
const src = fiber.props && fiber.props.__source;
|
|
2513
|
-
if (src && error && typeof error === 'object') {
|
|
2514
|
-
error.__ryunix_source = src;
|
|
2515
|
-
}
|
|
2516
|
-
let targetFiber = fiber;
|
|
2517
|
-
while (!error.__ryunix_source && targetFiber) {
|
|
2518
|
-
if (targetFiber.props && targetFiber.props.__source) {
|
|
2519
|
-
error.__ryunix_source = targetFiber.props.__source;
|
|
2520
|
-
}
|
|
2521
|
-
targetFiber = targetFiber.parent;
|
|
2522
|
-
}
|
|
2523
|
-
}
|
|
2524
|
-
catch (e) { }
|
|
2525
|
-
}
|
|
2526
|
-
// Traverse upwards to find nearest ErrorBoundary
|
|
2527
|
-
let boundaryFiber = fiber.parent;
|
|
2528
|
-
let foundBoundary = false;
|
|
2529
|
-
while (boundaryFiber) {
|
|
2530
|
-
if (boundaryFiber.type &&
|
|
2531
|
-
/** @type {{ ryunix_type?: string }} */ boundaryFiber.type
|
|
2532
|
-
.ryunix_type === 'RYUNIX_ERROR_BOUNDARY') {
|
|
2533
|
-
foundBoundary = true;
|
|
2534
|
-
break;
|
|
2535
|
-
}
|
|
2536
|
-
boundaryFiber = boundaryFiber.parent;
|
|
2537
|
-
}
|
|
2538
|
-
if (foundBoundary) {
|
|
2539
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
2540
|
-
console.warn('[Ryunix ErrorBoundary] Recovering tree at nearest boundary.');
|
|
2541
|
-
}
|
|
2542
|
-
// Assign the error state to the boundary so it can render the fallback
|
|
2543
|
-
boundaryFiber.stateError = error;
|
|
2544
|
-
// Discard the corrupted children of the crashing fiber to prevent undefined behavior
|
|
2545
|
-
fiber.child = null;
|
|
2546
|
-
// Rewind the rendering context to the ErrorBoundary fiber
|
|
2547
|
-
// so the work loop immediately starts re-evaluating the boundary branch
|
|
2548
|
-
return boundaryFiber;
|
|
2549
|
-
}
|
|
2550
|
-
else {
|
|
2551
|
-
// Uncaught fatal error: stop the work loop entirely
|
|
2552
|
-
console.error('[Ryunix] Fatal Uncaught Error. No ErrorBoundary was found in the tree to handle this exception:\n', error);
|
|
2553
|
-
state.nextUnitOfWork = null;
|
|
2554
|
-
return null;
|
|
2555
|
-
}
|
|
2556
|
-
}
|
|
2557
|
-
if (fiber.child) {
|
|
2558
|
-
return fiber.child;
|
|
2559
|
-
}
|
|
2560
|
-
let nextFiber = fiber;
|
|
2561
|
-
while (nextFiber) {
|
|
2562
|
-
// If we just finished a Host node during hydration,
|
|
2563
|
-
// the next fiber (sibling) should start at the next DOM sibling.
|
|
2564
|
-
if (state.isHydrating && nextFiber.dom) {
|
|
2565
|
-
state.hydrateCursor = nextValidSibling$1(nextFiber.dom.nextSibling);
|
|
2566
|
-
}
|
|
2567
|
-
if (nextFiber.sibling) {
|
|
2568
|
-
return nextFiber.sibling;
|
|
1991
|
+
const Children = () => {
|
|
1992
|
+
const { route, params, query, location } = useRouter();
|
|
1993
|
+
if (!route || !route.component)
|
|
1994
|
+
return null;
|
|
1995
|
+
const hash = useHash();
|
|
1996
|
+
useEffect(() => {
|
|
1997
|
+
if (hash) {
|
|
1998
|
+
const id = hash.slice(1);
|
|
1999
|
+
const el = document.getElementById(id);
|
|
2000
|
+
if (el)
|
|
2001
|
+
el.scrollIntoView({ block: 'start', behavior: 'smooth' });
|
|
2002
|
+
return;
|
|
2569
2003
|
}
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
// the loop will handle the parent's sibling or end.
|
|
2573
|
-
}
|
|
2574
|
-
}
|
|
2575
|
-
/**
|
|
2576
|
-
* @param {{ timeRemaining: () => number, didTimeout?: boolean }} deadline
|
|
2577
|
-
*/
|
|
2578
|
-
const workLoop = (deadline) => {
|
|
2579
|
-
const state = getState();
|
|
2580
|
-
let shouldYield = false;
|
|
2581
|
-
while ((state.nextUnitOfWork || workQueue.length > 0) && !shouldYield) {
|
|
2582
|
-
if (!state.nextUnitOfWork && workQueue.length > 0) {
|
|
2583
|
-
const nextRoot = workQueue.shift();
|
|
2584
|
-
state.wipRoot = nextRoot;
|
|
2585
|
-
state.nextUnitOfWork = nextRoot;
|
|
2586
|
-
state.deletions = [];
|
|
2587
|
-
// Restore specific hydration state for this root
|
|
2588
|
-
if (nextRoot.isHydrating !== undefined) {
|
|
2589
|
-
state.isHydrating = nextRoot.isHydrating;
|
|
2590
|
-
state.hydrateCursor = nextRoot.hydrateCursor;
|
|
2591
|
-
}
|
|
2004
|
+
if (typeof window !== 'undefined') {
|
|
2005
|
+
window.scrollTo(0, 0);
|
|
2592
2006
|
}
|
|
2593
|
-
|
|
2594
|
-
|
|
2007
|
+
}, [location, hash]);
|
|
2008
|
+
return createElement(route.component, {
|
|
2009
|
+
params,
|
|
2010
|
+
query,
|
|
2011
|
+
hash,
|
|
2012
|
+
location,
|
|
2013
|
+
});
|
|
2014
|
+
};
|
|
2015
|
+
const usePathname = () => {
|
|
2016
|
+
const { location } = useRouter();
|
|
2017
|
+
return location.split('?')[0].split('#')[0];
|
|
2018
|
+
};
|
|
2019
|
+
const useSearchParams = () => {
|
|
2020
|
+
const { query } = useRouter();
|
|
2021
|
+
return new URLSearchParams(query);
|
|
2022
|
+
};
|
|
2023
|
+
const Link = ({ to, prefetch = true, ...props }) => {
|
|
2024
|
+
const { navigate } = useRouter();
|
|
2025
|
+
const handleClick = (e) => {
|
|
2026
|
+
if (e.button !== 0 || e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) {
|
|
2027
|
+
return;
|
|
2595
2028
|
}
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
}
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2029
|
+
e.preventDefault();
|
|
2030
|
+
navigate(to);
|
|
2031
|
+
};
|
|
2032
|
+
const handleMouseEnter = () => {
|
|
2033
|
+
};
|
|
2034
|
+
const { className: _omitClassName, ...cleanedProps } = props;
|
|
2035
|
+
return createElement('a', {
|
|
2036
|
+
href: to,
|
|
2037
|
+
onClick: handleClick,
|
|
2038
|
+
onMouseEnter: handleMouseEnter,
|
|
2039
|
+
className: (props.className || props['ryunix-class']),
|
|
2040
|
+
...cleanedProps,
|
|
2041
|
+
}, props.children);
|
|
2608
2042
|
};
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
const
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
workQueue.push(root);
|
|
2617
|
-
}
|
|
2618
|
-
else {
|
|
2619
|
-
state.nextUnitOfWork = root;
|
|
2620
|
-
state.wipRoot = root;
|
|
2621
|
-
state.deletions = [];
|
|
2622
|
-
// Set immediate hydration state
|
|
2623
|
-
if (root.isHydrating !== undefined) {
|
|
2624
|
-
state.isHydrating = root.isHydrating;
|
|
2625
|
-
state.hydrateCursor = root.hydrateCursor;
|
|
2043
|
+
const NavLink = ({ to, exact = false, ...props }) => {
|
|
2044
|
+
const { location, navigate } = useRouter();
|
|
2045
|
+
const isActive = exact ? location === to : location.startsWith(to);
|
|
2046
|
+
const resolveClass = (cls) => (typeof cls === 'function' ? cls({ isActive }) : cls || '');
|
|
2047
|
+
const handleClick = (e) => {
|
|
2048
|
+
if (e.button !== 0 || e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) {
|
|
2049
|
+
return;
|
|
2626
2050
|
}
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2051
|
+
e.preventDefault();
|
|
2052
|
+
navigate(to);
|
|
2053
|
+
};
|
|
2054
|
+
const classAttrName = props['ryunix-class'] ? 'ryunix-class' : 'className';
|
|
2055
|
+
const classAttrValue = resolveClass((props['ryunix-class'] || props.className));
|
|
2056
|
+
const { ['ryunix-class']: _omitRyunix, className: _omitClassName, ...cleanedProps } = props;
|
|
2057
|
+
return createElement('a', {
|
|
2058
|
+
href: to,
|
|
2059
|
+
onClick: handleClick,
|
|
2060
|
+
[classAttrName]: classAttrValue,
|
|
2061
|
+
...cleanedProps,
|
|
2062
|
+
}, props.children);
|
|
2063
|
+
};
|
|
2064
|
+
const useStorePriority = (initialState) => {
|
|
2065
|
+
const reducer = (state, action) => typeof action === 'function'
|
|
2066
|
+
? action.value(state)
|
|
2067
|
+
: action.value;
|
|
2068
|
+
const [state, baseDispatch] = useReducer(reducer, initialState, undefined);
|
|
2069
|
+
const dispatch = (action, priority = Priority.NORMAL) => {
|
|
2070
|
+
const wrappedAction = {
|
|
2071
|
+
value: action,
|
|
2072
|
+
priority,
|
|
2073
|
+
};
|
|
2074
|
+
scheduleUpdate(() => baseDispatch(wrappedAction, priority), priority);
|
|
2075
|
+
};
|
|
2076
|
+
return [state, dispatch];
|
|
2077
|
+
};
|
|
2078
|
+
const useTransition = () => {
|
|
2079
|
+
const [isPending, setIsPending] = useStorePriority(false);
|
|
2080
|
+
const startTransition = (callback) => {
|
|
2081
|
+
setIsPending(true, Priority.IMMEDIATE);
|
|
2082
|
+
setTimeout(() => {
|
|
2083
|
+
runWithPriority(Priority.LOW, () => {
|
|
2084
|
+
callback();
|
|
2085
|
+
setIsPending(false, Priority.LOW);
|
|
2637
2086
|
});
|
|
2087
|
+
}, 0);
|
|
2088
|
+
};
|
|
2089
|
+
return [isPending, startTransition];
|
|
2090
|
+
};
|
|
2091
|
+
const useDeferredValue = (value) => {
|
|
2092
|
+
const [deferredValue, setDeferredValue] = useStorePriority(value);
|
|
2093
|
+
useEffect(() => {
|
|
2094
|
+
const timeout = setTimeout(() => {
|
|
2095
|
+
setDeferredValue(value, Priority.LOW);
|
|
2096
|
+
}, 100);
|
|
2097
|
+
return () => clearTimeout(timeout);
|
|
2098
|
+
}, [value]);
|
|
2099
|
+
return deferredValue;
|
|
2100
|
+
};
|
|
2101
|
+
const usePersistentStore = (key, initialState = '') => {
|
|
2102
|
+
const [state, dispatch] = useStore(() => {
|
|
2103
|
+
try {
|
|
2104
|
+
const item = window.localStorage.getItem(key);
|
|
2105
|
+
return item ? JSON.parse(item) : initialState;
|
|
2638
2106
|
}
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2107
|
+
catch (_error) {
|
|
2108
|
+
return initialState;
|
|
2109
|
+
}
|
|
2110
|
+
});
|
|
2111
|
+
const setValue = (value) => {
|
|
2112
|
+
try {
|
|
2113
|
+
dispatch(value);
|
|
2114
|
+
window.localStorage.setItem(key, JSON.stringify(value));
|
|
2115
|
+
}
|
|
2116
|
+
catch (error) {
|
|
2117
|
+
console.error(error);
|
|
2642
2118
|
}
|
|
2643
|
-
}
|
|
2644
|
-
};
|
|
2645
|
-
setScheduleWork(scheduleWork);
|
|
2646
|
-
|
|
2647
|
-
/**
|
|
2648
|
-
* @typedef {import('./createElement.js').RyunixNode} RyunixNode
|
|
2649
|
-
* @typedef {import('../types/internal.js').RyunixRootFiber} RyunixRootFiber
|
|
2650
|
-
* @typedef {import('../types/internal.js').RyunixComponent} RyunixComponent
|
|
2651
|
-
*/
|
|
2652
|
-
/**
|
|
2653
|
-
* The `render` function in JavaScript updates the DOM with a new element and schedules work to be done
|
|
2654
|
-
* on the element.
|
|
2655
|
-
* @param {RyunixNode} element
|
|
2656
|
-
* @param {Element | DocumentFragment} container
|
|
2657
|
-
* @returns {RyunixRootFiber}
|
|
2658
|
-
*/
|
|
2659
|
-
const render = (element, container) => {
|
|
2660
|
-
const state = getState();
|
|
2661
|
-
// Clear container before CSR render to avoid duplication
|
|
2662
|
-
clearContainer(/** @type {HTMLElement} */ container);
|
|
2663
|
-
/** @type {RyunixRootFiber} */
|
|
2664
|
-
const root = {
|
|
2665
|
-
dom: container,
|
|
2666
|
-
props: {
|
|
2667
|
-
children: [
|
|
2668
|
-
/** @type {import('../types/internal.js').RyunixNode} */ element,
|
|
2669
|
-
],
|
|
2670
|
-
},
|
|
2671
|
-
alternate: state.currentRoot,
|
|
2672
|
-
isHydrating: false,
|
|
2673
|
-
hydrateCursor: /** @type {ChildNode | null} */ null,
|
|
2674
2119
|
};
|
|
2675
|
-
|
|
2676
|
-
return root;
|
|
2677
|
-
};
|
|
2678
|
-
/**
|
|
2679
|
-
* @param {ChildNode | null} node
|
|
2680
|
-
* @returns {ChildNode | null}
|
|
2681
|
-
*/
|
|
2682
|
-
const nextValidSibling = (node) => {
|
|
2683
|
-
let next = node;
|
|
2684
|
-
while (next &&
|
|
2685
|
-
((next.nodeType === 3 && !next.nodeValue.trim()) ||
|
|
2686
|
-
next.nodeType === 8 ||
|
|
2687
|
-
(next.nodeType === 1 &&
|
|
2688
|
-
/** @type {Element} */ next.hasAttribute('data-ryunix-ssr')))) {
|
|
2689
|
-
next = next.nextSibling;
|
|
2690
|
-
}
|
|
2691
|
-
return next;
|
|
2120
|
+
return [state, setValue];
|
|
2692
2121
|
};
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
* @param {RyunixNode} element
|
|
2698
|
-
* @param {Element | DocumentFragment} container
|
|
2699
|
-
* @returns {RyunixRootFiber}
|
|
2700
|
-
*/
|
|
2701
|
-
const hydrate = (element, container) => {
|
|
2702
|
-
const state = getState();
|
|
2703
|
-
state.containerRoot = container;
|
|
2704
|
-
/** @type {RyunixRootFiber} */
|
|
2705
|
-
const root = {
|
|
2706
|
-
dom: container,
|
|
2707
|
-
props: {
|
|
2708
|
-
children: [
|
|
2709
|
-
/** @type {import('../types/internal.js').RyunixNode} */ element,
|
|
2710
|
-
],
|
|
2711
|
-
},
|
|
2712
|
-
alternate: state.currentRoot,
|
|
2713
|
-
isHydrating: true,
|
|
2714
|
-
hydrateCursor: nextValidSibling(container.firstChild),
|
|
2122
|
+
const useSwitch = (initialState = false) => {
|
|
2123
|
+
const [state, dispatch] = useStore(initialState);
|
|
2124
|
+
const toggle = () => {
|
|
2125
|
+
dispatch((prev) => !prev);
|
|
2715
2126
|
};
|
|
2716
|
-
|
|
2717
|
-
return root;
|
|
2127
|
+
return [state, toggle];
|
|
2718
2128
|
};
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
* @returns {RyunixRootFiber | undefined}
|
|
2724
|
-
*/
|
|
2725
|
-
const init = (MainElement, root = '__ryunix', components = {}) => {
|
|
2129
|
+
const useLayoutEffect = (callback, deps) => {
|
|
2130
|
+
if (typeof window === 'undefined') {
|
|
2131
|
+
return;
|
|
2132
|
+
}
|
|
2726
2133
|
const state = getState();
|
|
2727
|
-
state.
|
|
2728
|
-
|
|
2729
|
-
state.hydrationPolicy = getHydrationPolicy();
|
|
2730
|
-
state.scopedRecoveryQueue = [];
|
|
2731
|
-
state.hydrationRecover = false;
|
|
2732
|
-
// Reset any stale hydration flags
|
|
2733
|
-
state.isHydrating = false;
|
|
2734
|
-
state.hydrationFailed = false;
|
|
2735
|
-
// Auto-detect SSR based on child nodes - no need to manually set process.env.RYUNIX_SSR
|
|
2736
|
-
const hasChildNodes = state.containerRoot && state.containerRoot.hasChildNodes();
|
|
2737
|
-
if (process.env.NODE_ENV !== 'production' && process.env.RYUNIX_DEBUG) {
|
|
2738
|
-
console.log(`[Ryunix Debug] init: hasChildNodes=${hasChildNodes}, has SSR content detected.`);
|
|
2134
|
+
if (state.isServerRendering) {
|
|
2135
|
+
return;
|
|
2739
2136
|
}
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
if (hasChildNodes && ssrEnabled) {
|
|
2744
|
-
if (process.env.NODE_ENV !== 'production' && process.env.RYUNIX_DEBUG) {
|
|
2745
|
-
console.log(`[Ryunix Debug] init: SSR content detected. Starting hydration on #${root}`);
|
|
2746
|
-
}
|
|
2747
|
-
const res = hydrate(MainElement, state.containerRoot);
|
|
2748
|
-
return res;
|
|
2137
|
+
validateHookContext();
|
|
2138
|
+
if (!is.function(callback)) {
|
|
2139
|
+
throw new Error('useLayoutEffect callback must be a function');
|
|
2749
2140
|
}
|
|
2750
|
-
if (
|
|
2751
|
-
|
|
2141
|
+
if (deps !== undefined && !Array.isArray(deps)) {
|
|
2142
|
+
throw new Error('useLayoutEffect dependencies must be an array or undefined');
|
|
2752
2143
|
}
|
|
2753
|
-
const
|
|
2754
|
-
|
|
2144
|
+
const { hookIndex } = state;
|
|
2145
|
+
const wipFiber = state.wipFiber;
|
|
2146
|
+
if (!wipFiber.hooks)
|
|
2147
|
+
wipFiber.hooks = [];
|
|
2148
|
+
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
2149
|
+
const hasChanged = haveDepsChanged(oldHook?.deps, deps);
|
|
2150
|
+
const hook = {
|
|
2151
|
+
hookID: hookIndex,
|
|
2152
|
+
type: RYUNIX_TYPES.RYUNIX_EFFECT,
|
|
2153
|
+
deps,
|
|
2154
|
+
effect: hasChanged ? callback : null,
|
|
2155
|
+
cancel: oldHook?.cancel,
|
|
2156
|
+
isLayout: true,
|
|
2157
|
+
};
|
|
2158
|
+
wipFiber.hooks[hookIndex] = hook;
|
|
2159
|
+
state.hookIndex++;
|
|
2755
2160
|
};
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
return /** @type {RyunixNode} */ /** @type {(props: Record<string, unknown>) => RyunixNode} */ component(props);
|
|
2161
|
+
let idCounter = 0;
|
|
2162
|
+
const resetIdCounter = () => {
|
|
2163
|
+
idCounter = 0;
|
|
2164
|
+
};
|
|
2165
|
+
const useId = () => {
|
|
2166
|
+
const state = getState();
|
|
2167
|
+
if (state.isServerRendering) {
|
|
2168
|
+
return `:r${idCounter++}:`;
|
|
2765
2169
|
}
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2170
|
+
validateHookContext();
|
|
2171
|
+
const { hookIndex } = state;
|
|
2172
|
+
const wipFiber = state.wipFiber;
|
|
2173
|
+
if (!wipFiber.hooks)
|
|
2174
|
+
wipFiber.hooks = [];
|
|
2175
|
+
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
2176
|
+
const hook = {
|
|
2177
|
+
hookID: hookIndex,
|
|
2178
|
+
type: RYUNIX_TYPES.RYUNIX_REF,
|
|
2179
|
+
value: oldHook ? oldHook.value : `:r${idCounter++}:`,
|
|
2180
|
+
};
|
|
2181
|
+
wipFiber.hooks[hookIndex] = hook;
|
|
2182
|
+
state.hookIndex++;
|
|
2183
|
+
return hook.value;
|
|
2184
|
+
};
|
|
2185
|
+
const useDebounce = (value, delay = 300) => {
|
|
2186
|
+
const [debouncedValue, setDebouncedValue] = useStore(value);
|
|
2187
|
+
useEffect(() => {
|
|
2188
|
+
const timer = setTimeout(() => {
|
|
2189
|
+
setDebouncedValue(value);
|
|
2190
|
+
}, delay);
|
|
2191
|
+
return () => clearTimeout(timer);
|
|
2192
|
+
}, [value, delay]);
|
|
2193
|
+
return debouncedValue;
|
|
2194
|
+
};
|
|
2195
|
+
const useThrottle = (value, interval = 300) => {
|
|
2196
|
+
const [throttledValue, setThrottledValue] = useStore(value);
|
|
2197
|
+
const lastUpdated = useRef(Date.now());
|
|
2198
|
+
useEffect(() => {
|
|
2199
|
+
const now = Date.now();
|
|
2200
|
+
const elapsed = now - lastUpdated.current;
|
|
2201
|
+
if (elapsed >= interval) {
|
|
2202
|
+
lastUpdated.current = now;
|
|
2203
|
+
setThrottledValue(value);
|
|
2769
2204
|
}
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2205
|
+
else {
|
|
2206
|
+
const timer = setTimeout(() => {
|
|
2207
|
+
lastUpdated.current = Date.now();
|
|
2208
|
+
setThrottledValue(value);
|
|
2209
|
+
}, interval - elapsed);
|
|
2210
|
+
return () => clearTimeout(timer);
|
|
2211
|
+
}
|
|
2212
|
+
}, [value, interval]);
|
|
2213
|
+
return throttledValue;
|
|
2774
2214
|
};
|
|
2775
2215
|
|
|
2776
|
-
|
|
2777
|
-
* @typedef {import('./createElement.js').RyunixNode} RyunixNode
|
|
2778
|
-
* @typedef {import('./createElement.js').RyunixElement} RyunixElement
|
|
2779
|
-
* @typedef {import('../types/internal.js').RyunixRenderToStringOptions} RyunixRenderToStringOptions
|
|
2780
|
-
* @typedef {Promise<{ success: boolean, id: string, content: string, error?: unknown }>} RyunixSuspenseTask
|
|
2781
|
-
*/
|
|
2782
|
-
/**
|
|
2783
|
-
* @param {unknown} unsafe
|
|
2784
|
-
* @returns {string}
|
|
2785
|
-
*/
|
|
2216
|
+
const ASYNC_RENDER_ERROR = 'Async components require renderToStringAsync or renderToReadableStream, not renderToString';
|
|
2786
2217
|
const escapeHtml = (unsafe) => {
|
|
2787
2218
|
if (typeof unsafe !== 'string')
|
|
2788
2219
|
return String(unsafe);
|
|
@@ -2793,10 +2224,6 @@ const escapeHtml = (unsafe) => {
|
|
|
2793
2224
|
.replace(/"/g, '"')
|
|
2794
2225
|
.replace(/'/g, ''');
|
|
2795
2226
|
};
|
|
2796
|
-
/**
|
|
2797
|
-
* @param {Record<string, unknown>} styleObj
|
|
2798
|
-
* @returns {string}
|
|
2799
|
-
*/
|
|
2800
2227
|
const renderStyle = (styleObj) => {
|
|
2801
2228
|
if (!is.object(styleObj) || is.null(styleObj))
|
|
2802
2229
|
return '';
|
|
@@ -2821,10 +2248,83 @@ const VOID_ELEMENTS = new Set([
|
|
|
2821
2248
|
'track',
|
|
2822
2249
|
'wbr',
|
|
2823
2250
|
]);
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
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
|
+
};
|
|
2828
2328
|
const renderToStringImpl = (element) => {
|
|
2829
2329
|
if (element == null || typeof element === 'boolean') {
|
|
2830
2330
|
return '';
|
|
@@ -2835,101 +2335,50 @@ const renderToStringImpl = (element) => {
|
|
|
2835
2335
|
if (Array.isArray(element)) {
|
|
2836
2336
|
return element.map((child) => renderToStringImpl(child)).join('');
|
|
2837
2337
|
}
|
|
2838
|
-
/** @type {RyunixElement} */
|
|
2839
2338
|
const vnode = element;
|
|
2840
2339
|
if (vnode.type === RYUNIX_TYPES.TEXT_ELEMENT) {
|
|
2841
|
-
return escapeHtml(
|
|
2842
|
-
/** @type {import('./createElement.js').RyunixTextElement} */ vnode.props
|
|
2843
|
-
.nodeValue);
|
|
2340
|
+
return escapeHtml(vnode.props.nodeValue);
|
|
2844
2341
|
}
|
|
2845
2342
|
if (vnode.type === RYUNIX_TYPES.RYUNIX_FRAGMENT) {
|
|
2846
|
-
const children = vnode.props?.children
|
|
2343
|
+
const children = normalizeChildren(vnode.props?.children);
|
|
2847
2344
|
return children.map((child) => renderToStringImpl(child)).join('');
|
|
2848
2345
|
}
|
|
2849
2346
|
if (vnode.type === RYUNIX_TYPES.RYUNIX_CONTEXT) {
|
|
2850
|
-
// Context Providers just render their children transparently on the server
|
|
2851
2347
|
const state = getState();
|
|
2852
2348
|
state.ssrContexts = state.ssrContexts || {};
|
|
2853
|
-
const ctxProps =
|
|
2854
|
-
/** @type {{ _contextId?: string, value?: unknown, children?: RyunixNode | RyunixNode[] }} */ vnode.props ||
|
|
2855
|
-
{};
|
|
2349
|
+
const ctxProps = (vnode.props || {});
|
|
2856
2350
|
const ctxId = ctxProps._contextId;
|
|
2857
|
-
const prevCtx = state.ssrContexts[ctxId];
|
|
2351
|
+
const prevCtx = ctxId ? state.ssrContexts[ctxId] : undefined;
|
|
2858
2352
|
if (ctxId) {
|
|
2859
2353
|
state.ssrContexts[ctxId] = ctxProps.value;
|
|
2860
2354
|
}
|
|
2861
|
-
const children = ctxProps.children || [];
|
|
2862
|
-
let result = '';
|
|
2863
|
-
if (Array.isArray(children)) {
|
|
2864
|
-
result = children.map((child) => renderToStringImpl(child)).join('');
|
|
2865
|
-
}
|
|
2866
|
-
else {
|
|
2867
|
-
result = renderToStringImpl(children);
|
|
2868
|
-
}
|
|
2869
|
-
if (ctxId) {
|
|
2870
|
-
state.ssrContexts[ctxId] = prevCtx;
|
|
2871
|
-
}
|
|
2872
|
-
return result;
|
|
2873
|
-
}
|
|
2874
|
-
if (typeof vnode.type === 'function') {
|
|
2875
|
-
const type = vnode.type;
|
|
2876
|
-
const props = vnode.props || {};
|
|
2877
|
-
const renderedElement =
|
|
2878
|
-
/** @type {(props: Record<string, unknown>) => RyunixNode} */ type(props);
|
|
2879
|
-
return renderToStringImpl(renderedElement);
|
|
2880
|
-
}
|
|
2881
|
-
// It's a standard host element
|
|
2882
|
-
const type = String(vnode.type);
|
|
2883
|
-
const props = vnode.props || {};
|
|
2884
|
-
let attributes = '';
|
|
2885
|
-
let htmlChildren = '';
|
|
2886
|
-
let innerHTML = null;
|
|
2887
|
-
Object.entries(props).forEach(([key, value]) => {
|
|
2888
|
-
if (key === 'children') {
|
|
2889
|
-
if (Array.isArray(value)) {
|
|
2890
|
-
htmlChildren = value.map((child) => renderToStringImpl(child)).join('');
|
|
2891
|
-
}
|
|
2892
|
-
else {
|
|
2893
|
-
htmlChildren = renderToStringImpl(/** @type {RyunixNode} */ value);
|
|
2894
|
-
}
|
|
2895
|
-
}
|
|
2896
|
-
else if (key === 'dangerouslySetInnerHTML') {
|
|
2897
|
-
const inner = value;
|
|
2898
|
-
if (inner?.__html) {
|
|
2899
|
-
innerHTML = inner.__html;
|
|
2900
|
-
}
|
|
2901
|
-
}
|
|
2902
|
-
else if (key === STRINGS.STYLE || key === OLD_STRINGS.STYLE) {
|
|
2903
|
-
const styleString = renderStyle(
|
|
2904
|
-
/** @type {Record<string, unknown>} */ value);
|
|
2905
|
-
if (styleString) {
|
|
2906
|
-
attributes += ` style="${escapeHtml(styleString)}"`;
|
|
2907
|
-
}
|
|
2355
|
+
const children = ctxProps.children || [];
|
|
2356
|
+
let result = '';
|
|
2357
|
+
if (Array.isArray(children)) {
|
|
2358
|
+
result = children.map((child) => renderToStringImpl(child)).join('');
|
|
2908
2359
|
}
|
|
2909
|
-
else
|
|
2910
|
-
|
|
2911
|
-
attributes += ` class="${escapeHtml(value)}"`;
|
|
2912
|
-
}
|
|
2360
|
+
else {
|
|
2361
|
+
result = renderToStringImpl(children);
|
|
2913
2362
|
}
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
key !== '__self') {
|
|
2917
|
-
if (typeof value === 'boolean') {
|
|
2918
|
-
if (value)
|
|
2919
|
-
attributes += ` ${key}=""`;
|
|
2920
|
-
}
|
|
2921
|
-
else if (value != null) {
|
|
2922
|
-
let attrName = toSvgAttrName(key);
|
|
2923
|
-
let validatedValue = validateUri(attrName, value);
|
|
2924
|
-
attributes += ` ${attrName}="${escapeHtml(validatedValue)}"`;
|
|
2925
|
-
}
|
|
2363
|
+
if (ctxId) {
|
|
2364
|
+
state.ssrContexts[ctxId] = prevCtx;
|
|
2926
2365
|
}
|
|
2927
|
-
|
|
2366
|
+
return result;
|
|
2367
|
+
}
|
|
2368
|
+
if (typeof vnode.type === 'function') {
|
|
2369
|
+
const type = vnode.type;
|
|
2370
|
+
const props = vnode.props || {};
|
|
2371
|
+
const renderedElement = assertSyncRenderResult(type(props));
|
|
2372
|
+
return renderToStringImpl(renderedElement);
|
|
2373
|
+
}
|
|
2374
|
+
const type = String(vnode.type);
|
|
2375
|
+
const props = vnode.props || {};
|
|
2376
|
+
const { attributes, innerHTML, htmlChildren } = buildHostProps(props, renderToStringImpl);
|
|
2928
2377
|
if (VOID_ELEMENTS.has(type)) {
|
|
2929
|
-
return
|
|
2378
|
+
return formatHostOpenTag(type, attributes);
|
|
2930
2379
|
}
|
|
2931
2380
|
const finalContent = innerHTML !== null ? innerHTML : htmlChildren;
|
|
2932
|
-
return
|
|
2381
|
+
return `${formatHostOpenTag(type, attributes)}${finalContent}</${type}>`;
|
|
2933
2382
|
};
|
|
2934
2383
|
const RC_SCRIPT = `
|
|
2935
2384
|
function $RC(id, templateId) {
|
|
@@ -2943,21 +2392,12 @@ function $RC(id, templateId) {
|
|
|
2943
2392
|
`
|
|
2944
2393
|
.replace(/\s+/g, ' ')
|
|
2945
2394
|
.trim();
|
|
2946
|
-
/**
|
|
2947
|
-
* @param {RyunixNode | RyunixNode[]} element
|
|
2948
|
-
* @param {(chunk: string) => void} push
|
|
2949
|
-
* @param {RyunixSuspenseTask[]} [suspenseTasks]
|
|
2950
|
-
* @returns {Promise<void>}
|
|
2951
|
-
*/
|
|
2952
2395
|
const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
2953
|
-
if (element == null || typeof element === 'boolean') {
|
|
2954
|
-
return;
|
|
2955
|
-
}
|
|
2956
|
-
// Await the element if it's a promise (e.g. from an async Server Component directly rendered)
|
|
2957
2396
|
if (element instanceof Promise) {
|
|
2958
2397
|
element = await element;
|
|
2959
|
-
|
|
2960
|
-
|
|
2398
|
+
}
|
|
2399
|
+
if (element == null || typeof element === 'boolean') {
|
|
2400
|
+
return;
|
|
2961
2401
|
}
|
|
2962
2402
|
if (typeof element === 'string' || typeof element === 'number') {
|
|
2963
2403
|
push(escapeHtml(element));
|
|
@@ -2969,16 +2409,13 @@ const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
|
2969
2409
|
}
|
|
2970
2410
|
return;
|
|
2971
2411
|
}
|
|
2972
|
-
/** @type {RyunixElement} */
|
|
2973
2412
|
const vnode = element;
|
|
2974
2413
|
if (vnode.type === RYUNIX_TYPES.TEXT_ELEMENT) {
|
|
2975
|
-
push(escapeHtml(
|
|
2976
|
-
/** @type {import('./createElement.js').RyunixTextElement} */ vnode
|
|
2977
|
-
.props.nodeValue));
|
|
2414
|
+
push(escapeHtml(vnode.props.nodeValue));
|
|
2978
2415
|
return;
|
|
2979
2416
|
}
|
|
2980
2417
|
if (vnode.type === RYUNIX_TYPES.RYUNIX_FRAGMENT) {
|
|
2981
|
-
const children = vnode.props?.children
|
|
2418
|
+
const children = normalizeChildren(vnode.props?.children);
|
|
2982
2419
|
for (const child of children) {
|
|
2983
2420
|
await renderToStreamImpl(child, push, suspenseTasks);
|
|
2984
2421
|
}
|
|
@@ -2987,11 +2424,9 @@ const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
|
2987
2424
|
if (vnode.type === RYUNIX_TYPES.RYUNIX_CONTEXT) {
|
|
2988
2425
|
const state = getState();
|
|
2989
2426
|
state.ssrContexts = state.ssrContexts || {};
|
|
2990
|
-
const ctxProps =
|
|
2991
|
-
/** @type {{ _contextId?: string, value?: unknown, children?: RyunixNode | RyunixNode[] }} */ vnode.props ||
|
|
2992
|
-
{};
|
|
2427
|
+
const ctxProps = (vnode.props || {});
|
|
2993
2428
|
const ctxId = ctxProps._contextId;
|
|
2994
|
-
const prevCtx = state.ssrContexts[ctxId];
|
|
2429
|
+
const prevCtx = ctxId ? state.ssrContexts[ctxId] : undefined;
|
|
2995
2430
|
if (ctxId) {
|
|
2996
2431
|
state.ssrContexts[ctxId] = ctxProps.value;
|
|
2997
2432
|
}
|
|
@@ -3009,29 +2444,21 @@ const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
|
3009
2444
|
}
|
|
3010
2445
|
return;
|
|
3011
2446
|
}
|
|
3012
|
-
// Handle Suspense specifically
|
|
3013
2447
|
const suspenseType = vnode.type;
|
|
3014
2448
|
const isSuspenseBoundary = vnode.type === RYUNIX_TYPES.RYUNIX_SUSPENSE ||
|
|
3015
2449
|
(typeof suspenseType === 'object' &&
|
|
3016
2450
|
suspenseType != null &&
|
|
3017
|
-
|
|
3018
|
-
RYUNIX_TYPES.RYUNIX_SUSPENSE);
|
|
2451
|
+
suspenseType.type === RYUNIX_TYPES.RYUNIX_SUSPENSE);
|
|
3019
2452
|
if (isSuspenseBoundary) {
|
|
3020
|
-
const suspenseProps =
|
|
3021
|
-
/** @type {{ fallback?: RyunixNode, children?: RyunixNode | RyunixNode[] }} */ vnode.props ||
|
|
3022
|
-
{};
|
|
2453
|
+
const suspenseProps = (vnode.props || {});
|
|
3023
2454
|
const { fallback, children } = suspenseProps;
|
|
3024
2455
|
const id = `s-${Math.random().toString(36).slice(2, 9)}`;
|
|
3025
|
-
// In universal mode, Suspense renders children if ready, or fallback if pending.
|
|
3026
|
-
// BUT we want to force a background task for the REAL children if we hit a lazy component.
|
|
3027
2456
|
push(`<!--$?--><template id="B:${id}"></template><div id="S:${id}">`);
|
|
3028
|
-
// 1. Start rendering the actual content in the background
|
|
3029
2457
|
const task = (async () => {
|
|
3030
2458
|
const state = getState();
|
|
3031
2459
|
const wasBackground = state.isSuspenseBackground;
|
|
3032
2460
|
state.isSuspenseBackground = true;
|
|
3033
2461
|
let content = '';
|
|
3034
|
-
/** @param {string} chunk */
|
|
3035
2462
|
const subPush = (chunk) => {
|
|
3036
2463
|
content += chunk;
|
|
3037
2464
|
};
|
|
@@ -3045,67 +2472,30 @@ const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
|
3045
2472
|
finally {
|
|
3046
2473
|
state.isSuspenseBackground = wasBackground;
|
|
3047
2474
|
}
|
|
3048
|
-
})
|
|
2475
|
+
});
|
|
3049
2476
|
suspenseTasks.push(task);
|
|
3050
|
-
// 2. Render fallback immediately for the main stream
|
|
3051
2477
|
await renderToStreamImpl(fallback, push, suspenseTasks);
|
|
3052
2478
|
push(`</div><!--$/-->`);
|
|
3053
2479
|
return;
|
|
3054
2480
|
}
|
|
3055
|
-
|
|
3056
|
-
|
|
2481
|
+
const type = vnode.type;
|
|
2482
|
+
const props = vnode.props || {};
|
|
3057
2483
|
if (typeof type === 'function') {
|
|
3058
2484
|
if (process.env.RYUNIX_DEBUG) {
|
|
3059
2485
|
console.log('[SSR Debug] Rendering function:', type.name || 'anonymous');
|
|
3060
2486
|
}
|
|
3061
|
-
const renderedElement = await
|
|
2487
|
+
const renderedElement = await type(props);
|
|
3062
2488
|
await renderToStreamImpl(renderedElement, push, suspenseTasks);
|
|
3063
2489
|
return;
|
|
3064
2490
|
}
|
|
3065
|
-
// It's a standard host element
|
|
3066
2491
|
const hostTag = String(type);
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
Object.entries(props).forEach(([key, value]) => {
|
|
3071
|
-
if (key === 'children') ;
|
|
3072
|
-
else if (key === 'dangerouslySetInnerHTML') {
|
|
3073
|
-
const inner = value;
|
|
3074
|
-
if (inner?.__html) {
|
|
3075
|
-
innerHTML = inner.__html;
|
|
3076
|
-
}
|
|
3077
|
-
}
|
|
3078
|
-
else if (key === STRINGS.STYLE || key === OLD_STRINGS.STYLE) {
|
|
3079
|
-
const styleString = renderStyle(
|
|
3080
|
-
/** @type {Record<string, unknown>} */ value);
|
|
3081
|
-
if (styleString) {
|
|
3082
|
-
attributes += ` style="${escapeHtml(styleString)}"`;
|
|
3083
|
-
}
|
|
3084
|
-
}
|
|
3085
|
-
else if (key === STRINGS.CLASS_NAME || key === OLD_STRINGS.CLASS_NAME) {
|
|
3086
|
-
if (value) {
|
|
3087
|
-
attributes += ` class="${escapeHtml(value)}"`;
|
|
3088
|
-
}
|
|
3089
|
-
}
|
|
3090
|
-
else if (!key.startsWith('on') &&
|
|
3091
|
-
key !== '__source' &&
|
|
3092
|
-
key !== '__self') {
|
|
3093
|
-
if (typeof value === 'boolean') {
|
|
3094
|
-
if (value)
|
|
3095
|
-
attributes += ` ${key}=""`;
|
|
3096
|
-
}
|
|
3097
|
-
else if (value != null) {
|
|
3098
|
-
const attrName = toSvgAttrName(key);
|
|
3099
|
-
const validatedValue = validateUri(attrName, value);
|
|
3100
|
-
attributes += ` ${attrName}="${escapeHtml(validatedValue)}"`;
|
|
3101
|
-
}
|
|
3102
|
-
}
|
|
3103
|
-
});
|
|
3104
|
-
push(`<${hostTag}${attributes}>`);
|
|
2492
|
+
const children = props.children || [];
|
|
2493
|
+
const { attributes, innerHTML } = buildHostProps(props, () => '');
|
|
2494
|
+
push(formatHostOpenTag(hostTag, attributes));
|
|
3105
2495
|
if (innerHTML !== null) {
|
|
3106
2496
|
push(innerHTML);
|
|
3107
2497
|
}
|
|
3108
|
-
else {
|
|
2498
|
+
else if (!VOID_ELEMENTS.has(hostTag)) {
|
|
3109
2499
|
if (Array.isArray(children)) {
|
|
3110
2500
|
for (const child of children) {
|
|
3111
2501
|
await renderToStreamImpl(child, push, suspenseTasks);
|
|
@@ -3114,46 +2504,42 @@ const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
|
3114
2504
|
else {
|
|
3115
2505
|
await renderToStreamImpl(children, push, suspenseTasks);
|
|
3116
2506
|
}
|
|
3117
|
-
}
|
|
3118
|
-
if (!VOID_ELEMENTS.has(hostTag)) {
|
|
3119
2507
|
push(`</${hostTag}>`);
|
|
3120
2508
|
}
|
|
3121
2509
|
};
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
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
|
+
};
|
|
3127
2524
|
const renderToReadableStream = (element, options = {}) => {
|
|
3128
2525
|
const state = getState();
|
|
3129
2526
|
const encoder = new TextEncoder();
|
|
3130
|
-
// Reset idCounter for deterministic useId values
|
|
3131
|
-
resetIdCounter();
|
|
3132
2527
|
return new ReadableStream({
|
|
3133
2528
|
async start(controller) {
|
|
3134
2529
|
const wasServerRendering = state.isServerRendering;
|
|
3135
|
-
|
|
3136
|
-
state.ssrMetadata = {};
|
|
3137
|
-
/** @param {string} text */
|
|
2530
|
+
beginSsrRender();
|
|
3138
2531
|
const push = (text) => controller.enqueue(encoder.encode(text));
|
|
3139
|
-
/** @type {RyunixSuspenseTask[]} */
|
|
3140
2532
|
const suspenseTasks = [];
|
|
3141
2533
|
try {
|
|
3142
|
-
// 0. Inject RC helper script first
|
|
3143
2534
|
const nonceAttr = options.nonce ? ` nonce="${options.nonce}"` : '';
|
|
3144
2535
|
push(`<script${nonceAttr} data-ryunix-ssr>${RC_SCRIPT}</script>`);
|
|
3145
|
-
// 1. Render initial tree (with fallbacks)
|
|
3146
2536
|
await renderToStreamImpl(element, push, suspenseTasks);
|
|
3147
|
-
// 2. Process suspense tasks as they complete
|
|
3148
|
-
// For now, we wait for all, but in a real streaming scenario,
|
|
3149
|
-
// we could push them as they resolve.
|
|
3150
2537
|
while (suspenseTasks.length > 0) {
|
|
3151
2538
|
const task = suspenseTasks.shift();
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
}
|
|
2539
|
+
if (!task)
|
|
2540
|
+
continue;
|
|
2541
|
+
const res = await task();
|
|
2542
|
+
handleSuspenseTaskResult(res, push, nonceAttr);
|
|
3157
2543
|
}
|
|
3158
2544
|
controller.close();
|
|
3159
2545
|
}
|
|
@@ -3166,18 +2552,10 @@ const renderToReadableStream = (element, options = {}) => {
|
|
|
3166
2552
|
},
|
|
3167
2553
|
});
|
|
3168
2554
|
};
|
|
3169
|
-
|
|
3170
|
-
* @param {RyunixNode} element
|
|
3171
|
-
* @param {RyunixRenderToStringOptions} [options]
|
|
3172
|
-
* @returns {string}
|
|
3173
|
-
*/
|
|
3174
|
-
const renderToString = (element, options = {}) => {
|
|
2555
|
+
const renderToString = (element, _options = {}) => {
|
|
3175
2556
|
const state = getState();
|
|
3176
2557
|
const wasServerRendering = state.isServerRendering;
|
|
3177
|
-
|
|
3178
|
-
state.ssrMetadata = {};
|
|
3179
|
-
// Reset idCounter for deterministic useId values
|
|
3180
|
-
resetIdCounter();
|
|
2558
|
+
beginSsrRender();
|
|
3181
2559
|
try {
|
|
3182
2560
|
return renderToStringImpl(element);
|
|
3183
2561
|
}
|
|
@@ -3185,11 +2563,6 @@ const renderToString = (element, options = {}) => {
|
|
|
3185
2563
|
state.isServerRendering = wasServerRendering;
|
|
3186
2564
|
}
|
|
3187
2565
|
};
|
|
3188
|
-
/**
|
|
3189
|
-
* @param {RyunixNode} element
|
|
3190
|
-
* @param {RyunixRenderToStringOptions} [options]
|
|
3191
|
-
* @returns {Promise<string>}
|
|
3192
|
-
*/
|
|
3193
2566
|
const renderToStringAsync = async (element, options = {}) => {
|
|
3194
2567
|
const stream = renderToReadableStream(element, options);
|
|
3195
2568
|
const reader = stream.getReader();
|
|
@@ -3236,6 +2609,21 @@ function deepEqual(a, b) {
|
|
|
3236
2609
|
return keysA.every((key) => deepEqual(a[key], b[key]));
|
|
3237
2610
|
}
|
|
3238
2611
|
|
|
2612
|
+
function forwardRef(render) {
|
|
2613
|
+
if (typeof render !== 'function') {
|
|
2614
|
+
throw new Error('forwardRef requires a render function');
|
|
2615
|
+
}
|
|
2616
|
+
const ForwardRefComponent = ((props) => {
|
|
2617
|
+
const { ref, ...restProps } = props || {};
|
|
2618
|
+
return render(restProps, ref ?? null);
|
|
2619
|
+
});
|
|
2620
|
+
const named = render;
|
|
2621
|
+
ForwardRefComponent.displayName = `ForwardRef(${named.displayName || named.name || 'Component'})`;
|
|
2622
|
+
ForwardRefComponent._isForwardRef = true;
|
|
2623
|
+
ForwardRefComponent._render = render;
|
|
2624
|
+
return ForwardRefComponent;
|
|
2625
|
+
}
|
|
2626
|
+
|
|
3239
2627
|
const SUSPENSE_STATUS = {
|
|
3240
2628
|
PENDING: 'pending',
|
|
3241
2629
|
RESOLVED: 'resolved',
|
|
@@ -3321,13 +2709,57 @@ const Suspense = ({ fallback, children, }) => {
|
|
|
3321
2709
|
if (anyPending && !getState().isSuspenseBackground) {
|
|
3322
2710
|
return fallback || null;
|
|
3323
2711
|
}
|
|
3324
|
-
return createElement(Fragment, {
|
|
2712
|
+
return createElement(Fragment, {
|
|
2713
|
+
children: children,
|
|
2714
|
+
});
|
|
3325
2715
|
};
|
|
3326
2716
|
Suspense.type = RYUNIX_TYPES.RYUNIX_SUSPENSE;
|
|
3327
2717
|
function preload(importFn) {
|
|
3328
2718
|
return importFn();
|
|
3329
2719
|
}
|
|
3330
2720
|
|
|
2721
|
+
var index = /*#__PURE__*/Object.freeze({
|
|
2722
|
+
__proto__: null,
|
|
2723
|
+
Children: Children,
|
|
2724
|
+
INTERNAL_META_KEYS: INTERNAL_META_KEYS,
|
|
2725
|
+
Link: Link,
|
|
2726
|
+
NavLink: NavLink,
|
|
2727
|
+
RouterProvider: RouterProvider,
|
|
2728
|
+
Suspense: Suspense,
|
|
2729
|
+
createContext: createContext,
|
|
2730
|
+
deepEqual: deepEqual,
|
|
2731
|
+
forwardRef: forwardRef,
|
|
2732
|
+
lazy: lazy,
|
|
2733
|
+
memo: memo,
|
|
2734
|
+
mergeRouteMetadata: mergeRouteMetadata,
|
|
2735
|
+
preload: preload,
|
|
2736
|
+
resetIdCounter: resetIdCounter,
|
|
2737
|
+
resolvePageMetadata: resolvePageMetadata,
|
|
2738
|
+
shallowEqual: shallowEqual,
|
|
2739
|
+
useCallback: useCallback,
|
|
2740
|
+
useDebounce: useDebounce,
|
|
2741
|
+
useDeferredValue: useDeferredValue,
|
|
2742
|
+
useEffect: useEffect,
|
|
2743
|
+
useHash: useHash,
|
|
2744
|
+
useId: useId,
|
|
2745
|
+
useLayoutEffect: useLayoutEffect,
|
|
2746
|
+
useMemo: useMemo,
|
|
2747
|
+
useMetadata: useMetadata,
|
|
2748
|
+
usePathname: usePathname,
|
|
2749
|
+
usePersistentStore: usePersistentStore,
|
|
2750
|
+
usePersitentStore: usePersistentStore,
|
|
2751
|
+
useQuery: useQuery,
|
|
2752
|
+
useReducer: useReducer,
|
|
2753
|
+
useRef: useRef,
|
|
2754
|
+
useRouter: useRouter,
|
|
2755
|
+
useSearchParams: useSearchParams,
|
|
2756
|
+
useStore: useStore,
|
|
2757
|
+
useStorePriority: useStorePriority,
|
|
2758
|
+
useSwitch: useSwitch,
|
|
2759
|
+
useThrottle: useThrottle,
|
|
2760
|
+
useTransition: useTransition
|
|
2761
|
+
});
|
|
2762
|
+
|
|
3331
2763
|
const perfNow = () => typeof performance !== 'undefined' ? performance.now() : Date.now();
|
|
3332
2764
|
class Profiler {
|
|
3333
2765
|
enabled;
|
|
@@ -3449,44 +2881,11 @@ function withProfiler(Component, name) {
|
|
|
3449
2881
|
return Profiled;
|
|
3450
2882
|
}
|
|
3451
2883
|
|
|
3452
|
-
function
|
|
3453
|
-
if (typeof render !== 'function') {
|
|
3454
|
-
throw new Error('forwardRef requires a render function');
|
|
3455
|
-
}
|
|
3456
|
-
const ForwardRefComponent = ((props) => {
|
|
3457
|
-
const { ref, ...restProps } = props || {};
|
|
3458
|
-
return render(restProps, ref ?? null);
|
|
3459
|
-
});
|
|
3460
|
-
const named = render;
|
|
3461
|
-
ForwardRefComponent.displayName = `ForwardRef(${named.displayName || named.name || 'Component'})`;
|
|
3462
|
-
ForwardRefComponent._isForwardRef = true;
|
|
3463
|
-
ForwardRefComponent._render = render;
|
|
3464
|
-
return ForwardRefComponent;
|
|
3465
|
-
}
|
|
3466
|
-
|
|
3467
|
-
/**
|
|
3468
|
-
* Wraps content rendered exclusively on the server. During hydration Ryunix
|
|
3469
|
-
* preserves the server HTML inside this boundary.
|
|
3470
|
-
*
|
|
3471
|
-
* @param {object} props
|
|
3472
|
-
* @param {import('./createElement.js').RyunixNode} [props.children]
|
|
3473
|
-
* @param {string} [props.id]
|
|
3474
|
-
* @returns {import('./createElement.js').RyunixElement}
|
|
3475
|
-
*/
|
|
3476
|
-
function ServerBoundary({ children, id }) {
|
|
2884
|
+
function ServerBoundary({ children, id, }) {
|
|
3477
2885
|
return createElement('div', { 'data-ryunix-server': id, style: { display: 'contents' } }, children);
|
|
3478
2886
|
}
|
|
3479
2887
|
ServerBoundary.ryunix_type = 'RYUNIX_SERVER_BOUNDARY';
|
|
3480
|
-
|
|
3481
|
-
* Marks a DOM subtree for scoped hydration recovery. Mismatches inside this
|
|
3482
|
-
* boundary can be recovered locally without remounting the full app root.
|
|
3483
|
-
*
|
|
3484
|
-
* @param {object} props
|
|
3485
|
-
* @param {import('./createElement.js').RyunixNode} [props.children]
|
|
3486
|
-
* @param {string} [props.id]
|
|
3487
|
-
* @returns {import('./createElement.js').RyunixElement}
|
|
3488
|
-
*/
|
|
3489
|
-
function HydrationBoundary({ children, id }) {
|
|
2888
|
+
function HydrationBoundary({ children, id, }) {
|
|
3490
2889
|
return createElement('div', {
|
|
3491
2890
|
'data-ryunix-hydrate-boundary': id ?? '',
|
|
3492
2891
|
suppressHydrationWarning: true,
|
|
@@ -3510,11 +2909,6 @@ function ErrorBoundary({ children, fallback, }) {
|
|
|
3510
2909
|
ErrorBoundary.ryunix_type =
|
|
3511
2910
|
'RYUNIX_ERROR_BOUNDARY';
|
|
3512
2911
|
|
|
3513
|
-
/**
|
|
3514
|
-
* Client proxy for a compiled server action.
|
|
3515
|
-
* @param {string} actionId - Build-time action identifier
|
|
3516
|
-
* @returns {(...args: unknown[]) => Promise<unknown>}
|
|
3517
|
-
*/
|
|
3518
2912
|
function createActionProxy(actionId) {
|
|
3519
2913
|
return async function (...args) {
|
|
3520
2914
|
const response = await fetch('/_ryunix/action', {
|
|
@@ -3526,87 +2920,71 @@ function createActionProxy(actionId) {
|
|
|
3526
2920
|
body: JSON.stringify({ actionId, args }),
|
|
3527
2921
|
});
|
|
3528
2922
|
if (!response.ok) {
|
|
3529
|
-
const errorData = await response.json().catch(() => ({}));
|
|
2923
|
+
const errorData = (await response.json().catch(() => ({})));
|
|
3530
2924
|
throw new Error(errorData.error || 'Server Action failed');
|
|
3531
2925
|
}
|
|
3532
2926
|
return response.json();
|
|
3533
2927
|
};
|
|
3534
2928
|
}
|
|
3535
2929
|
|
|
3536
|
-
/**
|
|
3537
|
-
* @typedef {object} OverlayError
|
|
3538
|
-
* @property {string} [name]
|
|
3539
|
-
* @property {string} [message]
|
|
3540
|
-
* @property {string | string[]} [stack]
|
|
3541
|
-
* @property {{ fileName?: string, lineNumber?: number }} [__ryunix_source]
|
|
3542
|
-
*/
|
|
3543
|
-
/**
|
|
3544
|
-
* @param {unknown} propsOrError
|
|
3545
|
-
*/
|
|
3546
2930
|
function RyunixDevOverlay(propsOrError) {
|
|
3547
|
-
|
|
3548
|
-
const propsInput =
|
|
3549
|
-
/** @type {Record<string, unknown> | Error | null | undefined} */ propsOrError;
|
|
3550
|
-
// If propsOrError is an event or wrapped object, try to extract error
|
|
2931
|
+
const propsInput = propsOrError;
|
|
3551
2932
|
const rawError = propsInput &&
|
|
3552
2933
|
typeof propsInput === 'object' &&
|
|
3553
2934
|
!(propsInput instanceof Error) &&
|
|
3554
2935
|
propsInput.nativeEvent
|
|
3555
2936
|
? propsInput.error
|
|
3556
2937
|
: propsInput;
|
|
3557
|
-
/** @type {OverlayError | null} */
|
|
3558
2938
|
let error = null;
|
|
3559
2939
|
if (rawError instanceof Error) {
|
|
3560
2940
|
error = rawError;
|
|
3561
2941
|
}
|
|
3562
2942
|
else if (rawError && typeof rawError === 'object') {
|
|
3563
2943
|
if ('message' in rawError) {
|
|
3564
|
-
error =
|
|
2944
|
+
error = rawError;
|
|
3565
2945
|
}
|
|
3566
2946
|
else if ('error' in rawError) {
|
|
3567
|
-
const nested =
|
|
2947
|
+
const nested = rawError.error;
|
|
3568
2948
|
error =
|
|
3569
|
-
nested && typeof nested === 'object'
|
|
3570
|
-
? /** @type {OverlayError} */ nested
|
|
3571
|
-
: null;
|
|
2949
|
+
nested && typeof nested === 'object' ? nested : null;
|
|
3572
2950
|
}
|
|
3573
2951
|
else {
|
|
3574
|
-
error =
|
|
2952
|
+
error = rawError;
|
|
3575
2953
|
}
|
|
3576
2954
|
}
|
|
3577
|
-
// Debug string if error is broken
|
|
3578
2955
|
const debugObjectStr = JSON.stringify(propsInput, Object.getOwnPropertyNames(propsInput || {}));
|
|
3579
2956
|
const [snippetState, setSnippet] = useStore(null);
|
|
3580
2957
|
const [startLineState, setStartLine] = useStore(1);
|
|
3581
2958
|
const [errorFileState, setErrorFile] = useStore('');
|
|
3582
2959
|
const [errorLineState, setErrorLine] = useStore(0);
|
|
3583
|
-
const snippet =
|
|
3584
|
-
const startLine =
|
|
3585
|
-
const errorFile =
|
|
3586
|
-
const errorLine =
|
|
3587
|
-
// Normalize stack ensuring we have lines
|
|
3588
|
-
/** @type {string[]} */
|
|
2960
|
+
const snippet = snippetState;
|
|
2961
|
+
const startLine = startLineState;
|
|
2962
|
+
const errorFile = errorFileState;
|
|
2963
|
+
const errorLine = errorLineState;
|
|
3589
2964
|
let stackLines = [];
|
|
3590
2965
|
if (error && error.stack) {
|
|
3591
2966
|
stackLines =
|
|
3592
2967
|
typeof error.stack === 'string'
|
|
3593
|
-
? error.stack.split('\n').filter((
|
|
2968
|
+
? error.stack.split('\n').filter((line) => {
|
|
3594
2969
|
const trimmed = line.trim();
|
|
3595
2970
|
if (!trimmed)
|
|
3596
2971
|
return false;
|
|
3597
2972
|
if (trimmed.includes('node_modules'))
|
|
3598
2973
|
return false;
|
|
3599
|
-
// Filter out internal Ryunix core framework files to isolate user code
|
|
3600
2974
|
const isInternal = [
|
|
3601
|
-
'
|
|
2975
|
+
'fiber-update.js',
|
|
3602
2976
|
'workers.js',
|
|
3603
2977
|
'reconciler.js',
|
|
3604
2978
|
'commits.js',
|
|
2979
|
+
'hooks/hooks.js',
|
|
3605
2980
|
'hooks.js',
|
|
2981
|
+
'error-boundary.js',
|
|
3606
2982
|
'errorBoundary.js',
|
|
2983
|
+
'boundaries.js',
|
|
3607
2984
|
'serverBoundary.js',
|
|
3608
2985
|
'app-router.js',
|
|
3609
2986
|
'app-router-server.js',
|
|
2987
|
+
'render/render.js',
|
|
3610
2988
|
'render.js',
|
|
3611
2989
|
'createElement.js',
|
|
3612
2990
|
'index.js',
|
|
@@ -3614,7 +2992,7 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3614
2992
|
return !isInternal;
|
|
3615
2993
|
})
|
|
3616
2994
|
: Array.isArray(error.stack)
|
|
3617
|
-
?
|
|
2995
|
+
? error.stack
|
|
3618
2996
|
: [];
|
|
3619
2997
|
}
|
|
3620
2998
|
const errorName = error && typeof error.name === 'string' ? error.name : 'Unknown Error Type';
|
|
@@ -3624,22 +3002,18 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3624
3002
|
useEffect(() => {
|
|
3625
3003
|
let targetPath = null;
|
|
3626
3004
|
let targetLine = null;
|
|
3627
|
-
// 1. Direct JSX __source mapping (injected by Webpack/SWC)
|
|
3628
3005
|
const ryunixSource = error?.__ryunix_source;
|
|
3629
3006
|
if (ryunixSource?.fileName) {
|
|
3630
3007
|
targetPath = ryunixSource.fileName;
|
|
3631
3008
|
targetLine = ryunixSource.lineNumber ?? null;
|
|
3632
3009
|
}
|
|
3633
|
-
// 2. Fallback to Regex Stack Parsing
|
|
3634
3010
|
if (!targetPath || !targetLine) {
|
|
3635
3011
|
for (let i = 0; i < stackLines.length; i++) {
|
|
3636
3012
|
const line = stackLines[i];
|
|
3637
3013
|
if (!line.includes(':'))
|
|
3638
3014
|
continue;
|
|
3639
|
-
// Deterministic string-based parsing (no regex on uncontrolled data)
|
|
3640
3015
|
let matchedPath = null;
|
|
3641
3016
|
let matchedLine = null;
|
|
3642
|
-
// V8 format: "at fn (file:line:col)" — extract content between parens
|
|
3643
3017
|
const parenOpen = line.indexOf('(');
|
|
3644
3018
|
const parenClose = line.lastIndexOf(')');
|
|
3645
3019
|
if (parenOpen !== -1 && parenClose > parenOpen) {
|
|
@@ -3655,7 +3029,6 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3655
3029
|
}
|
|
3656
3030
|
}
|
|
3657
3031
|
}
|
|
3658
|
-
// V8 format without parens: "at file:line:col"
|
|
3659
3032
|
if (!matchedPath) {
|
|
3660
3033
|
const trimmed = line.trim();
|
|
3661
3034
|
if (trimmed.startsWith('at ')) {
|
|
@@ -3672,7 +3045,6 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3672
3045
|
}
|
|
3673
3046
|
}
|
|
3674
3047
|
}
|
|
3675
|
-
// Ryunix format: "file.ryx:line" or "file.jsx:line"
|
|
3676
3048
|
if (!matchedPath) {
|
|
3677
3049
|
const exts = ['.ryx', '.jsx', '.js', '.ts', '.tsx'];
|
|
3678
3050
|
const c1 = line.lastIndexOf(':');
|
|
@@ -3780,7 +3152,6 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3780
3152
|
maxHeight: '150px',
|
|
3781
3153
|
height: 'auto',
|
|
3782
3154
|
};
|
|
3783
|
-
/** @param {boolean} isErrorLine */
|
|
3784
3155
|
const lineStyle = (isErrorLine) => ({
|
|
3785
3156
|
display: 'flex',
|
|
3786
3157
|
backgroundColor: isErrorLine ? 'rgba(239, 68, 68, 0.15)' : 'transparent',
|
|
@@ -3838,9 +3209,7 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3838
3209
|
}, createElement('path', {
|
|
3839
3210
|
d: 'M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z',
|
|
3840
3211
|
}), createElement('polyline', { points: '13 2 13 9 20 9' })), errorFile, ':', errorLine), snippet &&
|
|
3841
|
-
createElement('div', { style: snippetContainerStyle }, createElement('div', { style: { display: 'flex', flexDirection: 'column' } }, ...snippetLines.map((
|
|
3842
|
-
/** @type {string} */ lineText,
|
|
3843
|
-
/** @type {number} */ index) => {
|
|
3212
|
+
createElement('div', { style: snippetContainerStyle }, createElement('div', { style: { display: 'flex', flexDirection: 'column' } }, ...snippetLines.map((lineText, index) => {
|
|
3844
3213
|
const currentLineNumber = startLine + index;
|
|
3845
3214
|
const isErrorLine = currentLineNumber === errorLine;
|
|
3846
3215
|
return createElement('div', { key: index, style: lineStyle(isErrorLine) }, createElement('span', {
|
|
@@ -3877,16 +3246,14 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3877
3246
|
flexDirection: 'column',
|
|
3878
3247
|
gap: '12px',
|
|
3879
3248
|
},
|
|
3880
|
-
}, ...stackLines.map((
|
|
3249
|
+
}, ...stackLines.map((line, i) => {
|
|
3881
3250
|
if (i === 0 &&
|
|
3882
3251
|
(line.startsWith('Error:') ||
|
|
3883
3252
|
line.startsWith('TypeError:')))
|
|
3884
3253
|
return null;
|
|
3885
|
-
// Deterministic string-based stack frame parsing (no polynomial regex)
|
|
3886
3254
|
const trimmed = line.trim();
|
|
3887
3255
|
let fnName = '<anonymous>';
|
|
3888
3256
|
let filePath = line;
|
|
3889
|
-
// V8 format: "at fnName (file:line:col)" or "at file:line:col"
|
|
3890
3257
|
if (trimmed.startsWith('at ')) {
|
|
3891
3258
|
const rest = trimmed.slice(3);
|
|
3892
3259
|
const parenOpen = rest.indexOf('(');
|
|
@@ -3897,7 +3264,6 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3897
3264
|
filePath = rest.slice(parenOpen + 1, parenClose);
|
|
3898
3265
|
}
|
|
3899
3266
|
else {
|
|
3900
|
-
// "at file:line:col" — no function name
|
|
3901
3267
|
if (rest.includes(':')) {
|
|
3902
3268
|
fnName = '<anonymous>';
|
|
3903
3269
|
}
|
|
@@ -3907,22 +3273,18 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3907
3273
|
filePath = rest;
|
|
3908
3274
|
}
|
|
3909
3275
|
}
|
|
3910
|
-
// Firefox format: "fnName@file:line:col"
|
|
3911
3276
|
else if (trimmed.includes('@')) {
|
|
3912
3277
|
const atIdx = trimmed.indexOf('@');
|
|
3913
3278
|
fnName = trimmed.slice(0, atIdx) || '<anonymous>';
|
|
3914
3279
|
filePath = trimmed.slice(atIdx + 1);
|
|
3915
3280
|
}
|
|
3916
|
-
// Ryunix format: "fnName file.ext:line"
|
|
3917
3281
|
else {
|
|
3918
3282
|
const exts = ['.ryx', '.jsx', '.js', '.ts', '.tsx'];
|
|
3919
3283
|
const parts = trimmed.split(/\s+/);
|
|
3920
3284
|
if (parts.length >= 2) {
|
|
3921
3285
|
const lastPart = parts[parts.length - 1];
|
|
3922
3286
|
const colonIdx = lastPart.indexOf(':');
|
|
3923
|
-
const fileCandidate = colonIdx > 0
|
|
3924
|
-
? lastPart.slice(0, colonIdx)
|
|
3925
|
-
: lastPart;
|
|
3287
|
+
const fileCandidate = colonIdx > 0 ? lastPart.slice(0, colonIdx) : lastPart;
|
|
3926
3288
|
if (exts.some((ext) => fileCandidate.endsWith(ext))) {
|
|
3927
3289
|
fnName = parts.slice(0, -1).join(' ');
|
|
3928
3290
|
filePath = lastPart;
|
|
@@ -3960,47 +3322,545 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3960
3322
|
: createElement('div', { style: { color: '#6b7280', fontStyle: 'italic' } }, 'No stack trace available.'))))));
|
|
3961
3323
|
}
|
|
3962
3324
|
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3325
|
+
const DEFAULT_THEME_COOKIE_NAME = 'ryunix_theme';
|
|
3326
|
+
const THEME_PREFERENCES = ['light', 'system', 'dark'];
|
|
3327
|
+
function createThemeController(options = {}) {
|
|
3328
|
+
const cookieName = options.cookieName ?? DEFAULT_THEME_COOKIE_NAME;
|
|
3329
|
+
const defaultTheme = options.defaultTheme ?? 'dark';
|
|
3330
|
+
const darkClass = options.darkClass ?? 'dark';
|
|
3331
|
+
const maxAgeSeconds = options.maxAgeSeconds ?? 365 * 24 * 60 * 60;
|
|
3332
|
+
const isThemePreference = (theme) => THEME_PREFERENCES.includes(theme);
|
|
3333
|
+
const getThemeCookie = () => {
|
|
3334
|
+
if (typeof document === 'undefined')
|
|
3335
|
+
return null;
|
|
3336
|
+
const match = document.cookie.match(new RegExp(`(?:^|;\\s*)${cookieName}=(light|system|dark)(?:;|$)`));
|
|
3337
|
+
return match ? match[1] : null;
|
|
3338
|
+
};
|
|
3339
|
+
const setThemeCookie = (theme) => {
|
|
3340
|
+
if (typeof document === 'undefined' || !isThemePreference(theme))
|
|
3341
|
+
return;
|
|
3342
|
+
document.cookie = `${cookieName}=${theme}; path=/; max-age=${maxAgeSeconds}; SameSite=Lax`;
|
|
3343
|
+
};
|
|
3344
|
+
const resolveThemeFromCookie = () => getThemeCookie() || defaultTheme;
|
|
3345
|
+
const getSystemColorScheme = () => {
|
|
3346
|
+
if (typeof window === 'undefined')
|
|
3347
|
+
return 'dark';
|
|
3348
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
3349
|
+
? 'dark'
|
|
3350
|
+
: 'light';
|
|
3351
|
+
};
|
|
3352
|
+
const resolveEffectiveTheme = (theme) => {
|
|
3353
|
+
const choice = isThemePreference(theme) ? theme : defaultTheme;
|
|
3354
|
+
if (choice === 'system')
|
|
3355
|
+
return getSystemColorScheme();
|
|
3356
|
+
return choice;
|
|
3357
|
+
};
|
|
3358
|
+
const applyTheme = (theme) => {
|
|
3359
|
+
if (typeof document === 'undefined')
|
|
3360
|
+
return;
|
|
3361
|
+
const preference = isThemePreference(theme) ? theme : defaultTheme;
|
|
3362
|
+
const effective = resolveEffectiveTheme(preference);
|
|
3363
|
+
const root = document.documentElement;
|
|
3364
|
+
root.classList.toggle(darkClass, effective === 'dark');
|
|
3365
|
+
root.dataset.theme = preference;
|
|
3366
|
+
root.dataset.themeEffective = effective;
|
|
3367
|
+
root.style.colorScheme = effective === 'dark' ? 'dark' : 'light';
|
|
3368
|
+
};
|
|
3369
|
+
const getInitScript = () => `(function(){try{var m=document.cookie.match(/(?:^|;\\s*)${cookieName}=(light|system|dark)(?:;|$)/);var t=m?m[1]:'${defaultTheme}';var dark=t==='dark'||(t==='system'&&window.matchMedia('(prefers-color-scheme: dark)').matches);var r=document.documentElement;r.classList.toggle('${darkClass}',dark);r.dataset.theme=t;r.dataset.themeEffective=dark?'dark':'light';r.style.colorScheme=dark?'dark':'light';}catch(e){document.documentElement.classList.add('${darkClass}');}})();`;
|
|
3370
|
+
const watchSystemTheme = (onChange) => {
|
|
3371
|
+
if (typeof window === 'undefined')
|
|
3372
|
+
return () => { };
|
|
3373
|
+
const media = window.matchMedia('(prefers-color-scheme: dark)');
|
|
3374
|
+
const handler = () => onChange(getSystemColorScheme());
|
|
3375
|
+
media.addEventListener('change', handler);
|
|
3376
|
+
return () => media.removeEventListener('change', handler);
|
|
3377
|
+
};
|
|
3378
|
+
return {
|
|
3379
|
+
cookieName,
|
|
3380
|
+
defaultTheme,
|
|
3381
|
+
themes: THEME_PREFERENCES,
|
|
3382
|
+
getThemeCookie,
|
|
3383
|
+
setThemeCookie,
|
|
3384
|
+
resolveThemeFromCookie,
|
|
3385
|
+
getSystemColorScheme,
|
|
3386
|
+
resolveEffectiveTheme,
|
|
3387
|
+
applyTheme,
|
|
3388
|
+
getInitScript,
|
|
3389
|
+
watchSystemTheme,
|
|
3390
|
+
};
|
|
3391
|
+
}
|
|
3392
|
+
const themeController = createThemeController();
|
|
3393
|
+
const { cookieName: THEME_COOKIE_NAME, defaultTheme, themes, getThemeCookie, setThemeCookie, resolveThemeFromCookie, getSystemColorScheme, resolveEffectiveTheme, applyTheme, watchSystemTheme, } = themeController;
|
|
3394
|
+
const themeInitScript = themeController.getInitScript();
|
|
3395
|
+
|
|
3396
|
+
const SunIcon = ({ className = '' }) => createElement('svg', {
|
|
3397
|
+
xmlns: 'http://www.w3.org/2000/svg',
|
|
3398
|
+
viewBox: '0 0 24 24',
|
|
3399
|
+
fill: 'none',
|
|
3400
|
+
stroke: 'currentColor',
|
|
3401
|
+
strokeWidth: 1.75,
|
|
3402
|
+
strokeLinecap: 'round',
|
|
3403
|
+
strokeLinejoin: 'round',
|
|
3404
|
+
className,
|
|
3405
|
+
'aria-hidden': true,
|
|
3406
|
+
}, createElement('circle', { cx: 12, cy: 12, r: 4 }), createElement('path', {
|
|
3407
|
+
d: 'M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41',
|
|
3408
|
+
}));
|
|
3409
|
+
const MonitorIcon = ({ className = '' }) => createElement('svg', {
|
|
3410
|
+
xmlns: 'http://www.w3.org/2000/svg',
|
|
3411
|
+
viewBox: '0 0 24 24',
|
|
3412
|
+
fill: 'none',
|
|
3413
|
+
stroke: 'currentColor',
|
|
3414
|
+
strokeWidth: 1.75,
|
|
3415
|
+
strokeLinecap: 'round',
|
|
3416
|
+
strokeLinejoin: 'round',
|
|
3417
|
+
className,
|
|
3418
|
+
'aria-hidden': true,
|
|
3419
|
+
}, createElement('rect', { x: 2, y: 3, width: 20, height: 14, rx: 2 }), createElement('path', { d: 'M8 21h8M12 17v4' }));
|
|
3420
|
+
const MoonIcon = ({ className = '' }) => createElement('svg', {
|
|
3421
|
+
xmlns: 'http://www.w3.org/2000/svg',
|
|
3422
|
+
viewBox: '0 0 24 24',
|
|
3423
|
+
fill: 'none',
|
|
3424
|
+
stroke: 'currentColor',
|
|
3425
|
+
strokeWidth: 1.75,
|
|
3426
|
+
strokeLinecap: 'round',
|
|
3427
|
+
strokeLinejoin: 'round',
|
|
3428
|
+
className,
|
|
3429
|
+
'aria-hidden': true,
|
|
3430
|
+
}, createElement('path', {
|
|
3431
|
+
d: 'M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z',
|
|
3432
|
+
}));
|
|
3433
|
+
const ICONS = {
|
|
3434
|
+
light: SunIcon,
|
|
3435
|
+
system: MonitorIcon,
|
|
3436
|
+
dark: MoonIcon,
|
|
3437
|
+
};
|
|
3438
|
+
function ThemeToggle({ labels, className = '', controller = themeController, }) {
|
|
3439
|
+
const [theme, setTheme] = useStore(controller.defaultTheme);
|
|
3440
|
+
useEffect(() => {
|
|
3441
|
+
const saved = controller.resolveThemeFromCookie();
|
|
3442
|
+
setTheme(saved);
|
|
3443
|
+
controller.applyTheme(saved);
|
|
3444
|
+
}, []);
|
|
3445
|
+
useEffect(() => {
|
|
3446
|
+
if (theme !== 'system')
|
|
3447
|
+
return undefined;
|
|
3448
|
+
return controller.watchSystemTheme(() => controller.applyTheme('system'));
|
|
3449
|
+
}, [theme]);
|
|
3450
|
+
const selectTheme = (next) => {
|
|
3451
|
+
if (next === theme)
|
|
3452
|
+
return;
|
|
3453
|
+
setTheme(next);
|
|
3454
|
+
controller.setThemeCookie(next);
|
|
3455
|
+
controller.applyTheme(next);
|
|
3456
|
+
};
|
|
3457
|
+
return createElement('div', {
|
|
3458
|
+
className: `ryx-theme-segment ${className}`.trim(),
|
|
3459
|
+
role: 'radiogroup',
|
|
3460
|
+
'aria-label': labels.title,
|
|
3461
|
+
}, ...controller.themes.map((id) => {
|
|
3462
|
+
const active = theme === id;
|
|
3463
|
+
const Icon = ICONS[id];
|
|
3464
|
+
return createElement('button', {
|
|
3465
|
+
key: id,
|
|
3466
|
+
type: 'button',
|
|
3467
|
+
role: 'radio',
|
|
3468
|
+
'aria-checked': active ? 'true' : 'false',
|
|
3469
|
+
title: labels[id],
|
|
3470
|
+
className: `ryx-theme-segment-btn${active ? ' ryx-theme-segment-btn--active' : ''}`,
|
|
3471
|
+
onClick: () => selectTheme(id),
|
|
3472
|
+
}, Icon({ className: 'ryx-theme-segment-icon' }), createElement('span', { className: 'ryx-theme-segment-sr-only' }, labels[id]));
|
|
3473
|
+
}));
|
|
3474
|
+
}
|
|
3475
|
+
function ThemeInitScript({ controller = themeController, } = {}) {
|
|
3476
|
+
return createElement('script', {
|
|
3477
|
+
dangerouslySetInnerHTML: { __html: controller.getInitScript() },
|
|
3478
|
+
});
|
|
3479
|
+
}
|
|
3480
|
+
|
|
3481
|
+
const renderBrand = ({ image, title, href = '/', imageAlt, blockClass, linkClass, imageClass, titleClass, }) => {
|
|
3482
|
+
if (!image && !title)
|
|
3483
|
+
return null;
|
|
3484
|
+
const content = [
|
|
3485
|
+
image
|
|
3486
|
+
? createElement('img', {
|
|
3487
|
+
src: image,
|
|
3488
|
+
alt: imageAlt ?? title ?? '',
|
|
3489
|
+
className: imageClass,
|
|
3490
|
+
})
|
|
3491
|
+
: null,
|
|
3492
|
+
title ? createElement('span', { className: titleClass }, title) : null,
|
|
3493
|
+
].filter(Boolean);
|
|
3494
|
+
return createElement('div', { className: blockClass }, createElement('a', { href, className: linkClass }, ...content));
|
|
3495
|
+
};
|
|
3496
|
+
const renderChildren = (children) => {
|
|
3497
|
+
if (children == null || children === false)
|
|
3498
|
+
return [];
|
|
3499
|
+
return flattenArray(Array.isArray(children) ? children : [children]).filter((child) => child != null && child !== false);
|
|
3500
|
+
};
|
|
3501
|
+
const renderSlot = (className, children) => {
|
|
3502
|
+
const items = renderChildren(children);
|
|
3503
|
+
if (items.length === 0)
|
|
3504
|
+
return null;
|
|
3505
|
+
return createElement('div', { className }, ...items);
|
|
3506
|
+
};
|
|
3507
|
+
function Main({ maxWidth, className = '', innerClassName = '', children, }) {
|
|
3508
|
+
const innerStyle = maxWidth != null && maxWidth !== ''
|
|
3509
|
+
? { maxWidth, marginInline: 'auto', width: '100%' }
|
|
3510
|
+
: undefined;
|
|
3511
|
+
return createElement('main', {
|
|
3512
|
+
className: `ryx-main ${className}`.trim(),
|
|
3513
|
+
}, createElement('div', {
|
|
3514
|
+
className: `ryx-main-inner ${innerClassName}`.trim(),
|
|
3515
|
+
style: innerStyle,
|
|
3516
|
+
}, ...renderChildren(children)));
|
|
3517
|
+
}
|
|
3518
|
+
function Header({ image, title, href = '/', imageAlt, sticky = true, className = '', children, }) {
|
|
3519
|
+
const headerClass = [
|
|
3520
|
+
'ryx-header',
|
|
3521
|
+
sticky !== false ? 'ryx-header--sticky' : '',
|
|
3522
|
+
className,
|
|
3523
|
+
]
|
|
3524
|
+
.filter(Boolean)
|
|
3525
|
+
.join(' ');
|
|
3526
|
+
return createElement('header', {
|
|
3527
|
+
className: headerClass,
|
|
3528
|
+
}, createElement('div', { className: 'ryx-header-inner' }, createElement('div', { className: 'ryx-header-start' }, renderBrand({
|
|
3529
|
+
image,
|
|
3530
|
+
title,
|
|
3531
|
+
href,
|
|
3532
|
+
imageAlt,
|
|
3533
|
+
blockClass: 'ryx-header-brand',
|
|
3534
|
+
linkClass: 'ryx-header-brand-link',
|
|
3535
|
+
imageClass: 'ryx-header-brand-image',
|
|
3536
|
+
titleClass: 'ryx-header-brand-title',
|
|
3537
|
+
})), renderSlot('ryx-header-end', children)));
|
|
3538
|
+
}
|
|
3539
|
+
function Footer({ image, title, description, href = '/', imageAlt, className = '', children, bottomStart, bottomEnd, }) {
|
|
3540
|
+
const brand = renderBrand({
|
|
3541
|
+
image,
|
|
3542
|
+
title,
|
|
3543
|
+
href,
|
|
3544
|
+
imageAlt,
|
|
3545
|
+
blockClass: 'ryx-footer-brand',
|
|
3546
|
+
linkClass: 'ryx-footer-brand-link',
|
|
3547
|
+
imageClass: 'ryx-footer-brand-image',
|
|
3548
|
+
titleClass: 'ryx-footer-brand-title',
|
|
3549
|
+
});
|
|
3550
|
+
const brandColumn = brand || description
|
|
3551
|
+
? createElement('div', { className: 'ryx-footer-brand-column' }, brand, description
|
|
3552
|
+
? createElement('p', { className: 'ryx-footer-description' }, description)
|
|
3553
|
+
: null)
|
|
3554
|
+
: null;
|
|
3555
|
+
const columns = renderChildren(children).map((child, index) => createElement('div', { key: index, className: 'ryx-footer-column' }, child));
|
|
3556
|
+
const bottomStartSlot = renderSlot('ryx-footer-bottom-start', bottomStart);
|
|
3557
|
+
const bottomEndSlot = renderSlot('ryx-footer-bottom-end', bottomEnd);
|
|
3558
|
+
const bottomBar = bottomStartSlot || bottomEndSlot
|
|
3559
|
+
? createElement('div', { className: 'ryx-footer-bottom' }, bottomStartSlot, bottomEndSlot)
|
|
3560
|
+
: null;
|
|
3561
|
+
return createElement('footer', {
|
|
3562
|
+
className: `ryx-footer ${className}`.trim(),
|
|
3563
|
+
}, createElement('div', {
|
|
3564
|
+
className: 'ryx-footer-accent',
|
|
3565
|
+
'aria-hidden': 'true',
|
|
3566
|
+
}), createElement('div', {
|
|
3567
|
+
className: 'ryx-footer-glow',
|
|
3568
|
+
'aria-hidden': 'true',
|
|
3569
|
+
}), createElement('div', { className: 'ryx-footer-inner' }, brandColumn || columns.length > 0
|
|
3570
|
+
? createElement('div', { className: 'ryx-footer-grid' }, brandColumn, ...columns)
|
|
3571
|
+
: null, bottomBar));
|
|
3572
|
+
}
|
|
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
|
+
}
|
|
3969
3806
|
|
|
3970
3807
|
var Ryunix = /*#__PURE__*/Object.freeze({
|
|
3971
3808
|
__proto__: null,
|
|
3972
3809
|
Children: Children,
|
|
3810
|
+
DEFAULT_LOCALE_COOKIE_NAME: DEFAULT_LOCALE_COOKIE_NAME,
|
|
3811
|
+
DEFAULT_THEME_COOKIE_NAME: DEFAULT_THEME_COOKIE_NAME,
|
|
3973
3812
|
ErrorBoundary: ErrorBoundary,
|
|
3813
|
+
Footer: Footer,
|
|
3974
3814
|
Fragment: Fragment,
|
|
3975
|
-
|
|
3815
|
+
Header: Header,
|
|
3816
|
+
Hooks: index,
|
|
3976
3817
|
HydrationBoundary: HydrationBoundary,
|
|
3818
|
+
INTERNAL_META_KEYS: INTERNAL_META_KEYS,
|
|
3977
3819
|
Link: Link,
|
|
3820
|
+
Main: Main,
|
|
3978
3821
|
NavLink: NavLink,
|
|
3979
3822
|
Priority: Priority,
|
|
3980
3823
|
RouterProvider: RouterProvider,
|
|
3981
3824
|
RyunixDevOverlay: RyunixDevOverlay,
|
|
3982
3825
|
ServerBoundary: ServerBoundary,
|
|
3983
3826
|
Suspense: Suspense,
|
|
3827
|
+
THEME_PREFERENCES: THEME_PREFERENCES,
|
|
3828
|
+
ThemeInitScript: ThemeInitScript,
|
|
3829
|
+
ThemeToggle: ThemeToggle,
|
|
3830
|
+
applyTheme: applyTheme,
|
|
3984
3831
|
batchUpdates: batchUpdates,
|
|
3985
3832
|
cloneElement: cloneElement,
|
|
3986
3833
|
createActionProxy: createActionProxy,
|
|
3834
|
+
createAppI18n: createAppI18n,
|
|
3987
3835
|
createContext: createContext,
|
|
3988
3836
|
createElement: createElement,
|
|
3837
|
+
createI18n: createI18n,
|
|
3838
|
+
createI18nFromConfig: createI18nFromConfig,
|
|
3989
3839
|
createPortal: createPortal,
|
|
3840
|
+
createThemeController: createThemeController,
|
|
3990
3841
|
deepEqual: deepEqual,
|
|
3842
|
+
defineMessages: defineMessages,
|
|
3991
3843
|
escapeHtml: escapeHtml,
|
|
3992
3844
|
forwardRef: forwardRef,
|
|
3845
|
+
getLocaleFromPath: getLocaleFromPath,
|
|
3846
|
+
getRyunixI18nConfig: getRyunixI18nConfig,
|
|
3993
3847
|
getState: getState,
|
|
3848
|
+
getSystemColorScheme: getSystemColorScheme,
|
|
3849
|
+
getThemeCookie: getThemeCookie,
|
|
3994
3850
|
hydrate: hydrate,
|
|
3995
3851
|
init: init,
|
|
3996
3852
|
isValidElement: isValidElement,
|
|
3997
3853
|
lazy: lazy,
|
|
3854
|
+
localePath: localePath,
|
|
3998
3855
|
logHydrationBoundaryMismatch: logHydrationBoundaryMismatch,
|
|
3999
3856
|
logHydrationBoundaryRecovery: logHydrationBoundaryRecovery,
|
|
4000
3857
|
logHydrationFatal: logHydrationFatal,
|
|
4001
3858
|
logHydrationInfo: logHydrationInfo,
|
|
4002
3859
|
logHydrationRecoverable: logHydrationRecoverable,
|
|
4003
3860
|
memo: memo,
|
|
3861
|
+
mergeRouteMetadata: mergeRouteMetadata,
|
|
3862
|
+
normalizeI18nConfig: normalizeI18nConfig,
|
|
3863
|
+
pickLocale: pickLocale,
|
|
4004
3864
|
preload: preload,
|
|
4005
3865
|
profiler: profiler,
|
|
4006
3866
|
render: render,
|
|
@@ -4008,8 +3868,15 @@ var Ryunix = /*#__PURE__*/Object.freeze({
|
|
|
4008
3868
|
renderToString: renderToString,
|
|
4009
3869
|
renderToStringAsync: renderToStringAsync,
|
|
4010
3870
|
resetIdCounter: resetIdCounter,
|
|
3871
|
+
resolveEffectiveTheme: resolveEffectiveTheme,
|
|
3872
|
+
resolvePageMetadata: resolvePageMetadata,
|
|
3873
|
+
resolveThemeFromCookie: resolveThemeFromCookie,
|
|
4011
3874
|
safeRender: safeRender,
|
|
3875
|
+
setThemeCookie: setThemeCookie,
|
|
4012
3876
|
shallowEqual: shallowEqual,
|
|
3877
|
+
swapLocalePath: swapLocalePath,
|
|
3878
|
+
themeController: themeController,
|
|
3879
|
+
themeInitScript: themeInitScript,
|
|
4013
3880
|
useCallback: useCallback,
|
|
4014
3881
|
useDebounce: useDebounce,
|
|
4015
3882
|
useDeferredValue: useDeferredValue,
|
|
@@ -4033,10 +3900,79 @@ var Ryunix = /*#__PURE__*/Object.freeze({
|
|
|
4033
3900
|
useSwitch: useSwitch,
|
|
4034
3901
|
useThrottle: useThrottle,
|
|
4035
3902
|
useTransition: useTransition,
|
|
3903
|
+
watchSystemTheme: watchSystemTheme,
|
|
4036
3904
|
withProfiler: withProfiler
|
|
4037
3905
|
});
|
|
4038
3906
|
|
|
4039
|
-
|
|
3907
|
+
const { Provider: MDXProvider, useContext: useMDXComponents } = createContext('ryunix.mdx', {});
|
|
3908
|
+
const getMDXComponents = (components) => {
|
|
3909
|
+
const contextComponents = useMDXComponents();
|
|
3910
|
+
return {
|
|
3911
|
+
...contextComponents,
|
|
3912
|
+
...components,
|
|
3913
|
+
};
|
|
3914
|
+
};
|
|
3915
|
+
const RYUNIX_STYLE_ENABLED = globalThis.process && String(globalThis.process.env?.RYUNIX_STYLE) === 'true';
|
|
3916
|
+
const ryxProps = (props) => {
|
|
3917
|
+
const { unstyled, ...rest } = props;
|
|
3918
|
+
if (unstyled || rest['data-ryx-unstyled']) {
|
|
3919
|
+
return { ...rest, 'data-ryx-unstyled': true };
|
|
3920
|
+
}
|
|
3921
|
+
return rest;
|
|
3922
|
+
};
|
|
3923
|
+
const mergeClassName = (existing, base) => {
|
|
3924
|
+
if (!existing)
|
|
3925
|
+
return base;
|
|
3926
|
+
if (Array.isArray(existing))
|
|
3927
|
+
return [...existing, base].join(' ');
|
|
3928
|
+
return `${existing} ${base}`;
|
|
3929
|
+
};
|
|
3930
|
+
const styledMdxHost = (tag, ryxClass, props) => {
|
|
3931
|
+
const next = ryxProps(props);
|
|
3932
|
+
if (!RYUNIX_STYLE_ENABLED || !ryxClass || next['data-ryx-unstyled']) {
|
|
3933
|
+
return createElement(tag, next);
|
|
3934
|
+
}
|
|
3935
|
+
return createElement(tag, {
|
|
3936
|
+
...next,
|
|
3937
|
+
className: mergeClassName(next.className, ryxClass),
|
|
3938
|
+
});
|
|
3939
|
+
};
|
|
3940
|
+
const defaultComponents = {
|
|
3941
|
+
h1: (props) => styledMdxHost('h1', 'ryx-h1', props),
|
|
3942
|
+
h2: (props) => styledMdxHost('h2', 'ryx-h2', props),
|
|
3943
|
+
h3: (props) => styledMdxHost('h3', 'ryx-h3', props),
|
|
3944
|
+
h4: (props) => styledMdxHost('h4', 'ryx-h4', props),
|
|
3945
|
+
h5: (props) => styledMdxHost('h5', 'ryx-h5', props),
|
|
3946
|
+
h6: (props) => styledMdxHost('h6', 'ryx-h6', props),
|
|
3947
|
+
p: (props) => styledMdxHost('p', 'ryx-p', props),
|
|
3948
|
+
a: (props) => styledMdxHost('a', 'ryx-a', props),
|
|
3949
|
+
strong: (props) => styledMdxHost('strong', 'ryx-strong', props),
|
|
3950
|
+
em: (props) => styledMdxHost('em', 'ryx-em', props),
|
|
3951
|
+
code: (props) => styledMdxHost('code', 'ryx-code', props),
|
|
3952
|
+
ul: (props) => styledMdxHost('ul', 'ryx-ul', props),
|
|
3953
|
+
ol: (props) => styledMdxHost('ol', 'ryx-ol', props),
|
|
3954
|
+
li: (props) => styledMdxHost('li', 'ryx-li', props),
|
|
3955
|
+
blockquote: (props) => styledMdxHost('blockquote', 'ryx-blockquote', props),
|
|
3956
|
+
pre: (props) => styledMdxHost('pre', 'ryx-pre', props),
|
|
3957
|
+
hr: (props) => styledMdxHost('hr', 'ryx-hr', props),
|
|
3958
|
+
table: (props) => styledMdxHost('table', 'ryx-table', props),
|
|
3959
|
+
thead: (props) => styledMdxHost('thead', 'ryx-thead', props),
|
|
3960
|
+
tbody: (props) => styledMdxHost('tbody', 'ryx-tbody', props),
|
|
3961
|
+
tr: (props) => styledMdxHost('tr', 'ryx-tr', props),
|
|
3962
|
+
th: (props) => styledMdxHost('th', 'ryx-th', props),
|
|
3963
|
+
td: (props) => styledMdxHost('td', 'ryx-td', props),
|
|
3964
|
+
img: (props) => styledMdxHost('img', 'ryx-img', props),
|
|
3965
|
+
};
|
|
3966
|
+
const Image = ({ src, ...props }) => {
|
|
3967
|
+
return createElement('img', { ...props, src });
|
|
3968
|
+
};
|
|
3969
|
+
const MDXContent = ({ children, components = {}, }) => {
|
|
3970
|
+
const mergedComponents = getMDXComponents(components);
|
|
3971
|
+
return createElement(MDXProvider, { value: mergedComponents }, createElement('div', null, children));
|
|
3972
|
+
};
|
|
3973
|
+
|
|
3974
|
+
if (typeof window !== 'undefined')
|
|
3975
|
+
window.Ryunix = Ryunix;
|
|
4040
3976
|
|
|
4041
|
-
export { Children, ErrorBoundary, Fragment,
|
|
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 };
|
|
4042
3978
|
//# sourceMappingURL=Ryunix.esm.js.map
|