@unsetsoft/ryunixjs 1.2.5-canary.11 → 1.2.5-canary.12
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 +1436 -1876
- package/dist/Ryunix.esm.js.map +1 -1
- package/dist/Ryunix.umd.js +1452 -1874
- 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 +12 -5
- 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 +126 -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,6 @@ const updateDom = (dom, prevProps = {}, nextProps = {}) => {
|
|
|
512
426
|
const el = dom;
|
|
513
427
|
const domEl = el;
|
|
514
428
|
const handlerMap = domEl._ryunixHandlers;
|
|
515
|
-
// Remove old event listeners
|
|
516
429
|
Object.keys(prevProps)
|
|
517
430
|
.filter(isEvent)
|
|
518
431
|
.filter((key) => isGone(nextProps)(key) || isNew(prevProps, nextProps)(key))
|
|
@@ -532,12 +445,10 @@ const updateDom = (dom, prevProps = {}, nextProps = {}) => {
|
|
|
532
445
|
}
|
|
533
446
|
}
|
|
534
447
|
});
|
|
535
|
-
// Remove old properties
|
|
536
448
|
Object.keys(prevProps)
|
|
537
449
|
.filter(isProperty)
|
|
538
450
|
.filter(isGone(nextProps))
|
|
539
451
|
.forEach((propKey) => {
|
|
540
|
-
// Skip special properties
|
|
541
452
|
if (propKey === STRINGS.STYLE ||
|
|
542
453
|
propKey === OLD_STRINGS.STYLE ||
|
|
543
454
|
propKey === STRINGS.CLASS_NAME ||
|
|
@@ -549,59 +460,45 @@ const updateDom = (dom, prevProps = {}, nextProps = {}) => {
|
|
|
549
460
|
el.removeAttribute(attrName);
|
|
550
461
|
}
|
|
551
462
|
else {
|
|
552
|
-
|
|
463
|
+
el[propKey] = '';
|
|
553
464
|
el.removeAttribute(propKey);
|
|
554
465
|
}
|
|
555
466
|
});
|
|
556
|
-
// Set new properties
|
|
557
467
|
Object.keys(nextProps)
|
|
558
468
|
.filter(isProperty)
|
|
559
469
|
.filter(isNew(prevProps, nextProps))
|
|
560
470
|
.forEach((propKey) => {
|
|
561
471
|
try {
|
|
562
|
-
// Handle style properties
|
|
563
472
|
if (propKey === STRINGS.STYLE || propKey === OLD_STRINGS.STYLE) {
|
|
564
473
|
const styleValue = nextProps[propKey];
|
|
565
|
-
applyStyles(el,
|
|
474
|
+
applyStyles(el, styleValue);
|
|
566
475
|
}
|
|
567
|
-
// Handle className properties
|
|
568
476
|
else if (propKey === STRINGS.CLASS_NAME) {
|
|
569
|
-
applyClasses(el,
|
|
570
|
-
/** @type {string} */ prevProps[STRINGS.CLASS_NAME],
|
|
571
|
-
/** @type {string} */ nextProps[STRINGS.CLASS_NAME]);
|
|
477
|
+
applyClasses(el, prevProps[STRINGS.CLASS_NAME], nextProps[STRINGS.CLASS_NAME]);
|
|
572
478
|
}
|
|
573
479
|
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]);
|
|
480
|
+
applyClasses(el, prevProps[OLD_STRINGS.CLASS_NAME], nextProps[OLD_STRINGS.CLASS_NAME]);
|
|
577
481
|
}
|
|
578
|
-
// Handle other properties
|
|
579
482
|
else {
|
|
580
|
-
// Special handling for value and checked (controlled components)
|
|
581
483
|
if (propKey === 'value' || propKey === 'checked') {
|
|
582
|
-
if (
|
|
583
|
-
|
|
584
|
-
/** @type {Record<string, unknown>} */ /** @type {unknown} */ el[propKey] = nextProps[propKey];
|
|
484
|
+
if (el[propKey] !== nextProps[propKey]) {
|
|
485
|
+
el[propKey] = nextProps[propKey];
|
|
585
486
|
}
|
|
586
487
|
}
|
|
587
488
|
else {
|
|
588
489
|
const isSvgNode = el instanceof SVGElement;
|
|
589
490
|
if (isSvgNode) {
|
|
590
491
|
const attrName = toSvgAttrName(propKey);
|
|
591
|
-
/** @type {unknown} */
|
|
592
492
|
const svgValidated = checkAttributeUri(attrName, nextProps[propKey]);
|
|
593
|
-
|
|
594
|
-
el.setAttribute(attrName, /** @type {string} */ svgValidated);
|
|
493
|
+
el.setAttribute(attrName, svgValidated);
|
|
595
494
|
}
|
|
596
495
|
else {
|
|
597
496
|
const attrVal = nextProps[propKey];
|
|
598
|
-
/** @type {unknown} */
|
|
599
497
|
const safeValue = checkAttributeUri(propKey, attrVal);
|
|
600
|
-
|
|
601
|
-
// Best effort: set html attributes if it's not a primitive component property
|
|
498
|
+
el[propKey] = safeValue;
|
|
602
499
|
if (typeof attrVal !== 'object' &&
|
|
603
500
|
typeof attrVal !== 'function') {
|
|
604
|
-
el.setAttribute(propKey,
|
|
501
|
+
el.setAttribute(propKey, safeValue);
|
|
605
502
|
}
|
|
606
503
|
}
|
|
607
504
|
}
|
|
@@ -613,7 +510,6 @@ const updateDom = (dom, prevProps = {}, nextProps = {}) => {
|
|
|
613
510
|
}
|
|
614
511
|
}
|
|
615
512
|
});
|
|
616
|
-
// Add new event listeners
|
|
617
513
|
Object.keys(nextProps)
|
|
618
514
|
.filter(isEvent)
|
|
619
515
|
.filter(isNew(prevProps, nextProps))
|
|
@@ -623,15 +519,6 @@ const updateDom = (dom, prevProps = {}, nextProps = {}) => {
|
|
|
623
519
|
const handler = (e) => {
|
|
624
520
|
runWithPriority(Priority.IMMEDIATE, () => nextProps[propKey](e));
|
|
625
521
|
};
|
|
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
522
|
if (!domEl._ryunixHandlers) {
|
|
636
523
|
domEl._ryunixHandlers = new Map();
|
|
637
524
|
}
|
|
@@ -645,10 +532,6 @@ const updateDom = (dom, prevProps = {}, nextProps = {}) => {
|
|
|
645
532
|
}
|
|
646
533
|
});
|
|
647
534
|
};
|
|
648
|
-
/**
|
|
649
|
-
* Clear all children from a DOM element
|
|
650
|
-
* @param {HTMLElement} container - DOM element to clear
|
|
651
|
-
*/
|
|
652
535
|
const clearContainer = (container) => {
|
|
653
536
|
if (!container)
|
|
654
537
|
return;
|
|
@@ -676,10 +559,6 @@ function createPortal(children, container) {
|
|
|
676
559
|
}
|
|
677
560
|
|
|
678
561
|
const PREFIX = '[Ryunix Hydration]';
|
|
679
|
-
/**
|
|
680
|
-
* @param {'warn' | 'error'} level
|
|
681
|
-
* @param {string} message
|
|
682
|
-
*/
|
|
683
562
|
const emit = (level, message) => {
|
|
684
563
|
const line = `${PREFIX} ${message}`;
|
|
685
564
|
if (level === 'error') {
|
|
@@ -691,16 +570,11 @@ const emit = (level, message) => {
|
|
|
691
570
|
};
|
|
692
571
|
const shouldReportStrict = () => process.env.NODE_ENV !== 'production' &&
|
|
693
572
|
process.env.RYUNIX_HYDRATION_STRICT === 'true';
|
|
694
|
-
/** @param {string} message */
|
|
695
573
|
const logHydrationInfo = (message) => {
|
|
696
574
|
if (!shouldReportStrict())
|
|
697
575
|
return;
|
|
698
576
|
emit('warn', message);
|
|
699
577
|
};
|
|
700
|
-
/**
|
|
701
|
-
* Log the first hydration DOM mismatch (tag/text vs client tree).
|
|
702
|
-
* @param {string} detail
|
|
703
|
-
*/
|
|
704
578
|
const logHydrationMismatch = (detail) => {
|
|
705
579
|
const state = getState();
|
|
706
580
|
if (state.hydrationMismatchReported)
|
|
@@ -709,9 +583,6 @@ const logHydrationMismatch = (detail) => {
|
|
|
709
583
|
const level = process.env.NODE_ENV === 'production' ? 'error' : 'warn';
|
|
710
584
|
emit(level, `${detail} Server HTML did not match the client render. Falling back to client-side render.`);
|
|
711
585
|
};
|
|
712
|
-
/**
|
|
713
|
-
* @param {string} detail
|
|
714
|
-
*/
|
|
715
586
|
const logHydrationBoundaryMismatch = (detail) => {
|
|
716
587
|
const state = getState();
|
|
717
588
|
if (state.hydrationBoundaryMismatchReported)
|
|
@@ -720,18 +591,11 @@ const logHydrationBoundaryMismatch = (detail) => {
|
|
|
720
591
|
const level = process.env.NODE_ENV === 'production' ? 'error' : 'warn';
|
|
721
592
|
emit(level, `${detail} Recovering the nearest hydration boundary with a scoped client render.`);
|
|
722
593
|
};
|
|
723
|
-
/**
|
|
724
|
-
* @param {string} detail
|
|
725
|
-
*/
|
|
726
594
|
const logHydrationRecoverable = (detail) => {
|
|
727
595
|
if (!shouldReportStrict())
|
|
728
596
|
return;
|
|
729
597
|
emit('warn', `Recovered a hydration mismatch (${detail}) without root fallback.`);
|
|
730
598
|
};
|
|
731
|
-
/**
|
|
732
|
-
* Log when hydration failed and the SSR container is being cleared.
|
|
733
|
-
* @param {string} [reason]
|
|
734
|
-
*/
|
|
735
599
|
const logHydrationFailure = (reason = '') => {
|
|
736
600
|
const state = getState();
|
|
737
601
|
if (state.hydrationFailureReported)
|
|
@@ -745,10 +609,6 @@ const logHydrationFailure = (reason = '') => {
|
|
|
745
609
|
const level = process.env.NODE_ENV === 'production' ? 'error' : 'warn';
|
|
746
610
|
emit(level, `${detail}Clearing #__ryunix and remounting on the client.`);
|
|
747
611
|
};
|
|
748
|
-
/**
|
|
749
|
-
* Log when leftover SSR nodes are removed after hydration (soft mismatch).
|
|
750
|
-
* @param {number} count
|
|
751
|
-
*/
|
|
752
612
|
const logHydrationUnmatchedNodes = (count) => {
|
|
753
613
|
if (!count)
|
|
754
614
|
return;
|
|
@@ -758,7 +618,6 @@ const logHydrationUnmatchedNodes = (count) => {
|
|
|
758
618
|
state.hydrationUnmatchedReported = true;
|
|
759
619
|
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
620
|
};
|
|
761
|
-
/** Log CSR recovery after a failed hydration pass. */
|
|
762
621
|
const logHydrationRecovery = () => {
|
|
763
622
|
const state = getState();
|
|
764
623
|
if (state.hydrationRecoveryReported)
|
|
@@ -766,7 +625,6 @@ const logHydrationRecovery = () => {
|
|
|
766
625
|
state.hydrationRecoveryReported = true;
|
|
767
626
|
emit('warn', 'Remounting the application on the client after hydration failure.');
|
|
768
627
|
};
|
|
769
|
-
/** Log scoped boundary recovery after a local mismatch. */
|
|
770
628
|
const logHydrationBoundaryRecovery = () => {
|
|
771
629
|
const state = getState();
|
|
772
630
|
if (state.hydrationBoundaryRecoveryReported)
|
|
@@ -774,13 +632,9 @@ const logHydrationBoundaryRecovery = () => {
|
|
|
774
632
|
state.hydrationBoundaryRecoveryReported = true;
|
|
775
633
|
emit('warn', 'Remounting a hydration boundary after local mismatch.');
|
|
776
634
|
};
|
|
777
|
-
/**
|
|
778
|
-
* @param {string} reason
|
|
779
|
-
*/
|
|
780
635
|
const logHydrationFatal = (reason) => {
|
|
781
636
|
emit('error', reason);
|
|
782
637
|
};
|
|
783
|
-
/** Reset per-mount hydration log flags (call from init). */
|
|
784
638
|
const resetHydrationLogFlags = () => {
|
|
785
639
|
const state = getState();
|
|
786
640
|
state.hydrationMismatchReported = false;
|
|
@@ -791,25 +645,14 @@ const resetHydrationLogFlags = () => {
|
|
|
791
645
|
state.hydrationBoundaryRecoveryReported = false;
|
|
792
646
|
};
|
|
793
647
|
|
|
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
648
|
const runLayoutEffects = (fiber) => {
|
|
804
649
|
if (!fiber?.hooks?.length)
|
|
805
650
|
return;
|
|
806
651
|
for (let i = 0; i < fiber.hooks.length; i++) {
|
|
807
|
-
/** @type {import('../types/internal.js').RyunixHook & { isLayout?: boolean }} */
|
|
808
652
|
const hook = fiber.hooks[i];
|
|
809
653
|
if (hook.type === RYUNIX_TYPES.RYUNIX_EFFECT &&
|
|
810
654
|
hook.isLayout &&
|
|
811
655
|
is.function(hook.effect)) {
|
|
812
|
-
// Cancel previous layout cleanup if exists
|
|
813
656
|
if (is.function(hook.cancel)) {
|
|
814
657
|
try {
|
|
815
658
|
hook.cancel();
|
|
@@ -820,11 +663,10 @@ const runLayoutEffects = (fiber) => {
|
|
|
820
663
|
}
|
|
821
664
|
}
|
|
822
665
|
}
|
|
823
|
-
// Run new layout effect synchronously
|
|
824
666
|
try {
|
|
825
667
|
const cleanup = hook.effect();
|
|
826
668
|
hook.cancel = is.function(cleanup)
|
|
827
|
-
?
|
|
669
|
+
? cleanup
|
|
828
670
|
: null;
|
|
829
671
|
}
|
|
830
672
|
catch (error) {
|
|
@@ -837,20 +679,14 @@ const runLayoutEffects = (fiber) => {
|
|
|
837
679
|
}
|
|
838
680
|
}
|
|
839
681
|
};
|
|
840
|
-
/**
|
|
841
|
-
* Run normal (non-layout) effects asynchronously after paint.
|
|
842
|
-
* @param {RyunixFiber} fiber
|
|
843
|
-
*/
|
|
844
682
|
const runNormalEffects = (fiber) => {
|
|
845
683
|
if (!fiber?.hooks?.length)
|
|
846
684
|
return;
|
|
847
685
|
for (let i = 0; i < fiber.hooks.length; i++) {
|
|
848
|
-
/** @type {import('../types/internal.js').RyunixHook & { isLayout?: boolean }} */
|
|
849
686
|
const hook = fiber.hooks[i];
|
|
850
687
|
if (hook.type === RYUNIX_TYPES.RYUNIX_EFFECT &&
|
|
851
688
|
!hook.isLayout &&
|
|
852
689
|
is.function(hook.effect)) {
|
|
853
|
-
// Cancel previous cleanup if exists
|
|
854
690
|
if (is.function(hook.cancel)) {
|
|
855
691
|
try {
|
|
856
692
|
hook.cancel();
|
|
@@ -861,11 +697,10 @@ const runNormalEffects = (fiber) => {
|
|
|
861
697
|
}
|
|
862
698
|
}
|
|
863
699
|
}
|
|
864
|
-
// Run new effect
|
|
865
700
|
try {
|
|
866
701
|
const cleanup = hook.effect();
|
|
867
702
|
hook.cancel = is.function(cleanup)
|
|
868
|
-
?
|
|
703
|
+
? cleanup
|
|
869
704
|
: null;
|
|
870
705
|
}
|
|
871
706
|
catch (error) {
|
|
@@ -878,25 +713,17 @@ const runNormalEffects = (fiber) => {
|
|
|
878
713
|
}
|
|
879
714
|
}
|
|
880
715
|
};
|
|
881
|
-
/**
|
|
882
|
-
* The `commitRoot` function commits the changes made to the virtual DOM by updating the actual DOM.
|
|
883
|
-
*/
|
|
884
716
|
function commitRoot() {
|
|
885
717
|
const state = getState();
|
|
886
718
|
state.deletions.forEach(commitWork);
|
|
887
|
-
const finishedWork =
|
|
888
|
-
// Swap the currentRoot pointer BEFORE running effects
|
|
889
|
-
// This allows dispatches inside effects to base their new work on the just-finished tree
|
|
719
|
+
const finishedWork = state.wipRoot;
|
|
890
720
|
state.currentRoot = finishedWork;
|
|
891
|
-
// After hydration is done, reset the flag and cleanup unconsumed nodes
|
|
892
721
|
if (state.isHydrating || state.hydrationFailed) {
|
|
893
722
|
if (process.env.NODE_ENV !== 'production' && process.env.RYUNIX_DEBUG) {
|
|
894
723
|
console.log(`[Ryunix Debug] commitRoot - isHydrating: ${state.isHydrating}, hydrationFailed: ${state.hydrationFailed}`);
|
|
895
724
|
}
|
|
896
725
|
if (state.hydrationFailed) ;
|
|
897
726
|
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
727
|
let cursor = state.hydrateCursor;
|
|
901
728
|
let removed = 0;
|
|
902
729
|
if (cursor &&
|
|
@@ -919,23 +746,17 @@ function commitRoot() {
|
|
|
919
746
|
state.hydrateCursor = null;
|
|
920
747
|
}
|
|
921
748
|
commitWork(finishedWork.child);
|
|
922
|
-
// If wipRoot was not reassigned by a synchronous dispatch during effects, clear it
|
|
923
749
|
if (state.wipRoot === finishedWork) {
|
|
924
750
|
state.wipRoot = null;
|
|
925
751
|
}
|
|
926
752
|
}
|
|
927
|
-
/**
|
|
928
|
-
* @param {RyunixFiber | null | undefined} fiber
|
|
929
|
-
*/
|
|
930
753
|
function commitWork(fiber) {
|
|
931
754
|
if (!fiber) {
|
|
932
755
|
return;
|
|
933
756
|
}
|
|
934
|
-
// Handle portal fibers — they render into a different container
|
|
935
757
|
if (fiber.type === RYUNIX_PORTAL || fiber._isPortal) {
|
|
936
758
|
const portalContainer = fiber.containerInfo;
|
|
937
759
|
if (portalContainer) {
|
|
938
|
-
// Process portal children into the portal container
|
|
939
760
|
const portalFiber = fiber.child;
|
|
940
761
|
if (portalFiber) {
|
|
941
762
|
commitPortalWork(portalFiber, portalContainer);
|
|
@@ -959,9 +780,7 @@ function commitWork(fiber) {
|
|
|
959
780
|
}
|
|
960
781
|
domParent.appendChild(fiber.dom);
|
|
961
782
|
}
|
|
962
|
-
// Layout effects run synchronously during commit
|
|
963
783
|
runLayoutEffects(fiber);
|
|
964
|
-
// Normal effects run after paint
|
|
965
784
|
runNormalEffects(fiber);
|
|
966
785
|
}
|
|
967
786
|
else if (fiber.effectTag === EFFECT_TAGS.UPDATE) {
|
|
@@ -978,8 +797,6 @@ function commitWork(fiber) {
|
|
|
978
797
|
if (process.env.NODE_ENV !== 'production' && process.env.RYUNIX_DEBUG) {
|
|
979
798
|
console.log('[Ryunix Debug] Hydration fallback PLACEMENT:', fiber.type);
|
|
980
799
|
}
|
|
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
800
|
if (fiber.dom != null) {
|
|
984
801
|
domParent.appendChild(fiber.dom);
|
|
985
802
|
}
|
|
@@ -998,7 +815,6 @@ function commitWork(fiber) {
|
|
|
998
815
|
}
|
|
999
816
|
}
|
|
1000
817
|
else if (fiber.effectTag === EFFECT_TAGS.DELETION) {
|
|
1001
|
-
// Run cleanups BEFORE removing DOM to allow cleanup functions to read DOM state
|
|
1002
818
|
cancelEffectsDeep(fiber);
|
|
1003
819
|
commitDeletion(fiber);
|
|
1004
820
|
return;
|
|
@@ -1006,11 +822,6 @@ function commitWork(fiber) {
|
|
|
1006
822
|
commitWork(fiber.child);
|
|
1007
823
|
commitWork(fiber.sibling);
|
|
1008
824
|
}
|
|
1009
|
-
/**
|
|
1010
|
-
* Commit work for portal children into a specific container
|
|
1011
|
-
* @param {RyunixFiber | null | undefined} fiber
|
|
1012
|
-
* @param {Element | DocumentFragment} portalContainer
|
|
1013
|
-
*/
|
|
1014
825
|
const commitPortalWork = (fiber, portalContainer) => {
|
|
1015
826
|
if (!fiber)
|
|
1016
827
|
return;
|
|
@@ -1037,10 +848,6 @@ const commitPortalWork = (fiber, portalContainer) => {
|
|
|
1037
848
|
commitPortalWork(fiber.child, portalContainer);
|
|
1038
849
|
commitPortalWork(fiber.sibling, portalContainer);
|
|
1039
850
|
};
|
|
1040
|
-
/**
|
|
1041
|
-
* @param {RyunixFiber} fiber
|
|
1042
|
-
* @param {Node} domParent
|
|
1043
|
-
*/
|
|
1044
851
|
const commitDeletion = (fiber, domParent) => {
|
|
1045
852
|
if (fiber.dom) {
|
|
1046
853
|
if (fiber.dom.parentNode) {
|
|
@@ -1130,852 +937,635 @@ const reconcileChildren = (wipFiber, elements) => {
|
|
|
1130
937
|
});
|
|
1131
938
|
};
|
|
1132
939
|
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
940
|
+
const getHydrationPolicy = () => {
|
|
941
|
+
const env = (globalThis.process && globalThis.process.env) || {};
|
|
942
|
+
const recoverRaw = env.RYUNIX_HYDRATION_RECOVER || 'boundary';
|
|
943
|
+
const boundariesRaw = env.RYUNIX_HYDRATION_BOUNDARIES || 'route';
|
|
944
|
+
const strict = env.RYUNIX_HYDRATION_STRICT === 'true';
|
|
945
|
+
const recover = recoverRaw === 'none' || recoverRaw === 'root' ? recoverRaw : 'boundary';
|
|
946
|
+
const boundaries = boundariesRaw === 'server-only' || boundariesRaw === 'all-layouts'
|
|
947
|
+
? boundariesRaw
|
|
948
|
+
: 'route';
|
|
949
|
+
return {
|
|
950
|
+
recover,
|
|
951
|
+
boundaries,
|
|
952
|
+
strict,
|
|
953
|
+
};
|
|
1143
954
|
};
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
955
|
+
const findNearestHydrationBoundary = (fiber) => {
|
|
956
|
+
let current = fiber || null;
|
|
957
|
+
while (current) {
|
|
958
|
+
const props = current.props;
|
|
959
|
+
if (props &&
|
|
960
|
+
Object.prototype.hasOwnProperty.call(props, 'data-ryunix-hydrate-boundary')) {
|
|
961
|
+
return current;
|
|
962
|
+
}
|
|
963
|
+
const type = current.type;
|
|
964
|
+
const maybeTyped = type;
|
|
965
|
+
if (type &&
|
|
966
|
+
typeof type === 'function' &&
|
|
967
|
+
(maybeTyped?.ryunix_type === 'RYUNIX_HYDRATION_BOUNDARY' ||
|
|
968
|
+
maybeTyped?.ryunix_type === 'RYUNIX_SERVER_BOUNDARY')) {
|
|
969
|
+
return current;
|
|
970
|
+
}
|
|
971
|
+
current = current.parent || null;
|
|
1154
972
|
}
|
|
973
|
+
return null;
|
|
1155
974
|
};
|
|
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();
|
|
975
|
+
const findBoundaryDomFromNode = (node) => {
|
|
976
|
+
let current = node ?? null;
|
|
977
|
+
while (current) {
|
|
978
|
+
if (current.nodeType === 1 &&
|
|
979
|
+
current.hasAttribute('data-ryunix-hydrate-boundary')) {
|
|
980
|
+
return current;
|
|
1169
981
|
}
|
|
982
|
+
current = current.parentNode;
|
|
1170
983
|
}
|
|
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.');
|
|
984
|
+
return null;
|
|
985
|
+
};
|
|
986
|
+
const getBoundaryDom = (fiber) => {
|
|
987
|
+
if (!fiber)
|
|
988
|
+
return null;
|
|
989
|
+
if (fiber.dom && fiber.dom.nodeType === 1) {
|
|
990
|
+
const el = fiber.dom;
|
|
991
|
+
if (el.hasAttribute('data-ryunix-hydrate-boundary'))
|
|
992
|
+
return el;
|
|
1202
993
|
}
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
994
|
+
let child = fiber.child || null;
|
|
995
|
+
while (child) {
|
|
996
|
+
if (child.dom && child.dom.nodeType === 1) {
|
|
997
|
+
const el = child.dom;
|
|
998
|
+
if (el.hasAttribute('data-ryunix-hydrate-boundary'))
|
|
999
|
+
return el;
|
|
1000
|
+
}
|
|
1001
|
+
child = child.child || child.sibling || null;
|
|
1206
1002
|
}
|
|
1003
|
+
return null;
|
|
1207
1004
|
};
|
|
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]));
|
|
1005
|
+
const skipHydrationSubtree = (cursor, boundaryRoot) => {
|
|
1006
|
+
if (!cursor || !boundaryRoot)
|
|
1007
|
+
return cursor;
|
|
1008
|
+
if (cursor === boundaryRoot)
|
|
1009
|
+
return boundaryRoot.nextSibling;
|
|
1010
|
+
if (boundaryRoot.contains(cursor))
|
|
1011
|
+
return boundaryRoot.nextSibling;
|
|
1012
|
+
return cursor;
|
|
1233
1013
|
};
|
|
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
|
-
}
|
|
1014
|
+
const enqueueScopedRecovery = (boundaryFiber, boundaryDom, resumeCursor) => {
|
|
1015
|
+
if (!boundaryFiber || !boundaryDom)
|
|
1016
|
+
return;
|
|
1249
1017
|
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);
|
|
1018
|
+
const queue = state.scopedRecoveryQueue || [];
|
|
1019
|
+
queue.push({
|
|
1020
|
+
boundaryFiber,
|
|
1021
|
+
boundaryDom,
|
|
1022
|
+
resumeCursor,
|
|
1023
|
+
element: (Array.isArray(boundaryFiber.props?.children)
|
|
1024
|
+
? boundaryFiber.props.children[0]
|
|
1025
|
+
: boundaryFiber.props?.children),
|
|
1026
|
+
});
|
|
1027
|
+
state.scopedRecoveryQueue = queue;
|
|
1264
1028
|
};
|
|
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
|
-
}
|
|
1029
|
+
|
|
1030
|
+
const updateFunctionComponent = (fiber) => {
|
|
1277
1031
|
const state = getState();
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
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
|
-
});
|
|
1032
|
+
state.wipFiber = fiber;
|
|
1033
|
+
state.hookIndex = 0;
|
|
1034
|
+
state.wipFiber.hooks = [];
|
|
1035
|
+
if (state.isHydrating) {
|
|
1036
|
+
fiber.effectTag = EFFECT_TAGS.HYDRATE;
|
|
1303
1037
|
}
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1038
|
+
const componentType = fiber.type;
|
|
1039
|
+
if (componentType._isMemo && fiber.alternate) {
|
|
1040
|
+
const { children: _pc, ...prevRest } = fiber.alternate.props || {};
|
|
1041
|
+
const { children: _nc, ...nextRest } = fiber.props || {};
|
|
1042
|
+
if (componentType._arePropsEqual?.(prevRest, nextRest)) {
|
|
1043
|
+
fiber.hooks = fiber.alternate.hooks;
|
|
1044
|
+
const oldChild = fiber.alternate.child;
|
|
1045
|
+
if (oldChild) {
|
|
1046
|
+
oldChild.parent = fiber;
|
|
1047
|
+
fiber.child = oldChild;
|
|
1309
1048
|
}
|
|
1310
1049
|
return;
|
|
1311
1050
|
}
|
|
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
|
-
}
|
|
1353
|
-
validateHookContext();
|
|
1354
|
-
if (!is.function(callback)) {
|
|
1355
|
-
throw new Error('useEffect callback must be a function');
|
|
1356
1051
|
}
|
|
1357
|
-
|
|
1358
|
-
|
|
1052
|
+
let children = [
|
|
1053
|
+
componentType(fiber.props),
|
|
1054
|
+
];
|
|
1055
|
+
if (componentType._contextId && fiber.props?.value !== undefined) {
|
|
1056
|
+
fiber._contextId = componentType._contextId;
|
|
1057
|
+
fiber._contextValue = fiber.props.value;
|
|
1359
1058
|
}
|
|
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++;
|
|
1059
|
+
reconcileChildren(fiber, children);
|
|
1373
1060
|
};
|
|
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 };
|
|
1061
|
+
const isUnderClientOnlyBoundary = (fiber) => {
|
|
1062
|
+
let current = fiber?.parent || null;
|
|
1063
|
+
while (current) {
|
|
1064
|
+
if (current._hydrateClientOnly)
|
|
1065
|
+
return true;
|
|
1066
|
+
current = current.parent || null;
|
|
1386
1067
|
}
|
|
1068
|
+
return false;
|
|
1069
|
+
};
|
|
1070
|
+
const updateHostComponent = (fiber) => {
|
|
1387
1071
|
const state = getState();
|
|
1388
|
-
if (
|
|
1389
|
-
|
|
1072
|
+
if (fiber.type === RYUNIX_TYPES.RYUNIX_CONTEXT) {
|
|
1073
|
+
fiber._contextId =
|
|
1074
|
+
fiber.props?._contextId;
|
|
1075
|
+
fiber._contextValue = fiber.props?.value;
|
|
1390
1076
|
}
|
|
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');
|
|
1435
|
-
}
|
|
1436
|
-
const { hookIndex } = state;
|
|
1437
|
-
const wipFiber = /** @type {RyunixFiber} */ state.wipFiber;
|
|
1438
|
-
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
1439
|
-
let value;
|
|
1440
|
-
if (oldHook && !haveDepsChanged(oldHook.deps, deps)) {
|
|
1441
|
-
value = /** @type {{ value?: unknown }} */ oldHook.value;
|
|
1077
|
+
const isPassthrough = fiber.type === RYUNIX_TYPES.RYUNIX_FRAGMENT ||
|
|
1078
|
+
fiber.type === RYUNIX_TYPES.RYUNIX_CONTEXT ||
|
|
1079
|
+
fiber.type === Symbol.for('ryunix.portal');
|
|
1080
|
+
if (state.isHydrating && isPassthrough) {
|
|
1081
|
+
fiber.effectTag = EFFECT_TAGS.HYDRATE;
|
|
1442
1082
|
}
|
|
1443
|
-
else {
|
|
1444
|
-
|
|
1445
|
-
|
|
1083
|
+
else if (state.isHydrating && isUnderClientOnlyBoundary(fiber)) {
|
|
1084
|
+
if (!fiber.dom) {
|
|
1085
|
+
fiber.dom = createDom(fiber);
|
|
1086
|
+
fiber.effectTag = EFFECT_TAGS.PLACEMENT;
|
|
1446
1087
|
}
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1088
|
+
}
|
|
1089
|
+
else if (!fiber.dom) {
|
|
1090
|
+
if (state.isHydrating && state.hydrateCursor) {
|
|
1091
|
+
const domNode = state.hydrateCursor;
|
|
1092
|
+
const isText = fiber.type === RYUNIX_TYPES.TEXT_ELEMENT && domNode.nodeType === 3;
|
|
1093
|
+
const isElement = typeof fiber.type === 'string' &&
|
|
1094
|
+
domNode.nodeType === 1 &&
|
|
1095
|
+
domNode.tagName.toLowerCase() === fiber.type.toLowerCase();
|
|
1096
|
+
if (isText || isElement) {
|
|
1097
|
+
fiber.dom = domNode;
|
|
1098
|
+
fiber.effectTag = EFFECT_TAGS.HYDRATE;
|
|
1099
|
+
if (isText &&
|
|
1100
|
+
fiber.props?.nodeValue != null &&
|
|
1101
|
+
domNode.nodeValue !== String(fiber.props.nodeValue)) {
|
|
1102
|
+
domNode.nodeValue = String(fiber.props.nodeValue);
|
|
1103
|
+
logHydrationRecoverable('text');
|
|
1104
|
+
}
|
|
1105
|
+
if (isElement &&
|
|
1106
|
+
domNode.hasAttribute('data-ryunix-hydrate-boundary')) {
|
|
1107
|
+
fiber._hydrateClientOnly = true;
|
|
1108
|
+
}
|
|
1109
|
+
state.hydrateCursor = nextValidSibling$1(domNode.firstChild);
|
|
1450
1110
|
}
|
|
1451
|
-
|
|
1111
|
+
else {
|
|
1112
|
+
const policy = getHydrationPolicy();
|
|
1113
|
+
const detail = `Mismatch at ${getTypeLabel(fiber.type)}. Expected ${domNode.nodeType === 1 ? domNode.tagName : 'text'} but got ${String(fiber.type)}.`;
|
|
1114
|
+
const boundaryFiber = findNearestHydrationBoundary(fiber);
|
|
1115
|
+
const boundaryDom = (boundaryFiber ? getBoundaryDom(boundaryFiber) : null) ??
|
|
1116
|
+
findBoundaryDomFromNode(state.hydrateCursor);
|
|
1117
|
+
if (policy.recover === 'boundary' && boundaryFiber && boundaryDom) {
|
|
1118
|
+
logHydrationBoundaryMismatch(detail);
|
|
1119
|
+
enqueueScopedRecovery(boundaryFiber, boundaryDom, state.hydrateCursor ?? null);
|
|
1120
|
+
state.hydrateCursor = skipHydrationSubtree(state.hydrateCursor ?? null, boundaryDom);
|
|
1121
|
+
fiber.dom = createDom(fiber);
|
|
1122
|
+
fiber.effectTag = EFFECT_TAGS.PLACEMENT;
|
|
1123
|
+
}
|
|
1124
|
+
else if (policy.recover === 'none') {
|
|
1125
|
+
logHydrationFatal(detail);
|
|
1126
|
+
state.isHydrating = false;
|
|
1127
|
+
state.hydrateCursor = null;
|
|
1128
|
+
fiber.dom = createDom(fiber);
|
|
1129
|
+
fiber.effectTag = EFFECT_TAGS.PLACEMENT;
|
|
1130
|
+
}
|
|
1131
|
+
else {
|
|
1132
|
+
logHydrationMismatch(detail);
|
|
1133
|
+
state.isHydrating = false;
|
|
1134
|
+
state.hydrationFailed = true;
|
|
1135
|
+
state.hydrateCursor = null;
|
|
1136
|
+
fiber.dom = createDom(fiber);
|
|
1137
|
+
fiber.effectTag = EFFECT_TAGS.PLACEMENT;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
else {
|
|
1142
|
+
fiber.dom = createDom(fiber);
|
|
1452
1143
|
}
|
|
1453
1144
|
}
|
|
1454
|
-
const
|
|
1455
|
-
|
|
1456
|
-
type: RYUNIX_TYPES.RYUNIX_MEMO,
|
|
1457
|
-
value,
|
|
1458
|
-
deps,
|
|
1459
|
-
};
|
|
1460
|
-
wipFiber.hooks[hookIndex] = hook;
|
|
1461
|
-
state.hookIndex++;
|
|
1462
|
-
return value;
|
|
1145
|
+
const children = fiber.props?.children || [];
|
|
1146
|
+
reconcileChildren(fiber, children);
|
|
1463
1147
|
};
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
const
|
|
1477
|
-
if (
|
|
1478
|
-
|
|
1148
|
+
const getTypeLabel = (type) => {
|
|
1149
|
+
if (typeof type === 'symbol')
|
|
1150
|
+
return type.description || type.toString();
|
|
1151
|
+
if (typeof type === 'function')
|
|
1152
|
+
return type.name || 'anonymous';
|
|
1153
|
+
return String(type);
|
|
1154
|
+
};
|
|
1155
|
+
|
|
1156
|
+
let scheduleWorkFn = null;
|
|
1157
|
+
const setScheduleWork = (fn) => {
|
|
1158
|
+
scheduleWorkFn = fn;
|
|
1159
|
+
};
|
|
1160
|
+
const scheduleWork$1 = (root, priority) => {
|
|
1161
|
+
if (scheduleWorkFn) {
|
|
1162
|
+
return scheduleWorkFn(root, priority);
|
|
1479
1163
|
}
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
* @param {unknown} [defaultValue]
|
|
1493
|
-
* @returns {{ Provider: RyunixComponent & { _contextId?: string | symbol }, useContext: (ctxID?: string | symbol) => unknown }}
|
|
1494
|
-
*/
|
|
1495
|
-
const createContext = (contextId = RYUNIX_TYPES.RYUNIX_CONTEXT, defaultValue = {}) => {
|
|
1496
|
-
/** @param {{ value?: unknown, children?: import('../types/internal.js').RyunixNode }} props */
|
|
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,
|
|
1164
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1165
|
+
console.warn('[Ryunix] scheduleWork called before being initialized.');
|
|
1166
|
+
}
|
|
1167
|
+
};
|
|
1168
|
+
|
|
1169
|
+
const renderSubtree = (element, container) => {
|
|
1170
|
+
clearContainer(container);
|
|
1171
|
+
const root = {
|
|
1172
|
+
dom: container,
|
|
1173
|
+
props: { children: [element] },
|
|
1174
|
+
isHydrating: false,
|
|
1175
|
+
hydrateCursor: null,
|
|
1530
1176
|
};
|
|
1177
|
+
scheduleWork$1(root, undefined);
|
|
1531
1178
|
};
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
const query = {};
|
|
1542
|
-
for (const [key, value] of searchParams.entries()) {
|
|
1543
|
-
query[key] = value;
|
|
1179
|
+
const recoverScopedHydrationFailures = () => {
|
|
1180
|
+
const state = getState();
|
|
1181
|
+
const queue = state.scopedRecoveryQueue;
|
|
1182
|
+
if (!queue?.length)
|
|
1183
|
+
return;
|
|
1184
|
+
state.scopedRecoveryQueue = [];
|
|
1185
|
+
for (const item of queue) {
|
|
1186
|
+
logHydrationBoundaryRecovery();
|
|
1187
|
+
renderSubtree(item.element, item.boundaryDom);
|
|
1544
1188
|
}
|
|
1545
|
-
return query;
|
|
1546
1189
|
};
|
|
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 = {}) => {
|
|
1190
|
+
const recoverHydrationFailureIfNeeded = () => {
|
|
1582
1191
|
const state = getState();
|
|
1583
|
-
if (state.
|
|
1584
|
-
state.ssrMetadata = { ...state.ssrMetadata, ...tags };
|
|
1192
|
+
if (!state.hydrationFailed || state.hydrationRecover)
|
|
1585
1193
|
return;
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1194
|
+
const policy = getHydrationPolicy();
|
|
1195
|
+
if (policy.recover === 'none')
|
|
1196
|
+
return;
|
|
1197
|
+
const container = state.containerRoot || state.currentRoot?.dom;
|
|
1198
|
+
const element = state.currentRoot?.props?.children?.[0];
|
|
1199
|
+
if (!container || element == null)
|
|
1200
|
+
return;
|
|
1201
|
+
state.hydrationRecover = true;
|
|
1202
|
+
state.hydrationFailed = false;
|
|
1203
|
+
logHydrationFailure('');
|
|
1204
|
+
logHydrationRecovery();
|
|
1205
|
+
renderSubtree(element, container);
|
|
1206
|
+
};
|
|
1207
|
+
const runHydrationRecovery = () => {
|
|
1208
|
+
recoverScopedHydrationFailures();
|
|
1209
|
+
recoverHydrationFailureIfNeeded();
|
|
1210
|
+
};
|
|
1211
|
+
|
|
1212
|
+
let workQueue = [];
|
|
1213
|
+
let isWorkLoopScheduled = false;
|
|
1214
|
+
function performUnitOfWork(fiber) {
|
|
1215
|
+
const state = getState();
|
|
1216
|
+
const isFunctionComponent = fiber.type instanceof Function || typeof fiber.type === 'function';
|
|
1217
|
+
try {
|
|
1218
|
+
if (isFunctionComponent) {
|
|
1219
|
+
updateFunctionComponent(fiber);
|
|
1599
1220
|
}
|
|
1600
1221
|
else {
|
|
1601
|
-
|
|
1222
|
+
updateHostComponent(fiber);
|
|
1602
1223
|
}
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1224
|
+
}
|
|
1225
|
+
catch (error) {
|
|
1226
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1227
|
+
console.error('[Ryunix ErrorBoundary] Caught error during render:', error);
|
|
1228
|
+
try {
|
|
1229
|
+
const src = fiber.props && fiber.props.__source;
|
|
1230
|
+
if (src && error && typeof error === 'object') {
|
|
1231
|
+
error.__ryunix_source = src;
|
|
1232
|
+
}
|
|
1233
|
+
let targetFiber = fiber;
|
|
1234
|
+
while (!error.__ryunix_source && targetFiber) {
|
|
1235
|
+
if (targetFiber.props && targetFiber.props.__source) {
|
|
1236
|
+
error.__ryunix_source = targetFiber.props.__source;
|
|
1237
|
+
}
|
|
1238
|
+
targetFiber = targetFiber.parent;
|
|
1239
|
+
}
|
|
1610
1240
|
}
|
|
1611
|
-
|
|
1241
|
+
catch (e) { }
|
|
1612
1242
|
}
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
}
|
|
1624
|
-
meta.setAttribute('content', value);
|
|
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;
|
|
1243
|
+
let boundaryFiber = fiber.parent;
|
|
1244
|
+
let foundBoundary = false;
|
|
1245
|
+
while (boundaryFiber) {
|
|
1246
|
+
if (boundaryFiber.type &&
|
|
1247
|
+
boundaryFiber.type
|
|
1248
|
+
.ryunix_type === 'RYUNIX_ERROR_BOUNDARY') {
|
|
1249
|
+
foundBoundary = true;
|
|
1250
|
+
break;
|
|
1251
|
+
}
|
|
1252
|
+
boundaryFiber = boundaryFiber.parent;
|
|
1655
1253
|
}
|
|
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 };
|
|
1254
|
+
if (foundBoundary) {
|
|
1255
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1256
|
+
console.warn('[Ryunix ErrorBoundary] Recovering tree at nearest boundary.');
|
|
1257
|
+
}
|
|
1258
|
+
boundaryFiber.stateError = error;
|
|
1259
|
+
fiber.child = null;
|
|
1260
|
+
return boundaryFiber;
|
|
1261
|
+
}
|
|
1262
|
+
else {
|
|
1263
|
+
console.error('[Ryunix] Fatal Uncaught Error. No ErrorBoundary was found in the tree to handle this exception:\n', error);
|
|
1264
|
+
state.nextUnitOfWork = null;
|
|
1265
|
+
return null;
|
|
1678
1266
|
}
|
|
1679
1267
|
}
|
|
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];
|
|
1268
|
+
if (fiber.child) {
|
|
1269
|
+
return fiber.child;
|
|
1689
1270
|
}
|
|
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 }));
|
|
1271
|
+
let nextFiber = fiber;
|
|
1272
|
+
while (nextFiber) {
|
|
1273
|
+
if (state.isHydrating && nextFiber.dom) {
|
|
1274
|
+
state.hydrateCursor = nextValidSibling$1(nextFiber.dom.nextSibling);
|
|
1275
|
+
}
|
|
1276
|
+
if (nextFiber.sibling) {
|
|
1277
|
+
return nextFiber.sibling;
|
|
1278
|
+
}
|
|
1279
|
+
nextFiber = nextFiber.parent;
|
|
1713
1280
|
}
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
window.location.assign(path);
|
|
1729
|
-
return;
|
|
1281
|
+
}
|
|
1282
|
+
const workLoop = (deadline) => {
|
|
1283
|
+
const state = getState();
|
|
1284
|
+
let shouldYield = false;
|
|
1285
|
+
while ((state.nextUnitOfWork || workQueue.length > 0) && !shouldYield) {
|
|
1286
|
+
if (!state.nextUnitOfWork && workQueue.length > 0) {
|
|
1287
|
+
const nextRoot = workQueue.shift();
|
|
1288
|
+
state.wipRoot = nextRoot;
|
|
1289
|
+
state.nextUnitOfWork = nextRoot;
|
|
1290
|
+
state.deletions = [];
|
|
1291
|
+
if (nextRoot.isHydrating !== undefined) {
|
|
1292
|
+
state.isHydrating = nextRoot.isHydrating;
|
|
1293
|
+
state.hydrateCursor = nextRoot.hydrateCursor;
|
|
1294
|
+
}
|
|
1730
1295
|
}
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
};
|
|
1734
|
-
const currentRouteData = findRoute(routes, location);
|
|
1735
|
-
const query = useQuery();
|
|
1736
|
-
/** @type {RyunixRouterContextValue} */
|
|
1737
|
-
const contextValue = {
|
|
1738
|
-
location,
|
|
1739
|
-
params: currentRouteData.params || {},
|
|
1740
|
-
query,
|
|
1741
|
-
navigate,
|
|
1742
|
-
route: currentRouteData.route,
|
|
1743
|
-
};
|
|
1744
|
-
return createElement(
|
|
1745
|
-
/** @type {string | symbol | Function} */ RouterContext.Provider, { value: contextValue }, Fragment({ children }));
|
|
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');
|
|
1753
|
-
};
|
|
1754
|
-
/**
|
|
1755
|
-
* The `Children` function in JavaScript uses router hooks to handle scrolling to a specific element
|
|
1756
|
-
* based on the hash in the URL.
|
|
1757
|
-
* @returns {import('./createElement.js').RyunixElement | null}
|
|
1758
|
-
*/
|
|
1759
|
-
const Children = () => {
|
|
1760
|
-
const { route, params, query, location } = useRouter();
|
|
1761
|
-
if (!route || !route.component)
|
|
1762
|
-
return null;
|
|
1763
|
-
const hash = useHash();
|
|
1764
|
-
useEffect(() => {
|
|
1765
|
-
if (hash) {
|
|
1766
|
-
const id = /** @type {string} */ hash.slice(1);
|
|
1767
|
-
const el = document.getElementById(id);
|
|
1768
|
-
if (el)
|
|
1769
|
-
el.scrollIntoView({ block: 'start', behavior: 'smooth' });
|
|
1296
|
+
if (state.nextUnitOfWork) {
|
|
1297
|
+
state.nextUnitOfWork = performUnitOfWork(state.nextUnitOfWork);
|
|
1770
1298
|
}
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
}
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
* @returns {string}
|
|
1784
|
-
*/
|
|
1785
|
-
const usePathname = () => {
|
|
1786
|
-
const { location } = useRouter();
|
|
1787
|
-
return location.split('?')[0].split('#')[0];
|
|
1788
|
-
};
|
|
1789
|
-
/**
|
|
1790
|
-
* useSearchParams - Returns the current URLSearchParams object
|
|
1791
|
-
* @returns {URLSearchParams}
|
|
1792
|
-
*/
|
|
1793
|
-
const useSearchParams = () => {
|
|
1794
|
-
const { query } = useRouter();
|
|
1795
|
-
return new URLSearchParams(query);
|
|
1299
|
+
shouldYield = deadline.timeRemaining() < 1;
|
|
1300
|
+
}
|
|
1301
|
+
if (!state.nextUnitOfWork && state.wipRoot) {
|
|
1302
|
+
commitRoot();
|
|
1303
|
+
runHydrationRecovery();
|
|
1304
|
+
}
|
|
1305
|
+
if (state.nextUnitOfWork || workQueue.length > 0) {
|
|
1306
|
+
rIC(workLoop);
|
|
1307
|
+
}
|
|
1308
|
+
else {
|
|
1309
|
+
isWorkLoopScheduled = false;
|
|
1310
|
+
}
|
|
1796
1311
|
};
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1312
|
+
const scheduleWork = (root, priority = getCurrentPriority()) => {
|
|
1313
|
+
const state = getState();
|
|
1314
|
+
if (state.wipRoot) {
|
|
1315
|
+
workQueue.push(root);
|
|
1316
|
+
}
|
|
1317
|
+
else {
|
|
1318
|
+
state.nextUnitOfWork = root;
|
|
1319
|
+
state.wipRoot = root;
|
|
1320
|
+
state.deletions = [];
|
|
1321
|
+
if (root.isHydrating !== undefined) {
|
|
1322
|
+
state.isHydrating = root.isHydrating;
|
|
1323
|
+
state.hydrateCursor = root.hydrateCursor;
|
|
1809
1324
|
}
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
onMouseEnter: handleMouseEnter,
|
|
1820
|
-
className: props.className || props['ryunix-class'],
|
|
1821
|
-
...cleanedProps,
|
|
1822
|
-
}, props.children);
|
|
1823
|
-
};
|
|
1824
|
-
/**
|
|
1825
|
-
* The NavLink function in JavaScript is a component that generates a link element with customizable
|
|
1826
|
-
* classes and active state based on the current location.
|
|
1827
|
-
* @param {{ to: string, exact?: boolean, children?: import('../types/internal.js').RyunixNode } & Record<string, unknown>} props
|
|
1828
|
-
* @returns {import('./createElement.js').RyunixElement}
|
|
1829
|
-
*/
|
|
1830
|
-
const NavLink = ({ to, exact = false, ...props }) => {
|
|
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;
|
|
1325
|
+
}
|
|
1326
|
+
state.hookIndex = 0;
|
|
1327
|
+
state.effects = [];
|
|
1328
|
+
if (!isWorkLoopScheduled) {
|
|
1329
|
+
isWorkLoopScheduled = true;
|
|
1330
|
+
if (priority <= Priority.USER_BLOCKING) {
|
|
1331
|
+
Promise.resolve().then(() => {
|
|
1332
|
+
workLoop({ timeRemaining: () => 10, didTimeout: true });
|
|
1333
|
+
});
|
|
1839
1334
|
}
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
const classAttrValue = resolveClass(
|
|
1845
|
-
/** @type {string | ((args: { isActive: boolean }) => string) | undefined} */ props['ryunix-class'] || props.className);
|
|
1846
|
-
const { ['ryunix-class']: _omitRyunix, className: _omitClassName, ...cleanedProps } = props;
|
|
1847
|
-
return createElement('a', {
|
|
1848
|
-
href: to,
|
|
1849
|
-
onClick: handleClick,
|
|
1850
|
-
[classAttrName]: classAttrValue,
|
|
1851
|
-
...cleanedProps,
|
|
1852
|
-
}, props.children);
|
|
1335
|
+
else {
|
|
1336
|
+
rIC(workLoop);
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1853
1339
|
};
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
const
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
priority,
|
|
1870
|
-
};
|
|
1871
|
-
scheduleUpdate(() => baseDispatch(wrappedAction, priority), priority);
|
|
1340
|
+
setScheduleWork(scheduleWork);
|
|
1341
|
+
|
|
1342
|
+
const render = (element, container) => {
|
|
1343
|
+
const state = getState();
|
|
1344
|
+
clearContainer(container);
|
|
1345
|
+
const root = {
|
|
1346
|
+
dom: container,
|
|
1347
|
+
props: {
|
|
1348
|
+
children: [
|
|
1349
|
+
element,
|
|
1350
|
+
],
|
|
1351
|
+
},
|
|
1352
|
+
alternate: state.currentRoot,
|
|
1353
|
+
isHydrating: false,
|
|
1354
|
+
hydrateCursor: null,
|
|
1872
1355
|
};
|
|
1873
|
-
|
|
1356
|
+
scheduleWork(root);
|
|
1357
|
+
return root;
|
|
1874
1358
|
};
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
callback();
|
|
1887
|
-
setIsPending(false, Priority.LOW);
|
|
1888
|
-
});
|
|
1889
|
-
}, 0);
|
|
1890
|
-
};
|
|
1891
|
-
return [/** @type {boolean} */ isPending, startTransition];
|
|
1359
|
+
const SSR_ROOT_ATTR = 'data-ryunix-ssr-root';
|
|
1360
|
+
const nextValidSibling = (node) => {
|
|
1361
|
+
let next = node;
|
|
1362
|
+
while (next &&
|
|
1363
|
+
((next.nodeType === 3 && !next.nodeValue.trim()) ||
|
|
1364
|
+
next.nodeType === 8 ||
|
|
1365
|
+
(next.nodeType === 1 &&
|
|
1366
|
+
next.hasAttribute('data-ryunix-ssr')))) {
|
|
1367
|
+
next = next.nextSibling;
|
|
1368
|
+
}
|
|
1369
|
+
return next;
|
|
1892
1370
|
};
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1371
|
+
const hydrate = (element, container) => {
|
|
1372
|
+
const state = getState();
|
|
1373
|
+
state.containerRoot = container;
|
|
1374
|
+
const root = {
|
|
1375
|
+
dom: container,
|
|
1376
|
+
props: {
|
|
1377
|
+
children: [
|
|
1378
|
+
element,
|
|
1379
|
+
],
|
|
1380
|
+
},
|
|
1381
|
+
alternate: state.currentRoot,
|
|
1382
|
+
isHydrating: true,
|
|
1383
|
+
hydrateCursor: nextValidSibling(container.firstChild),
|
|
1384
|
+
};
|
|
1385
|
+
scheduleWork(root);
|
|
1386
|
+
return root;
|
|
1907
1387
|
};
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
* value that will be used if there is no data stored in the local storage under the specified `key`.
|
|
1916
|
-
* It serves as the default value for the state if no data is retrieved from the local storage.
|
|
1917
|
-
* @param {string} key
|
|
1918
|
-
* @param {unknown} [initialState]
|
|
1919
|
-
* @returns {[unknown, (value: unknown) => void]}
|
|
1920
|
-
*/
|
|
1921
|
-
const usePersistentStore = (key, initialState = '') => {
|
|
1922
|
-
const [state, dispatch] = useStore(() => {
|
|
1923
|
-
try {
|
|
1924
|
-
const item = window.localStorage.getItem(key);
|
|
1925
|
-
return item ? JSON.parse(item) : initialState;
|
|
1388
|
+
const init = (MainElement, root = '__ryunix', components = {}) => {
|
|
1389
|
+
const state = getState();
|
|
1390
|
+
const container = document.getElementById(root);
|
|
1391
|
+
state.containerRoot = container;
|
|
1392
|
+
if (!container) {
|
|
1393
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1394
|
+
console.warn(`[Ryunix] init: container #${root} not found.`);
|
|
1926
1395
|
}
|
|
1927
|
-
|
|
1928
|
-
|
|
1396
|
+
return undefined;
|
|
1397
|
+
}
|
|
1398
|
+
resetHydrationLogFlags();
|
|
1399
|
+
state.hydrationPolicy = getHydrationPolicy();
|
|
1400
|
+
state.scopedRecoveryQueue = [];
|
|
1401
|
+
state.hydrationRecover = false;
|
|
1402
|
+
state.isHydrating = false;
|
|
1403
|
+
state.hydrationFailed = false;
|
|
1404
|
+
if (state.currentRoot) {
|
|
1405
|
+
if (process.env.NODE_ENV !== 'production' && process.env.RYUNIX_DEBUG) {
|
|
1406
|
+
console.log(`[Ryunix Debug] init: existing root detected. Client render on #${root}`);
|
|
1929
1407
|
}
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
window.localStorage.setItem(key, JSON.stringify(value));
|
|
1408
|
+
return render(MainElement, container);
|
|
1409
|
+
}
|
|
1410
|
+
const ssrEnabled = process.env.RYUNIX_SSR !== 'false';
|
|
1411
|
+
const isSsrPayload = ssrEnabled &&
|
|
1412
|
+
container.hasAttribute(SSR_ROOT_ATTR) &&
|
|
1413
|
+
container.hasChildNodes();
|
|
1414
|
+
if (process.env.NODE_ENV !== 'production' && process.env.RYUNIX_DEBUG) {
|
|
1415
|
+
console.log(`[Ryunix Debug] init: isSsrPayload=${isSsrPayload}, hasChildNodes=${container.hasChildNodes()}`);
|
|
1416
|
+
}
|
|
1417
|
+
if (isSsrPayload) {
|
|
1418
|
+
if (process.env.NODE_ENV !== 'production' && process.env.RYUNIX_DEBUG) {
|
|
1419
|
+
console.log(`[Ryunix Debug] init: hydrating SSR markup on #${root}`);
|
|
1943
1420
|
}
|
|
1944
|
-
|
|
1945
|
-
|
|
1421
|
+
container.removeAttribute(SSR_ROOT_ATTR);
|
|
1422
|
+
return hydrate(MainElement, container);
|
|
1423
|
+
}
|
|
1424
|
+
if (process.env.NODE_ENV !== 'production' && process.env.RYUNIX_DEBUG) {
|
|
1425
|
+
console.log(`[Ryunix Debug] init: client render on #${root}`);
|
|
1426
|
+
}
|
|
1427
|
+
return render(MainElement, container);
|
|
1428
|
+
};
|
|
1429
|
+
const safeRender = (component, props, onError) => {
|
|
1430
|
+
try {
|
|
1431
|
+
return component(props);
|
|
1432
|
+
}
|
|
1433
|
+
catch (error) {
|
|
1434
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1435
|
+
console.error('Component error:', error);
|
|
1946
1436
|
}
|
|
1947
|
-
|
|
1948
|
-
|
|
1437
|
+
if (onError)
|
|
1438
|
+
onError(error);
|
|
1439
|
+
return null;
|
|
1440
|
+
}
|
|
1949
1441
|
};
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1442
|
+
|
|
1443
|
+
let isBatching = false;
|
|
1444
|
+
let pendingUpdates = [];
|
|
1445
|
+
function batchUpdates(callback) {
|
|
1446
|
+
const wasBatching = isBatching;
|
|
1447
|
+
isBatching = true;
|
|
1448
|
+
try {
|
|
1449
|
+
callback();
|
|
1450
|
+
}
|
|
1451
|
+
finally {
|
|
1452
|
+
isBatching = wasBatching;
|
|
1453
|
+
if (!isBatching && pendingUpdates.length > 0) {
|
|
1454
|
+
flushUpdates();
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
function queueUpdate(update) {
|
|
1459
|
+
pendingUpdates.push(update);
|
|
1460
|
+
if (!isBatching) {
|
|
1461
|
+
flushUpdates();
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
function flushUpdates() {
|
|
1465
|
+
if (pendingUpdates.length === 0)
|
|
1466
|
+
return;
|
|
1467
|
+
const updates = pendingUpdates;
|
|
1468
|
+
pendingUpdates = [];
|
|
1469
|
+
updates.forEach((update) => update());
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
process.env.NODE_ENV !== 'production';
|
|
1473
|
+
const validateHookContext = (hookName = 'A hook') => {
|
|
1474
|
+
const state = getState();
|
|
1475
|
+
if (!state.wipFiber) {
|
|
1476
|
+
throw new Error(`${hookName} can only be called inside function components. ` +
|
|
1477
|
+
'Make sure you are calling hooks at the top level of your component.');
|
|
1478
|
+
}
|
|
1479
|
+
const wipFiber = state.wipFiber;
|
|
1480
|
+
if (!Array.isArray(wipFiber.hooks)) {
|
|
1481
|
+
wipFiber.hooks = [];
|
|
1482
|
+
}
|
|
1483
|
+
};
|
|
1484
|
+
|
|
1485
|
+
const haveDepsChanged = (oldDeps, newDeps) => {
|
|
1486
|
+
if (!oldDeps || !newDeps)
|
|
1487
|
+
return true;
|
|
1488
|
+
if (oldDeps.length !== newDeps.length)
|
|
1489
|
+
return true;
|
|
1490
|
+
return oldDeps.some((dep, i) => !Object.is(dep, newDeps[i]));
|
|
1491
|
+
};
|
|
1492
|
+
const useStore = (initialState, priority = getCurrentPriority()) => {
|
|
1493
|
+
if (typeof window === 'undefined') {
|
|
1494
|
+
return [
|
|
1495
|
+
is.function(initialState)
|
|
1496
|
+
? initialState()
|
|
1497
|
+
: initialState,
|
|
1498
|
+
() => { },
|
|
1499
|
+
];
|
|
1500
|
+
}
|
|
1501
|
+
const state = getState();
|
|
1502
|
+
if (state.isServerRendering) {
|
|
1503
|
+
return [
|
|
1504
|
+
is.function(initialState)
|
|
1505
|
+
? initialState()
|
|
1506
|
+
: initialState,
|
|
1507
|
+
() => { },
|
|
1508
|
+
];
|
|
1509
|
+
}
|
|
1510
|
+
const reducer = (state, action) => is.function(action) ? action(state) : action;
|
|
1511
|
+
return useReducer(reducer, initialState, undefined, priority);
|
|
1512
|
+
};
|
|
1513
|
+
const useReducer = (reducer, initialState, init, defaultPriority = getCurrentPriority()) => {
|
|
1514
|
+
if (typeof window === 'undefined') {
|
|
1515
|
+
return [init ? init(initialState) : initialState, () => { }];
|
|
1516
|
+
}
|
|
1517
|
+
const state = getState();
|
|
1518
|
+
if (state.isServerRendering) {
|
|
1519
|
+
return [init ? init(initialState) : initialState, () => { }];
|
|
1520
|
+
}
|
|
1521
|
+
validateHookContext();
|
|
1522
|
+
const { hookIndex } = state;
|
|
1523
|
+
const wipFiber = state.wipFiber;
|
|
1524
|
+
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
1525
|
+
const hook = {
|
|
1526
|
+
hookID: hookIndex,
|
|
1527
|
+
type: RYUNIX_TYPES.RYUNIX_STORE,
|
|
1528
|
+
state: oldHook ? oldHook.state : init ? init(initialState) : initialState,
|
|
1529
|
+
queue: [],
|
|
1967
1530
|
};
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1531
|
+
if (oldHook?.queue) {
|
|
1532
|
+
oldHook.queue.forEach((action) => {
|
|
1533
|
+
try {
|
|
1534
|
+
hook.state = reducer(hook.state, action);
|
|
1535
|
+
}
|
|
1536
|
+
catch (error) {
|
|
1537
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1538
|
+
console.error('Error in reducer:', error);
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1543
|
+
const dispatch = (action, priority = defaultPriority) => {
|
|
1544
|
+
if (action === undefined) {
|
|
1545
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1546
|
+
console.warn('dispatch called with undefined action');
|
|
1547
|
+
}
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
hook.queue.push(action);
|
|
1551
|
+
const currentState = getState();
|
|
1552
|
+
const activeRoot = currentState.currentRoot ||
|
|
1553
|
+
currentState.wipRoot;
|
|
1554
|
+
if (!activeRoot)
|
|
1555
|
+
return;
|
|
1556
|
+
const newRoot = {
|
|
1557
|
+
dom: activeRoot.dom,
|
|
1558
|
+
props: activeRoot.props,
|
|
1559
|
+
alternate: currentState.currentRoot || null,
|
|
1560
|
+
};
|
|
1561
|
+
queueUpdate(() => scheduleWork$1(newRoot, priority));
|
|
1562
|
+
};
|
|
1563
|
+
wipFiber.hooks[hookIndex] =
|
|
1564
|
+
hook;
|
|
1565
|
+
state.hookIndex++;
|
|
1566
|
+
return [hook.state, dispatch];
|
|
1567
|
+
};
|
|
1568
|
+
const useEffect = (callback, deps) => {
|
|
1979
1569
|
if (typeof window === 'undefined') {
|
|
1980
1570
|
return;
|
|
1981
1571
|
}
|
|
@@ -1985,13 +1575,13 @@ const useLayoutEffect = (callback, deps) => {
|
|
|
1985
1575
|
}
|
|
1986
1576
|
validateHookContext();
|
|
1987
1577
|
if (!is.function(callback)) {
|
|
1988
|
-
throw new Error('
|
|
1578
|
+
throw new Error('useEffect callback must be a function');
|
|
1989
1579
|
}
|
|
1990
1580
|
if (deps !== undefined && !Array.isArray(deps)) {
|
|
1991
|
-
throw new Error('
|
|
1581
|
+
throw new Error('useEffect dependencies must be an array or undefined');
|
|
1992
1582
|
}
|
|
1993
1583
|
const { hookIndex } = state;
|
|
1994
|
-
const wipFiber =
|
|
1584
|
+
const wipFiber = state.wipFiber;
|
|
1995
1585
|
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
1996
1586
|
const hasChanged = haveDepsChanged(oldHook?.deps, deps);
|
|
1997
1587
|
const hook = {
|
|
@@ -2000,789 +1590,498 @@ const useLayoutEffect = (callback, deps) => {
|
|
|
2000
1590
|
deps,
|
|
2001
1591
|
effect: hasChanged ? callback : null,
|
|
2002
1592
|
cancel: oldHook?.cancel,
|
|
2003
|
-
isLayout: true, // Flag to run synchronously during commit
|
|
2004
1593
|
};
|
|
2005
|
-
wipFiber.hooks[hookIndex] =
|
|
1594
|
+
wipFiber.hooks[hookIndex] =
|
|
1595
|
+
hook;
|
|
2006
1596
|
state.hookIndex++;
|
|
2007
1597
|
};
|
|
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 = () => {
|
|
1598
|
+
const useRef = (initialValue) => {
|
|
1599
|
+
if (typeof window === 'undefined') {
|
|
1600
|
+
return { current: initialValue };
|
|
1601
|
+
}
|
|
2023
1602
|
const state = getState();
|
|
2024
1603
|
if (state.isServerRendering) {
|
|
2025
|
-
|
|
2026
|
-
return `:r${idCounter++}:`;
|
|
1604
|
+
return { current: initialValue };
|
|
2027
1605
|
}
|
|
2028
1606
|
validateHookContext();
|
|
2029
1607
|
const { hookIndex } = state;
|
|
2030
|
-
const wipFiber =
|
|
1608
|
+
const wipFiber = state.wipFiber;
|
|
2031
1609
|
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
2032
1610
|
const hook = {
|
|
2033
1611
|
hookID: hookIndex,
|
|
2034
1612
|
type: RYUNIX_TYPES.RYUNIX_REF,
|
|
2035
1613
|
value: oldHook
|
|
2036
|
-
?
|
|
2037
|
-
:
|
|
1614
|
+
? oldHook.value
|
|
1615
|
+
: { current: initialValue },
|
|
2038
1616
|
};
|
|
2039
|
-
wipFiber.hooks[hookIndex] =
|
|
1617
|
+
wipFiber.hooks[hookIndex] =
|
|
1618
|
+
hook;
|
|
2040
1619
|
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;
|
|
1620
|
+
return hook.value;
|
|
2059
1621
|
};
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
* @param {unknown} value - Value to throttle
|
|
2064
|
-
* @param {number} interval - Minimum interval in milliseconds (default: 300)
|
|
2065
|
-
* @returns {unknown} Throttled value
|
|
2066
|
-
*/
|
|
2067
|
-
const useThrottle = (value, interval = 300) => {
|
|
2068
|
-
const [throttledValue, setThrottledValue] = useStore(value);
|
|
2069
|
-
const lastUpdated = useRef(Date.now());
|
|
2070
|
-
useEffect(() => {
|
|
2071
|
-
const now = Date.now();
|
|
2072
|
-
const elapsed = now - lastUpdated.current;
|
|
2073
|
-
if (elapsed >= interval) {
|
|
2074
|
-
lastUpdated.current = now;
|
|
2075
|
-
setThrottledValue(value);
|
|
2076
|
-
}
|
|
2077
|
-
else {
|
|
2078
|
-
const timer = setTimeout(() => {
|
|
2079
|
-
lastUpdated.current = Date.now();
|
|
2080
|
-
setThrottledValue(value);
|
|
2081
|
-
}, interval - elapsed);
|
|
2082
|
-
return () => clearTimeout(timer);
|
|
2083
|
-
}
|
|
2084
|
-
}, [value, interval]);
|
|
2085
|
-
return throttledValue;
|
|
2086
|
-
};
|
|
2087
|
-
|
|
2088
|
-
var hooks = /*#__PURE__*/Object.freeze({
|
|
2089
|
-
__proto__: null,
|
|
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,
|
|
2133
|
-
};
|
|
2134
|
-
};
|
|
2135
|
-
/**
|
|
2136
|
-
*/
|
|
2137
|
-
const findNearestHydrationBoundary = (fiber) => {
|
|
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;
|
|
1622
|
+
const useMemo = (compute, deps) => {
|
|
1623
|
+
if (typeof window === 'undefined') {
|
|
1624
|
+
return compute();
|
|
2154
1625
|
}
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
*/
|
|
2159
|
-
const getBoundaryDom = (fiber) => {
|
|
2160
|
-
if (!fiber)
|
|
2161
|
-
return null;
|
|
2162
|
-
if (fiber.dom && fiber.dom.nodeType === 1) {
|
|
2163
|
-
const el = fiber.dom;
|
|
2164
|
-
if (el.hasAttribute('data-ryunix-hydrate-boundary'))
|
|
2165
|
-
return el;
|
|
1626
|
+
const state = getState();
|
|
1627
|
+
if (state.isServerRendering) {
|
|
1628
|
+
return compute();
|
|
2166
1629
|
}
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
const el = child.dom;
|
|
2171
|
-
if (el.hasAttribute('data-ryunix-hydrate-boundary'))
|
|
2172
|
-
return el;
|
|
2173
|
-
}
|
|
2174
|
-
child = child.child || child.sibling || null;
|
|
1630
|
+
validateHookContext();
|
|
1631
|
+
if (!is.function(compute)) {
|
|
1632
|
+
throw new Error('useMemo callback must be a function');
|
|
2175
1633
|
}
|
|
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;
|
|
1634
|
+
if (!Array.isArray(deps)) {
|
|
1635
|
+
throw new Error('useMemo requires a dependencies array');
|
|
2222
1636
|
}
|
|
2223
|
-
|
|
2224
|
-
const
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
1637
|
+
const { hookIndex } = state;
|
|
1638
|
+
const wipFiber = state.wipFiber;
|
|
1639
|
+
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
1640
|
+
let value;
|
|
1641
|
+
if (oldHook && !haveDepsChanged(oldHook.deps, deps)) {
|
|
1642
|
+
value = oldHook.value;
|
|
1643
|
+
}
|
|
1644
|
+
else {
|
|
1645
|
+
try {
|
|
1646
|
+
value = compute();
|
|
1647
|
+
}
|
|
1648
|
+
catch (error) {
|
|
1649
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1650
|
+
console.error('Error in useMemo computation:', error);
|
|
2235
1651
|
}
|
|
2236
|
-
|
|
1652
|
+
throw error;
|
|
2237
1653
|
}
|
|
2238
1654
|
}
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
1655
|
+
const hook = {
|
|
1656
|
+
hookID: hookIndex,
|
|
1657
|
+
type: RYUNIX_TYPES.RYUNIX_MEMO,
|
|
1658
|
+
value,
|
|
1659
|
+
deps,
|
|
1660
|
+
};
|
|
1661
|
+
wipFiber.hooks[hookIndex] =
|
|
1662
|
+
hook;
|
|
1663
|
+
state.hookIndex++;
|
|
1664
|
+
return value;
|
|
2247
1665
|
};
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
*/
|
|
2252
|
-
const isUnderClientOnlyBoundary = (fiber) => {
|
|
2253
|
-
let current = fiber?.parent || null;
|
|
2254
|
-
while (current) {
|
|
2255
|
-
if (current._hydrateClientOnly)
|
|
2256
|
-
return true;
|
|
2257
|
-
current = current.parent || null;
|
|
1666
|
+
const useCallback = (callback, deps) => {
|
|
1667
|
+
if (!is.function(callback)) {
|
|
1668
|
+
throw new Error('useCallback requires a function as first argument');
|
|
2258
1669
|
}
|
|
2259
|
-
return
|
|
1670
|
+
return useMemo(() => callback, deps);
|
|
2260
1671
|
};
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
fiber.type === Symbol.for('ryunix.portal');
|
|
2274
|
-
if (state.isHydrating && isPassthrough) {
|
|
2275
|
-
fiber.effectTag = EFFECT_TAGS.HYDRATE;
|
|
2276
|
-
}
|
|
2277
|
-
else if (state.isHydrating && isUnderClientOnlyBoundary(fiber)) {
|
|
2278
|
-
if (!fiber.dom) {
|
|
2279
|
-
fiber.dom = /** @type {HTMLElement | Text | null} */ createDom(fiber);
|
|
2280
|
-
fiber.effectTag = EFFECT_TAGS.PLACEMENT;
|
|
1672
|
+
const createContext = (contextId = RYUNIX_TYPES.RYUNIX_CONTEXT, defaultValue = {}) => {
|
|
1673
|
+
const Provider = ({ value, children }) => {
|
|
1674
|
+
return createElement(RYUNIX_TYPES.RYUNIX_CONTEXT, { value, children, _contextId: contextId }, ...flattenArray([children]));
|
|
1675
|
+
};
|
|
1676
|
+
Provider._contextId = contextId;
|
|
1677
|
+
const useContext = (ctxID = contextId) => {
|
|
1678
|
+
const state = getState();
|
|
1679
|
+
if (state.isServerRendering) {
|
|
1680
|
+
const ssrContexts = state.ssrContexts;
|
|
1681
|
+
return ssrContexts && ssrContexts[ctxID] !== undefined
|
|
1682
|
+
? ssrContexts[ctxID]
|
|
1683
|
+
: defaultValue;
|
|
2281
1684
|
}
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
const isElement = typeof fiber.type === 'string' &&
|
|
2288
|
-
domNode.nodeType === 1 &&
|
|
2289
|
-
domNode.tagName.toLowerCase() === fiber.type.toLowerCase();
|
|
2290
|
-
if (isText || isElement) {
|
|
2291
|
-
fiber.dom = /** @type {HTMLElement | Text} */ domNode;
|
|
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);
|
|
1685
|
+
validateHookContext();
|
|
1686
|
+
let fiber = state.wipFiber;
|
|
1687
|
+
while (fiber) {
|
|
1688
|
+
if (fiber._contextId === ctxID && fiber._contextValue !== undefined) {
|
|
1689
|
+
return fiber._contextValue;
|
|
2304
1690
|
}
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
const boundaryFiber = findNearestHydrationBoundary(fiber);
|
|
2309
|
-
const boundaryDom = boundaryFiber ? getBoundaryDom(boundaryFiber) : null;
|
|
2310
|
-
if (policy.recover === 'boundary' && boundaryFiber && boundaryDom) {
|
|
2311
|
-
logHydrationBoundaryMismatch(detail);
|
|
2312
|
-
enqueueScopedRecovery(boundaryFiber, boundaryDom, state.hydrateCursor ?? null);
|
|
2313
|
-
state.hydrateCursor = skipHydrationSubtree(state.hydrateCursor ?? null, boundaryDom);
|
|
2314
|
-
fiber.dom = /** @type {HTMLElement | Text | null} */ createDom(fiber);
|
|
2315
|
-
fiber.effectTag = EFFECT_TAGS.PLACEMENT;
|
|
2316
|
-
}
|
|
2317
|
-
else if (policy.recover === 'none') {
|
|
2318
|
-
logHydrationFatal(detail);
|
|
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
|
-
}
|
|
1691
|
+
const fiberType = fiber.type;
|
|
1692
|
+
if (fiberType?._contextId === ctxID && fiber.props?.value !== undefined) {
|
|
1693
|
+
return fiber.props.value;
|
|
2332
1694
|
}
|
|
1695
|
+
fiber = fiber.parent;
|
|
2333
1696
|
}
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
}
|
|
2337
|
-
}
|
|
2338
|
-
const children = fiber.props?.children || [];
|
|
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);
|
|
2351
|
-
};
|
|
2352
|
-
/**
|
|
2353
|
-
* The Component `Image` takes in a `src` and other props, and returns an `img` element with the
|
|
2354
|
-
* specified `src` and props.
|
|
2355
|
-
* @returns The `Image` component is being returned. It is a functional component that renders an `img`
|
|
2356
|
-
* element with the specified `src` and other props passed to it.
|
|
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 });
|
|
2364
|
-
};
|
|
2365
|
-
const { Provider: MDXProvider, useContext: useMDXComponents } = createContext('ryunix.mdx',
|
|
2366
|
-
/** @type {Record<string, RyunixComponent>} */ {});
|
|
2367
|
-
/**
|
|
2368
|
-
* Get merged MDX components from context and provided components
|
|
2369
|
-
* @param {Record<string, RyunixComponent>} [components] - Additional components to merge
|
|
2370
|
-
* @returns {Record<string, RyunixComponent>} Merged components object
|
|
2371
|
-
*/
|
|
2372
|
-
const getMDXComponents = (components) => {
|
|
2373
|
-
const contextComponents = useMDXComponents();
|
|
1697
|
+
return defaultValue;
|
|
1698
|
+
};
|
|
2374
1699
|
return {
|
|
2375
|
-
|
|
2376
|
-
|
|
1700
|
+
Provider: Provider,
|
|
1701
|
+
useContext,
|
|
2377
1702
|
};
|
|
2378
1703
|
};
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
const
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
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));
|
|
1704
|
+
const useQuery = () => {
|
|
1705
|
+
if (typeof window === 'undefined')
|
|
1706
|
+
return {};
|
|
1707
|
+
const searchParams = new URLSearchParams(window.location.search);
|
|
1708
|
+
const query = {};
|
|
1709
|
+
for (const [key, value] of searchParams.entries()) {
|
|
1710
|
+
query[key] = value;
|
|
1711
|
+
}
|
|
1712
|
+
return query;
|
|
2434
1713
|
};
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
const
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
props: { children: [element] },
|
|
2446
|
-
isHydrating: false,
|
|
2447
|
-
hydrateCursor: null,
|
|
2448
|
-
};
|
|
2449
|
-
scheduleWork$1(root, undefined);
|
|
1714
|
+
const useHash = () => {
|
|
1715
|
+
if (typeof window === 'undefined')
|
|
1716
|
+
return '';
|
|
1717
|
+
const [hash, setHash] = useStore(window.location.hash);
|
|
1718
|
+
useEffect(() => {
|
|
1719
|
+
const onHashChange = () => setHash(window.location.hash);
|
|
1720
|
+
window.addEventListener('hashchange', onHashChange);
|
|
1721
|
+
return () => window.removeEventListener('hashchange', onHashChange);
|
|
1722
|
+
}, []);
|
|
1723
|
+
return hash;
|
|
2450
1724
|
};
|
|
2451
|
-
const
|
|
1725
|
+
const useMetadata = (tags = {}, options = {}) => {
|
|
2452
1726
|
const state = getState();
|
|
2453
|
-
|
|
2454
|
-
|
|
1727
|
+
if (state.isServerRendering) {
|
|
1728
|
+
state.ssrMetadata = { ...state.ssrMetadata, ...tags };
|
|
2455
1729
|
return;
|
|
2456
|
-
state.scopedRecoveryQueue = [];
|
|
2457
|
-
for (const item of queue) {
|
|
2458
|
-
logHydrationBoundaryRecovery();
|
|
2459
|
-
renderSubtree(item.element, item.boundaryDom);
|
|
2460
1730
|
}
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
return;
|
|
2473
|
-
state.hydrationRecover = true;
|
|
2474
|
-
state.hydrationFailed = false;
|
|
2475
|
-
logHydrationFailure('');
|
|
2476
|
-
logHydrationRecovery();
|
|
2477
|
-
renderSubtree(/** @type {RyunixNode} */ element, container);
|
|
2478
|
-
};
|
|
2479
|
-
const runHydrationRecovery = () => {
|
|
2480
|
-
recoverScopedHydrationFailures();
|
|
2481
|
-
recoverHydrationFailureIfNeeded();
|
|
2482
|
-
};
|
|
2483
|
-
|
|
2484
|
-
/**
|
|
2485
|
-
* @typedef {import('../types/internal.js').RyunixFiber} RyunixFiber
|
|
2486
|
-
* @typedef {import('../types/internal.js').RyunixRootFiber} RyunixRootFiber
|
|
2487
|
-
*/
|
|
2488
|
-
/** @type {RyunixRootFiber[]} */
|
|
2489
|
-
let workQueue = [];
|
|
2490
|
-
/** @type {boolean} */
|
|
2491
|
-
let isWorkLoopScheduled = false;
|
|
2492
|
-
/**
|
|
2493
|
-
* @param {RyunixFiber} fiber
|
|
2494
|
-
* @returns {RyunixFiber | null}
|
|
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);
|
|
1731
|
+
useEffect(() => {
|
|
1732
|
+
if (typeof document === 'undefined')
|
|
1733
|
+
return;
|
|
1734
|
+
let finalTitle = 'Ryunix App';
|
|
1735
|
+
const template = options.title?.template;
|
|
1736
|
+
const defaultTitle = options.title?.prefix || 'Ryunix App';
|
|
1737
|
+
const pageTitle = tags.pageTitle || tags.title;
|
|
1738
|
+
if (is.string(pageTitle) && pageTitle.trim()) {
|
|
1739
|
+
finalTitle = template?.includes('%s')
|
|
1740
|
+
? template.replace('%s', pageTitle)
|
|
1741
|
+
: pageTitle;
|
|
2502
1742
|
}
|
|
2503
1743
|
else {
|
|
2504
|
-
|
|
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) { }
|
|
1744
|
+
finalTitle = defaultTitle;
|
|
2525
1745
|
}
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
foundBoundary = true;
|
|
2534
|
-
break;
|
|
1746
|
+
document.title = finalTitle;
|
|
1747
|
+
if (tags.canonical) {
|
|
1748
|
+
let link = document.querySelector('link[rel="canonical"]');
|
|
1749
|
+
if (!link) {
|
|
1750
|
+
link = document.createElement('link');
|
|
1751
|
+
link.setAttribute('rel', 'canonical');
|
|
1752
|
+
document.head.appendChild(link);
|
|
2535
1753
|
}
|
|
2536
|
-
|
|
1754
|
+
link.setAttribute('href', tags.canonical);
|
|
2537
1755
|
}
|
|
2538
|
-
|
|
2539
|
-
if (
|
|
2540
|
-
|
|
1756
|
+
Object.entries(tags).forEach(([key, value]) => {
|
|
1757
|
+
if (['title', 'pageTitle', 'canonical'].includes(key))
|
|
1758
|
+
return;
|
|
1759
|
+
const isProperty = key.startsWith('og:') || key.startsWith('twitter:');
|
|
1760
|
+
const selector = `meta[${isProperty ? 'property' : 'name'}='${key}']`;
|
|
1761
|
+
let meta = document.head.querySelector(selector);
|
|
1762
|
+
if (!meta) {
|
|
1763
|
+
meta = document.createElement('meta');
|
|
1764
|
+
meta.setAttribute(isProperty ? 'property' : 'name', key);
|
|
1765
|
+
document.head.appendChild(meta);
|
|
2541
1766
|
}
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
1767
|
+
meta.setAttribute('content', value);
|
|
1768
|
+
});
|
|
1769
|
+
}, [JSON.stringify(tags), JSON.stringify(options)]);
|
|
1770
|
+
};
|
|
1771
|
+
const RouterContext = createContext('ryunix.navigation', {
|
|
1772
|
+
location: '/',
|
|
1773
|
+
params: {},
|
|
1774
|
+
query: {},
|
|
1775
|
+
navigate: (_path) => { },
|
|
1776
|
+
route: null,
|
|
1777
|
+
});
|
|
1778
|
+
const findRoute = (routes, path) => {
|
|
1779
|
+
const pathname = path.split('?')[0].split('#')[0];
|
|
1780
|
+
const notFoundRoute = routes.find((route) => route.NotFound);
|
|
1781
|
+
const notFound = notFoundRoute
|
|
1782
|
+
? { route: { component: notFoundRoute.NotFound }, params: {} }
|
|
1783
|
+
: { route: { component: null }, params: {} };
|
|
1784
|
+
for (const route of routes) {
|
|
1785
|
+
if (route.subRoutes) {
|
|
1786
|
+
const childRoute = findRoute(route.subRoutes, path);
|
|
1787
|
+
if (childRoute)
|
|
1788
|
+
return childRoute;
|
|
2549
1789
|
}
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
1790
|
+
if (route.path === '*')
|
|
1791
|
+
return notFound;
|
|
1792
|
+
if (!route.path || typeof route.path !== 'string')
|
|
1793
|
+
continue;
|
|
1794
|
+
const keys = [];
|
|
1795
|
+
const pattern = new RegExp(`^${route.path.replace(/:(\.\.\.)?(\w+)/g, (match, isCatchAll, key) => {
|
|
1796
|
+
keys.push({ key, isCatchAll: !!isCatchAll });
|
|
1797
|
+
return isCatchAll ? '(.+)' : '([^/]+)';
|
|
1798
|
+
})}$`);
|
|
1799
|
+
const matchPath = pathname.match(pattern);
|
|
1800
|
+
if (matchPath) {
|
|
1801
|
+
const params = keys.reduce((acc, keyObj, index) => {
|
|
1802
|
+
const val = matchPath[index + 1];
|
|
1803
|
+
acc[keyObj.key] = keyObj.isCatchAll && val ? val.split('/') : val;
|
|
1804
|
+
return acc;
|
|
1805
|
+
}, {});
|
|
1806
|
+
return { route, params };
|
|
2555
1807
|
}
|
|
2556
1808
|
}
|
|
2557
|
-
|
|
2558
|
-
|
|
1809
|
+
return notFound;
|
|
1810
|
+
};
|
|
1811
|
+
const getSsrPathname = () => {
|
|
1812
|
+
const pathname = globalThis?.window?.location?.pathname;
|
|
1813
|
+
if (typeof pathname === 'string' && pathname) {
|
|
1814
|
+
return pathname.split('?')[0].split('#')[0];
|
|
2559
1815
|
}
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
1816
|
+
return '/';
|
|
1817
|
+
};
|
|
1818
|
+
const RouterProvider = ({ routes, children }) => {
|
|
1819
|
+
if (typeof window === 'undefined') {
|
|
1820
|
+
const location = getSsrPathname();
|
|
1821
|
+
const currentRouteData = findRoute(routes, location);
|
|
1822
|
+
const contextValue = {
|
|
1823
|
+
location,
|
|
1824
|
+
params: currentRouteData.params || {},
|
|
1825
|
+
query: {},
|
|
1826
|
+
navigate: () => { },
|
|
1827
|
+
route: currentRouteData.route,
|
|
1828
|
+
};
|
|
1829
|
+
return createElement(RouterContext.Provider, { value: contextValue }, Fragment({ children }));
|
|
2573
1830
|
}
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
if (nextRoot.isHydrating !== undefined) {
|
|
2589
|
-
state.isHydrating = nextRoot.isHydrating;
|
|
2590
|
-
state.hydrateCursor = nextRoot.hydrateCursor;
|
|
2591
|
-
}
|
|
1831
|
+
const [location, setLocation] = useStore(window.location.pathname);
|
|
1832
|
+
useEffect(() => {
|
|
1833
|
+
const update = () => setLocation(window.location.pathname);
|
|
1834
|
+
window.addEventListener('popstate', update);
|
|
1835
|
+
window.addEventListener('hashchange', update);
|
|
1836
|
+
return () => {
|
|
1837
|
+
window.removeEventListener('popstate', update);
|
|
1838
|
+
window.removeEventListener('hashchange', update);
|
|
1839
|
+
};
|
|
1840
|
+
}, []);
|
|
1841
|
+
const navigate = (path) => {
|
|
1842
|
+
if (typeof window !== 'undefined' && window.__RYUNIX_MPA__) {
|
|
1843
|
+
window.location.assign(path);
|
|
1844
|
+
return;
|
|
2592
1845
|
}
|
|
2593
|
-
|
|
2594
|
-
|
|
1846
|
+
window.history.pushState({}, '', path);
|
|
1847
|
+
setLocation(path);
|
|
1848
|
+
};
|
|
1849
|
+
const currentRouteData = findRoute(routes, location);
|
|
1850
|
+
const query = useQuery();
|
|
1851
|
+
const contextValue = {
|
|
1852
|
+
location,
|
|
1853
|
+
params: currentRouteData.params || {},
|
|
1854
|
+
query,
|
|
1855
|
+
navigate,
|
|
1856
|
+
route: currentRouteData.route,
|
|
1857
|
+
};
|
|
1858
|
+
return createElement(RouterContext.Provider, { value: contextValue }, Fragment({ children }));
|
|
1859
|
+
};
|
|
1860
|
+
const useRouter = () => {
|
|
1861
|
+
return RouterContext.useContext('ryunix.navigation');
|
|
1862
|
+
};
|
|
1863
|
+
const Children = () => {
|
|
1864
|
+
const { route, params, query, location } = useRouter();
|
|
1865
|
+
if (!route || !route.component)
|
|
1866
|
+
return null;
|
|
1867
|
+
const hash = useHash();
|
|
1868
|
+
useEffect(() => {
|
|
1869
|
+
if (hash) {
|
|
1870
|
+
const id = hash.slice(1);
|
|
1871
|
+
const el = document.getElementById(id);
|
|
1872
|
+
if (el)
|
|
1873
|
+
el.scrollIntoView({ block: 'start', behavior: 'smooth' });
|
|
2595
1874
|
}
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
}
|
|
2605
|
-
else {
|
|
2606
|
-
isWorkLoopScheduled = false;
|
|
2607
|
-
}
|
|
1875
|
+
}, [hash]);
|
|
1876
|
+
return createElement(route.component, {
|
|
1877
|
+
key: location,
|
|
1878
|
+
params,
|
|
1879
|
+
query,
|
|
1880
|
+
hash,
|
|
1881
|
+
location,
|
|
1882
|
+
});
|
|
2608
1883
|
};
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
const
|
|
2614
|
-
const
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
// Set immediate hydration state
|
|
2623
|
-
if (root.isHydrating !== undefined) {
|
|
2624
|
-
state.isHydrating = root.isHydrating;
|
|
2625
|
-
state.hydrateCursor = root.hydrateCursor;
|
|
1884
|
+
const usePathname = () => {
|
|
1885
|
+
const { location } = useRouter();
|
|
1886
|
+
return location.split('?')[0].split('#')[0];
|
|
1887
|
+
};
|
|
1888
|
+
const useSearchParams = () => {
|
|
1889
|
+
const { query } = useRouter();
|
|
1890
|
+
return new URLSearchParams(query);
|
|
1891
|
+
};
|
|
1892
|
+
const Link = ({ to, prefetch = true, ...props }) => {
|
|
1893
|
+
const { navigate } = useRouter();
|
|
1894
|
+
const handleClick = (e) => {
|
|
1895
|
+
if (e.button !== 0 || e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) {
|
|
1896
|
+
return;
|
|
2626
1897
|
}
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
1898
|
+
e.preventDefault();
|
|
1899
|
+
navigate(to);
|
|
1900
|
+
};
|
|
1901
|
+
const handleMouseEnter = () => {
|
|
1902
|
+
};
|
|
1903
|
+
const { className: _omitClassName, ...cleanedProps } = props;
|
|
1904
|
+
return createElement('a', {
|
|
1905
|
+
href: to,
|
|
1906
|
+
onClick: handleClick,
|
|
1907
|
+
onMouseEnter: handleMouseEnter,
|
|
1908
|
+
className: props.className || props['ryunix-class'],
|
|
1909
|
+
...cleanedProps,
|
|
1910
|
+
}, props.children);
|
|
1911
|
+
};
|
|
1912
|
+
const NavLink = ({ to, exact = false, ...props }) => {
|
|
1913
|
+
const { location, navigate } = useRouter();
|
|
1914
|
+
const isActive = exact ? location === to : location.startsWith(to);
|
|
1915
|
+
const resolveClass = (cls) => typeof cls === 'function' ? cls({ isActive }) : cls || '';
|
|
1916
|
+
const handleClick = (e) => {
|
|
1917
|
+
if (e.button !== 0 || e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) {
|
|
1918
|
+
return;
|
|
1919
|
+
}
|
|
1920
|
+
e.preventDefault();
|
|
1921
|
+
navigate(to);
|
|
1922
|
+
};
|
|
1923
|
+
const classAttrName = props['ryunix-class'] ? 'ryunix-class' : 'className';
|
|
1924
|
+
const classAttrValue = resolveClass(props['ryunix-class'] || props.className);
|
|
1925
|
+
const { ['ryunix-class']: _omitRyunix, className: _omitClassName, ...cleanedProps } = props;
|
|
1926
|
+
return createElement('a', {
|
|
1927
|
+
href: to,
|
|
1928
|
+
onClick: handleClick,
|
|
1929
|
+
[classAttrName]: classAttrValue,
|
|
1930
|
+
...cleanedProps,
|
|
1931
|
+
}, props.children);
|
|
1932
|
+
};
|
|
1933
|
+
const useStorePriority = (initialState) => {
|
|
1934
|
+
const reducer = (state, action) => typeof action === 'function'
|
|
1935
|
+
? action.value(state)
|
|
1936
|
+
: action.value;
|
|
1937
|
+
const [state, baseDispatch] = useReducer(reducer, initialState, undefined);
|
|
1938
|
+
const dispatch = (action, priority = Priority.NORMAL) => {
|
|
1939
|
+
const wrappedAction = {
|
|
1940
|
+
value: action,
|
|
1941
|
+
priority,
|
|
1942
|
+
};
|
|
1943
|
+
scheduleUpdate(() => baseDispatch(wrappedAction, priority), priority);
|
|
1944
|
+
};
|
|
1945
|
+
return [state, dispatch];
|
|
1946
|
+
};
|
|
1947
|
+
const useTransition = () => {
|
|
1948
|
+
const [isPending, setIsPending] = useStorePriority(false);
|
|
1949
|
+
const startTransition = (callback) => {
|
|
1950
|
+
setIsPending(true, Priority.IMMEDIATE);
|
|
1951
|
+
setTimeout(() => {
|
|
1952
|
+
runWithPriority(Priority.LOW, () => {
|
|
1953
|
+
callback();
|
|
1954
|
+
setIsPending(false, Priority.LOW);
|
|
2637
1955
|
});
|
|
1956
|
+
}, 0);
|
|
1957
|
+
};
|
|
1958
|
+
return [isPending, startTransition];
|
|
1959
|
+
};
|
|
1960
|
+
const useDeferredValue = (value) => {
|
|
1961
|
+
const [deferredValue, setDeferredValue] = useStorePriority(value);
|
|
1962
|
+
useEffect(() => {
|
|
1963
|
+
const timeout = setTimeout(() => {
|
|
1964
|
+
setDeferredValue(value, Priority.LOW);
|
|
1965
|
+
}, 100);
|
|
1966
|
+
return () => clearTimeout(timeout);
|
|
1967
|
+
}, [value]);
|
|
1968
|
+
return deferredValue;
|
|
1969
|
+
};
|
|
1970
|
+
const usePersistentStore = (key, initialState = '') => {
|
|
1971
|
+
const [state, dispatch] = useStore(() => {
|
|
1972
|
+
try {
|
|
1973
|
+
const item = window.localStorage.getItem(key);
|
|
1974
|
+
return item ? JSON.parse(item) : initialState;
|
|
2638
1975
|
}
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
1976
|
+
catch (error) {
|
|
1977
|
+
return initialState;
|
|
1978
|
+
}
|
|
1979
|
+
});
|
|
1980
|
+
const setValue = (value) => {
|
|
1981
|
+
try {
|
|
1982
|
+
dispatch(value);
|
|
1983
|
+
window.localStorage.setItem(key, JSON.stringify(value));
|
|
1984
|
+
}
|
|
1985
|
+
catch (error) {
|
|
1986
|
+
console.error(error);
|
|
2642
1987
|
}
|
|
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
1988
|
};
|
|
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;
|
|
1989
|
+
return [state, setValue];
|
|
2692
1990
|
};
|
|
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),
|
|
1991
|
+
const useSwitch = (initialState = false) => {
|
|
1992
|
+
const [state, dispatch] = useStore(initialState);
|
|
1993
|
+
const toggle = () => {
|
|
1994
|
+
dispatch((prev) => !prev);
|
|
2715
1995
|
};
|
|
2716
|
-
|
|
2717
|
-
return root;
|
|
1996
|
+
return [state, toggle];
|
|
2718
1997
|
};
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
* @returns {RyunixRootFiber | undefined}
|
|
2724
|
-
*/
|
|
2725
|
-
const init = (MainElement, root = '__ryunix', components = {}) => {
|
|
1998
|
+
const useLayoutEffect = (callback, deps) => {
|
|
1999
|
+
if (typeof window === 'undefined') {
|
|
2000
|
+
return;
|
|
2001
|
+
}
|
|
2726
2002
|
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.`);
|
|
2003
|
+
if (state.isServerRendering) {
|
|
2004
|
+
return;
|
|
2739
2005
|
}
|
|
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;
|
|
2006
|
+
validateHookContext();
|
|
2007
|
+
if (!is.function(callback)) {
|
|
2008
|
+
throw new Error('useLayoutEffect callback must be a function');
|
|
2749
2009
|
}
|
|
2750
|
-
if (
|
|
2751
|
-
|
|
2010
|
+
if (deps !== undefined && !Array.isArray(deps)) {
|
|
2011
|
+
throw new Error('useLayoutEffect dependencies must be an array or undefined');
|
|
2752
2012
|
}
|
|
2753
|
-
const
|
|
2754
|
-
|
|
2013
|
+
const { hookIndex } = state;
|
|
2014
|
+
const wipFiber = state.wipFiber;
|
|
2015
|
+
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
2016
|
+
const hasChanged = haveDepsChanged(oldHook?.deps, deps);
|
|
2017
|
+
const hook = {
|
|
2018
|
+
hookID: hookIndex,
|
|
2019
|
+
type: RYUNIX_TYPES.RYUNIX_EFFECT,
|
|
2020
|
+
deps,
|
|
2021
|
+
effect: hasChanged ? callback : null,
|
|
2022
|
+
cancel: oldHook?.cancel,
|
|
2023
|
+
isLayout: true,
|
|
2024
|
+
};
|
|
2025
|
+
wipFiber.hooks[hookIndex] =
|
|
2026
|
+
hook;
|
|
2027
|
+
state.hookIndex++;
|
|
2755
2028
|
};
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
return /** @type {RyunixNode} */ /** @type {(props: Record<string, unknown>) => RyunixNode} */ component(props);
|
|
2029
|
+
let idCounter = 0;
|
|
2030
|
+
const resetIdCounter = () => {
|
|
2031
|
+
idCounter = 0;
|
|
2032
|
+
};
|
|
2033
|
+
const useId = () => {
|
|
2034
|
+
const state = getState();
|
|
2035
|
+
if (state.isServerRendering) {
|
|
2036
|
+
return `:r${idCounter++}:`;
|
|
2765
2037
|
}
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2038
|
+
validateHookContext();
|
|
2039
|
+
const { hookIndex } = state;
|
|
2040
|
+
const wipFiber = state.wipFiber;
|
|
2041
|
+
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
2042
|
+
const hook = {
|
|
2043
|
+
hookID: hookIndex,
|
|
2044
|
+
type: RYUNIX_TYPES.RYUNIX_REF,
|
|
2045
|
+
value: oldHook
|
|
2046
|
+
? oldHook.value
|
|
2047
|
+
: `:r${idCounter++}:`,
|
|
2048
|
+
};
|
|
2049
|
+
wipFiber.hooks[hookIndex] =
|
|
2050
|
+
hook;
|
|
2051
|
+
state.hookIndex++;
|
|
2052
|
+
return hook.value;
|
|
2053
|
+
};
|
|
2054
|
+
const useDebounce = (value, delay = 300) => {
|
|
2055
|
+
const [debouncedValue, setDebouncedValue] = useStore(value);
|
|
2056
|
+
useEffect(() => {
|
|
2057
|
+
const timer = setTimeout(() => {
|
|
2058
|
+
setDebouncedValue(value);
|
|
2059
|
+
}, delay);
|
|
2060
|
+
return () => clearTimeout(timer);
|
|
2061
|
+
}, [value, delay]);
|
|
2062
|
+
return debouncedValue;
|
|
2063
|
+
};
|
|
2064
|
+
const useThrottle = (value, interval = 300) => {
|
|
2065
|
+
const [throttledValue, setThrottledValue] = useStore(value);
|
|
2066
|
+
const lastUpdated = useRef(Date.now());
|
|
2067
|
+
useEffect(() => {
|
|
2068
|
+
const now = Date.now();
|
|
2069
|
+
const elapsed = now - lastUpdated.current;
|
|
2070
|
+
if (elapsed >= interval) {
|
|
2071
|
+
lastUpdated.current = now;
|
|
2072
|
+
setThrottledValue(value);
|
|
2769
2073
|
}
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2074
|
+
else {
|
|
2075
|
+
const timer = setTimeout(() => {
|
|
2076
|
+
lastUpdated.current = Date.now();
|
|
2077
|
+
setThrottledValue(value);
|
|
2078
|
+
}, interval - elapsed);
|
|
2079
|
+
return () => clearTimeout(timer);
|
|
2080
|
+
}
|
|
2081
|
+
}, [value, interval]);
|
|
2082
|
+
return throttledValue;
|
|
2774
2083
|
};
|
|
2775
2084
|
|
|
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
|
-
*/
|
|
2786
2085
|
const escapeHtml = (unsafe) => {
|
|
2787
2086
|
if (typeof unsafe !== 'string')
|
|
2788
2087
|
return String(unsafe);
|
|
@@ -2793,10 +2092,6 @@ const escapeHtml = (unsafe) => {
|
|
|
2793
2092
|
.replace(/"/g, '"')
|
|
2794
2093
|
.replace(/'/g, ''');
|
|
2795
2094
|
};
|
|
2796
|
-
/**
|
|
2797
|
-
* @param {Record<string, unknown>} styleObj
|
|
2798
|
-
* @returns {string}
|
|
2799
|
-
*/
|
|
2800
2095
|
const renderStyle = (styleObj) => {
|
|
2801
2096
|
if (!is.object(styleObj) || is.null(styleObj))
|
|
2802
2097
|
return '';
|
|
@@ -2821,10 +2116,6 @@ const VOID_ELEMENTS = new Set([
|
|
|
2821
2116
|
'track',
|
|
2822
2117
|
'wbr',
|
|
2823
2118
|
]);
|
|
2824
|
-
/**
|
|
2825
|
-
* @param {RyunixNode | RyunixNode[]} element
|
|
2826
|
-
* @returns {string}
|
|
2827
|
-
*/
|
|
2828
2119
|
const renderToStringImpl = (element) => {
|
|
2829
2120
|
if (element == null || typeof element === 'boolean') {
|
|
2830
2121
|
return '';
|
|
@@ -2835,11 +2126,9 @@ const renderToStringImpl = (element) => {
|
|
|
2835
2126
|
if (Array.isArray(element)) {
|
|
2836
2127
|
return element.map((child) => renderToStringImpl(child)).join('');
|
|
2837
2128
|
}
|
|
2838
|
-
/** @type {RyunixElement} */
|
|
2839
2129
|
const vnode = element;
|
|
2840
2130
|
if (vnode.type === RYUNIX_TYPES.TEXT_ELEMENT) {
|
|
2841
|
-
return escapeHtml(
|
|
2842
|
-
/** @type {import('./createElement.js').RyunixTextElement} */ vnode.props
|
|
2131
|
+
return escapeHtml(vnode.props
|
|
2843
2132
|
.nodeValue);
|
|
2844
2133
|
}
|
|
2845
2134
|
if (vnode.type === RYUNIX_TYPES.RYUNIX_FRAGMENT) {
|
|
@@ -2847,11 +2136,9 @@ const renderToStringImpl = (element) => {
|
|
|
2847
2136
|
return children.map((child) => renderToStringImpl(child)).join('');
|
|
2848
2137
|
}
|
|
2849
2138
|
if (vnode.type === RYUNIX_TYPES.RYUNIX_CONTEXT) {
|
|
2850
|
-
// Context Providers just render their children transparently on the server
|
|
2851
2139
|
const state = getState();
|
|
2852
2140
|
state.ssrContexts = state.ssrContexts || {};
|
|
2853
|
-
const ctxProps =
|
|
2854
|
-
/** @type {{ _contextId?: string, value?: unknown, children?: RyunixNode | RyunixNode[] }} */ vnode.props ||
|
|
2141
|
+
const ctxProps = vnode.props ||
|
|
2855
2142
|
{};
|
|
2856
2143
|
const ctxId = ctxProps._contextId;
|
|
2857
2144
|
const prevCtx = state.ssrContexts[ctxId];
|
|
@@ -2874,11 +2161,9 @@ const renderToStringImpl = (element) => {
|
|
|
2874
2161
|
if (typeof vnode.type === 'function') {
|
|
2875
2162
|
const type = vnode.type;
|
|
2876
2163
|
const props = vnode.props || {};
|
|
2877
|
-
const renderedElement =
|
|
2878
|
-
/** @type {(props: Record<string, unknown>) => RyunixNode} */ type(props);
|
|
2164
|
+
const renderedElement = type(props);
|
|
2879
2165
|
return renderToStringImpl(renderedElement);
|
|
2880
2166
|
}
|
|
2881
|
-
// It's a standard host element
|
|
2882
2167
|
const type = String(vnode.type);
|
|
2883
2168
|
const props = vnode.props || {};
|
|
2884
2169
|
let attributes = '';
|
|
@@ -2890,7 +2175,7 @@ const renderToStringImpl = (element) => {
|
|
|
2890
2175
|
htmlChildren = value.map((child) => renderToStringImpl(child)).join('');
|
|
2891
2176
|
}
|
|
2892
2177
|
else {
|
|
2893
|
-
htmlChildren = renderToStringImpl(
|
|
2178
|
+
htmlChildren = renderToStringImpl(value);
|
|
2894
2179
|
}
|
|
2895
2180
|
}
|
|
2896
2181
|
else if (key === 'dangerouslySetInnerHTML') {
|
|
@@ -2900,8 +2185,7 @@ const renderToStringImpl = (element) => {
|
|
|
2900
2185
|
}
|
|
2901
2186
|
}
|
|
2902
2187
|
else if (key === STRINGS.STYLE || key === OLD_STRINGS.STYLE) {
|
|
2903
|
-
const styleString = renderStyle(
|
|
2904
|
-
/** @type {Record<string, unknown>} */ value);
|
|
2188
|
+
const styleString = renderStyle(value);
|
|
2905
2189
|
if (styleString) {
|
|
2906
2190
|
attributes += ` style="${escapeHtml(styleString)}"`;
|
|
2907
2191
|
}
|
|
@@ -2912,6 +2196,8 @@ const renderToStringImpl = (element) => {
|
|
|
2912
2196
|
}
|
|
2913
2197
|
}
|
|
2914
2198
|
else if (!key.startsWith('on') &&
|
|
2199
|
+
key !== 'key' &&
|
|
2200
|
+
key !== 'ref' &&
|
|
2915
2201
|
key !== '__source' &&
|
|
2916
2202
|
key !== '__self') {
|
|
2917
2203
|
if (typeof value === 'boolean') {
|
|
@@ -2943,17 +2229,10 @@ function $RC(id, templateId) {
|
|
|
2943
2229
|
`
|
|
2944
2230
|
.replace(/\s+/g, ' ')
|
|
2945
2231
|
.trim();
|
|
2946
|
-
/**
|
|
2947
|
-
* @param {RyunixNode | RyunixNode[]} element
|
|
2948
|
-
* @param {(chunk: string) => void} push
|
|
2949
|
-
* @param {RyunixSuspenseTask[]} [suspenseTasks]
|
|
2950
|
-
* @returns {Promise<void>}
|
|
2951
|
-
*/
|
|
2952
2232
|
const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
2953
2233
|
if (element == null || typeof element === 'boolean') {
|
|
2954
2234
|
return;
|
|
2955
2235
|
}
|
|
2956
|
-
// Await the element if it's a promise (e.g. from an async Server Component directly rendered)
|
|
2957
2236
|
if (element instanceof Promise) {
|
|
2958
2237
|
element = await element;
|
|
2959
2238
|
if (element == null || typeof element === 'boolean')
|
|
@@ -2969,11 +2248,9 @@ const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
|
2969
2248
|
}
|
|
2970
2249
|
return;
|
|
2971
2250
|
}
|
|
2972
|
-
/** @type {RyunixElement} */
|
|
2973
2251
|
const vnode = element;
|
|
2974
2252
|
if (vnode.type === RYUNIX_TYPES.TEXT_ELEMENT) {
|
|
2975
|
-
push(escapeHtml(
|
|
2976
|
-
/** @type {import('./createElement.js').RyunixTextElement} */ vnode
|
|
2253
|
+
push(escapeHtml(vnode
|
|
2977
2254
|
.props.nodeValue));
|
|
2978
2255
|
return;
|
|
2979
2256
|
}
|
|
@@ -2987,8 +2264,7 @@ const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
|
2987
2264
|
if (vnode.type === RYUNIX_TYPES.RYUNIX_CONTEXT) {
|
|
2988
2265
|
const state = getState();
|
|
2989
2266
|
state.ssrContexts = state.ssrContexts || {};
|
|
2990
|
-
const ctxProps =
|
|
2991
|
-
/** @type {{ _contextId?: string, value?: unknown, children?: RyunixNode | RyunixNode[] }} */ vnode.props ||
|
|
2267
|
+
const ctxProps = vnode.props ||
|
|
2992
2268
|
{};
|
|
2993
2269
|
const ctxId = ctxProps._contextId;
|
|
2994
2270
|
const prevCtx = state.ssrContexts[ctxId];
|
|
@@ -3009,29 +2285,23 @@ const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
|
3009
2285
|
}
|
|
3010
2286
|
return;
|
|
3011
2287
|
}
|
|
3012
|
-
// Handle Suspense specifically
|
|
3013
2288
|
const suspenseType = vnode.type;
|
|
3014
2289
|
const isSuspenseBoundary = vnode.type === RYUNIX_TYPES.RYUNIX_SUSPENSE ||
|
|
3015
2290
|
(typeof suspenseType === 'object' &&
|
|
3016
2291
|
suspenseType != null &&
|
|
3017
|
-
|
|
2292
|
+
suspenseType.type ===
|
|
3018
2293
|
RYUNIX_TYPES.RYUNIX_SUSPENSE);
|
|
3019
2294
|
if (isSuspenseBoundary) {
|
|
3020
|
-
const suspenseProps =
|
|
3021
|
-
/** @type {{ fallback?: RyunixNode, children?: RyunixNode | RyunixNode[] }} */ vnode.props ||
|
|
2295
|
+
const suspenseProps = vnode.props ||
|
|
3022
2296
|
{};
|
|
3023
2297
|
const { fallback, children } = suspenseProps;
|
|
3024
2298
|
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
2299
|
push(`<!--$?--><template id="B:${id}"></template><div id="S:${id}">`);
|
|
3028
|
-
// 1. Start rendering the actual content in the background
|
|
3029
2300
|
const task = (async () => {
|
|
3030
2301
|
const state = getState();
|
|
3031
2302
|
const wasBackground = state.isSuspenseBackground;
|
|
3032
2303
|
state.isSuspenseBackground = true;
|
|
3033
2304
|
let content = '';
|
|
3034
|
-
/** @param {string} chunk */
|
|
3035
2305
|
const subPush = (chunk) => {
|
|
3036
2306
|
content += chunk;
|
|
3037
2307
|
};
|
|
@@ -3047,7 +2317,6 @@ const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
|
3047
2317
|
}
|
|
3048
2318
|
})();
|
|
3049
2319
|
suspenseTasks.push(task);
|
|
3050
|
-
// 2. Render fallback immediately for the main stream
|
|
3051
2320
|
await renderToStreamImpl(fallback, push, suspenseTasks);
|
|
3052
2321
|
push(`</div><!--$/-->`);
|
|
3053
2322
|
return;
|
|
@@ -3058,11 +2327,10 @@ const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
|
3058
2327
|
if (process.env.RYUNIX_DEBUG) {
|
|
3059
2328
|
console.log('[SSR Debug] Rendering function:', type.name || 'anonymous');
|
|
3060
2329
|
}
|
|
3061
|
-
const renderedElement = await
|
|
2330
|
+
const renderedElement = await type(props);
|
|
3062
2331
|
await renderToStreamImpl(renderedElement, push, suspenseTasks);
|
|
3063
2332
|
return;
|
|
3064
2333
|
}
|
|
3065
|
-
// It's a standard host element
|
|
3066
2334
|
const hostTag = String(type);
|
|
3067
2335
|
let attributes = '';
|
|
3068
2336
|
let innerHTML = null;
|
|
@@ -3076,8 +2344,7 @@ const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
|
3076
2344
|
}
|
|
3077
2345
|
}
|
|
3078
2346
|
else if (key === STRINGS.STYLE || key === OLD_STRINGS.STYLE) {
|
|
3079
|
-
const styleString = renderStyle(
|
|
3080
|
-
/** @type {Record<string, unknown>} */ value);
|
|
2347
|
+
const styleString = renderStyle(value);
|
|
3081
2348
|
if (styleString) {
|
|
3082
2349
|
attributes += ` style="${escapeHtml(styleString)}"`;
|
|
3083
2350
|
}
|
|
@@ -3088,6 +2355,8 @@ const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
|
3088
2355
|
}
|
|
3089
2356
|
}
|
|
3090
2357
|
else if (!key.startsWith('on') &&
|
|
2358
|
+
key !== 'key' &&
|
|
2359
|
+
key !== 'ref' &&
|
|
3091
2360
|
key !== '__source' &&
|
|
3092
2361
|
key !== '__self') {
|
|
3093
2362
|
if (typeof value === 'boolean') {
|
|
@@ -3119,34 +2388,21 @@ const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
|
3119
2388
|
push(`</${hostTag}>`);
|
|
3120
2389
|
}
|
|
3121
2390
|
};
|
|
3122
|
-
/**
|
|
3123
|
-
* @param {RyunixNode} element
|
|
3124
|
-
* @param {RyunixRenderToStringOptions} [options]
|
|
3125
|
-
* @returns {ReadableStream<Uint8Array>}
|
|
3126
|
-
*/
|
|
3127
2391
|
const renderToReadableStream = (element, options = {}) => {
|
|
3128
2392
|
const state = getState();
|
|
3129
2393
|
const encoder = new TextEncoder();
|
|
3130
|
-
// Reset idCounter for deterministic useId values
|
|
3131
2394
|
resetIdCounter();
|
|
3132
2395
|
return new ReadableStream({
|
|
3133
2396
|
async start(controller) {
|
|
3134
2397
|
const wasServerRendering = state.isServerRendering;
|
|
3135
2398
|
state.isServerRendering = true;
|
|
3136
2399
|
state.ssrMetadata = {};
|
|
3137
|
-
/** @param {string} text */
|
|
3138
2400
|
const push = (text) => controller.enqueue(encoder.encode(text));
|
|
3139
|
-
/** @type {RyunixSuspenseTask[]} */
|
|
3140
2401
|
const suspenseTasks = [];
|
|
3141
2402
|
try {
|
|
3142
|
-
// 0. Inject RC helper script first
|
|
3143
2403
|
const nonceAttr = options.nonce ? ` nonce="${options.nonce}"` : '';
|
|
3144
2404
|
push(`<script${nonceAttr} data-ryunix-ssr>${RC_SCRIPT}</script>`);
|
|
3145
|
-
// 1. Render initial tree (with fallbacks)
|
|
3146
2405
|
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
2406
|
while (suspenseTasks.length > 0) {
|
|
3151
2407
|
const task = suspenseTasks.shift();
|
|
3152
2408
|
const res = await task;
|
|
@@ -3166,17 +2422,11 @@ const renderToReadableStream = (element, options = {}) => {
|
|
|
3166
2422
|
},
|
|
3167
2423
|
});
|
|
3168
2424
|
};
|
|
3169
|
-
/**
|
|
3170
|
-
* @param {RyunixNode} element
|
|
3171
|
-
* @param {RyunixRenderToStringOptions} [options]
|
|
3172
|
-
* @returns {string}
|
|
3173
|
-
*/
|
|
3174
2425
|
const renderToString = (element, options = {}) => {
|
|
3175
2426
|
const state = getState();
|
|
3176
2427
|
const wasServerRendering = state.isServerRendering;
|
|
3177
2428
|
state.isServerRendering = true;
|
|
3178
2429
|
state.ssrMetadata = {};
|
|
3179
|
-
// Reset idCounter for deterministic useId values
|
|
3180
2430
|
resetIdCounter();
|
|
3181
2431
|
try {
|
|
3182
2432
|
return renderToStringImpl(element);
|
|
@@ -3185,11 +2435,6 @@ const renderToString = (element, options = {}) => {
|
|
|
3185
2435
|
state.isServerRendering = wasServerRendering;
|
|
3186
2436
|
}
|
|
3187
2437
|
};
|
|
3188
|
-
/**
|
|
3189
|
-
* @param {RyunixNode} element
|
|
3190
|
-
* @param {RyunixRenderToStringOptions} [options]
|
|
3191
|
-
* @returns {Promise<string>}
|
|
3192
|
-
*/
|
|
3193
2438
|
const renderToStringAsync = async (element, options = {}) => {
|
|
3194
2439
|
const stream = renderToReadableStream(element, options);
|
|
3195
2440
|
const reader = stream.getReader();
|
|
@@ -3236,6 +2481,21 @@ function deepEqual(a, b) {
|
|
|
3236
2481
|
return keysA.every((key) => deepEqual(a[key], b[key]));
|
|
3237
2482
|
}
|
|
3238
2483
|
|
|
2484
|
+
function forwardRef(render) {
|
|
2485
|
+
if (typeof render !== 'function') {
|
|
2486
|
+
throw new Error('forwardRef requires a render function');
|
|
2487
|
+
}
|
|
2488
|
+
const ForwardRefComponent = ((props) => {
|
|
2489
|
+
const { ref, ...restProps } = props || {};
|
|
2490
|
+
return render(restProps, ref ?? null);
|
|
2491
|
+
});
|
|
2492
|
+
const named = render;
|
|
2493
|
+
ForwardRefComponent.displayName = `ForwardRef(${named.displayName || named.name || 'Component'})`;
|
|
2494
|
+
ForwardRefComponent._isForwardRef = true;
|
|
2495
|
+
ForwardRefComponent._render = render;
|
|
2496
|
+
return ForwardRefComponent;
|
|
2497
|
+
}
|
|
2498
|
+
|
|
3239
2499
|
const SUSPENSE_STATUS = {
|
|
3240
2500
|
PENDING: 'pending',
|
|
3241
2501
|
RESOLVED: 'resolved',
|
|
@@ -3328,6 +2588,45 @@ function preload(importFn) {
|
|
|
3328
2588
|
return importFn();
|
|
3329
2589
|
}
|
|
3330
2590
|
|
|
2591
|
+
var index = /*#__PURE__*/Object.freeze({
|
|
2592
|
+
__proto__: null,
|
|
2593
|
+
Children: Children,
|
|
2594
|
+
Link: Link,
|
|
2595
|
+
NavLink: NavLink,
|
|
2596
|
+
RouterProvider: RouterProvider,
|
|
2597
|
+
Suspense: Suspense,
|
|
2598
|
+
createContext: createContext,
|
|
2599
|
+
deepEqual: deepEqual,
|
|
2600
|
+
forwardRef: forwardRef,
|
|
2601
|
+
lazy: lazy,
|
|
2602
|
+
memo: memo,
|
|
2603
|
+
preload: preload,
|
|
2604
|
+
resetIdCounter: resetIdCounter,
|
|
2605
|
+
shallowEqual: shallowEqual,
|
|
2606
|
+
useCallback: useCallback,
|
|
2607
|
+
useDebounce: useDebounce,
|
|
2608
|
+
useDeferredValue: useDeferredValue,
|
|
2609
|
+
useEffect: useEffect,
|
|
2610
|
+
useHash: useHash,
|
|
2611
|
+
useId: useId,
|
|
2612
|
+
useLayoutEffect: useLayoutEffect,
|
|
2613
|
+
useMemo: useMemo,
|
|
2614
|
+
useMetadata: useMetadata,
|
|
2615
|
+
usePathname: usePathname,
|
|
2616
|
+
usePersistentStore: usePersistentStore,
|
|
2617
|
+
usePersitentStore: usePersistentStore,
|
|
2618
|
+
useQuery: useQuery,
|
|
2619
|
+
useReducer: useReducer,
|
|
2620
|
+
useRef: useRef,
|
|
2621
|
+
useRouter: useRouter,
|
|
2622
|
+
useSearchParams: useSearchParams,
|
|
2623
|
+
useStore: useStore,
|
|
2624
|
+
useStorePriority: useStorePriority,
|
|
2625
|
+
useSwitch: useSwitch,
|
|
2626
|
+
useThrottle: useThrottle,
|
|
2627
|
+
useTransition: useTransition
|
|
2628
|
+
});
|
|
2629
|
+
|
|
3331
2630
|
const perfNow = () => typeof performance !== 'undefined' ? performance.now() : Date.now();
|
|
3332
2631
|
class Profiler {
|
|
3333
2632
|
enabled;
|
|
@@ -3449,43 +2748,10 @@ function withProfiler(Component, name) {
|
|
|
3449
2748
|
return Profiled;
|
|
3450
2749
|
}
|
|
3451
2750
|
|
|
3452
|
-
function forwardRef(render) {
|
|
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
2751
|
function ServerBoundary({ children, id }) {
|
|
3477
2752
|
return createElement('div', { 'data-ryunix-server': id, style: { display: 'contents' } }, children);
|
|
3478
2753
|
}
|
|
3479
2754
|
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
2755
|
function HydrationBoundary({ children, id }) {
|
|
3490
2756
|
return createElement('div', {
|
|
3491
2757
|
'data-ryunix-hydrate-boundary': id ?? '',
|
|
@@ -3510,11 +2776,6 @@ function ErrorBoundary({ children, fallback, }) {
|
|
|
3510
2776
|
ErrorBoundary.ryunix_type =
|
|
3511
2777
|
'RYUNIX_ERROR_BOUNDARY';
|
|
3512
2778
|
|
|
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
2779
|
function createActionProxy(actionId) {
|
|
3519
2780
|
return async function (...args) {
|
|
3520
2781
|
const response = await fetch('/_ryunix/action', {
|
|
@@ -3533,80 +2794,66 @@ function createActionProxy(actionId) {
|
|
|
3533
2794
|
};
|
|
3534
2795
|
}
|
|
3535
2796
|
|
|
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
2797
|
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
|
|
2798
|
+
const propsInput = propsOrError;
|
|
3551
2799
|
const rawError = propsInput &&
|
|
3552
2800
|
typeof propsInput === 'object' &&
|
|
3553
2801
|
!(propsInput instanceof Error) &&
|
|
3554
2802
|
propsInput.nativeEvent
|
|
3555
2803
|
? propsInput.error
|
|
3556
2804
|
: propsInput;
|
|
3557
|
-
/** @type {OverlayError | null} */
|
|
3558
2805
|
let error = null;
|
|
3559
2806
|
if (rawError instanceof Error) {
|
|
3560
2807
|
error = rawError;
|
|
3561
2808
|
}
|
|
3562
2809
|
else if (rawError && typeof rawError === 'object') {
|
|
3563
2810
|
if ('message' in rawError) {
|
|
3564
|
-
error =
|
|
2811
|
+
error = rawError;
|
|
3565
2812
|
}
|
|
3566
2813
|
else if ('error' in rawError) {
|
|
3567
|
-
const nested =
|
|
2814
|
+
const nested = rawError.error;
|
|
3568
2815
|
error =
|
|
3569
2816
|
nested && typeof nested === 'object'
|
|
3570
|
-
?
|
|
2817
|
+
? nested
|
|
3571
2818
|
: null;
|
|
3572
2819
|
}
|
|
3573
2820
|
else {
|
|
3574
|
-
error =
|
|
2821
|
+
error = rawError;
|
|
3575
2822
|
}
|
|
3576
2823
|
}
|
|
3577
|
-
// Debug string if error is broken
|
|
3578
2824
|
const debugObjectStr = JSON.stringify(propsInput, Object.getOwnPropertyNames(propsInput || {}));
|
|
3579
2825
|
const [snippetState, setSnippet] = useStore(null);
|
|
3580
2826
|
const [startLineState, setStartLine] = useStore(1);
|
|
3581
2827
|
const [errorFileState, setErrorFile] = useStore('');
|
|
3582
2828
|
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[]} */
|
|
2829
|
+
const snippet = snippetState;
|
|
2830
|
+
const startLine = startLineState;
|
|
2831
|
+
const errorFile = errorFileState;
|
|
2832
|
+
const errorLine = errorLineState;
|
|
3589
2833
|
let stackLines = [];
|
|
3590
2834
|
if (error && error.stack) {
|
|
3591
2835
|
stackLines =
|
|
3592
2836
|
typeof error.stack === 'string'
|
|
3593
|
-
? error.stack.split('\n').filter((
|
|
2837
|
+
? error.stack.split('\n').filter((line) => {
|
|
3594
2838
|
const trimmed = line.trim();
|
|
3595
2839
|
if (!trimmed)
|
|
3596
2840
|
return false;
|
|
3597
2841
|
if (trimmed.includes('node_modules'))
|
|
3598
2842
|
return false;
|
|
3599
|
-
// Filter out internal Ryunix core framework files to isolate user code
|
|
3600
2843
|
const isInternal = [
|
|
3601
|
-
'
|
|
2844
|
+
'fiber-update.js',
|
|
3602
2845
|
'workers.js',
|
|
3603
2846
|
'reconciler.js',
|
|
3604
2847
|
'commits.js',
|
|
2848
|
+
'hooks/hooks.js',
|
|
3605
2849
|
'hooks.js',
|
|
2850
|
+
'error-boundary.js',
|
|
3606
2851
|
'errorBoundary.js',
|
|
2852
|
+
'boundaries.js',
|
|
3607
2853
|
'serverBoundary.js',
|
|
3608
2854
|
'app-router.js',
|
|
3609
2855
|
'app-router-server.js',
|
|
2856
|
+
'render/render.js',
|
|
3610
2857
|
'render.js',
|
|
3611
2858
|
'createElement.js',
|
|
3612
2859
|
'index.js',
|
|
@@ -3614,7 +2861,7 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3614
2861
|
return !isInternal;
|
|
3615
2862
|
})
|
|
3616
2863
|
: Array.isArray(error.stack)
|
|
3617
|
-
?
|
|
2864
|
+
? error.stack
|
|
3618
2865
|
: [];
|
|
3619
2866
|
}
|
|
3620
2867
|
const errorName = error && typeof error.name === 'string' ? error.name : 'Unknown Error Type';
|
|
@@ -3624,22 +2871,18 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3624
2871
|
useEffect(() => {
|
|
3625
2872
|
let targetPath = null;
|
|
3626
2873
|
let targetLine = null;
|
|
3627
|
-
// 1. Direct JSX __source mapping (injected by Webpack/SWC)
|
|
3628
2874
|
const ryunixSource = error?.__ryunix_source;
|
|
3629
2875
|
if (ryunixSource?.fileName) {
|
|
3630
2876
|
targetPath = ryunixSource.fileName;
|
|
3631
2877
|
targetLine = ryunixSource.lineNumber ?? null;
|
|
3632
2878
|
}
|
|
3633
|
-
// 2. Fallback to Regex Stack Parsing
|
|
3634
2879
|
if (!targetPath || !targetLine) {
|
|
3635
2880
|
for (let i = 0; i < stackLines.length; i++) {
|
|
3636
2881
|
const line = stackLines[i];
|
|
3637
2882
|
if (!line.includes(':'))
|
|
3638
2883
|
continue;
|
|
3639
|
-
// Deterministic string-based parsing (no regex on uncontrolled data)
|
|
3640
2884
|
let matchedPath = null;
|
|
3641
2885
|
let matchedLine = null;
|
|
3642
|
-
// V8 format: "at fn (file:line:col)" — extract content between parens
|
|
3643
2886
|
const parenOpen = line.indexOf('(');
|
|
3644
2887
|
const parenClose = line.lastIndexOf(')');
|
|
3645
2888
|
if (parenOpen !== -1 && parenClose > parenOpen) {
|
|
@@ -3655,7 +2898,6 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3655
2898
|
}
|
|
3656
2899
|
}
|
|
3657
2900
|
}
|
|
3658
|
-
// V8 format without parens: "at file:line:col"
|
|
3659
2901
|
if (!matchedPath) {
|
|
3660
2902
|
const trimmed = line.trim();
|
|
3661
2903
|
if (trimmed.startsWith('at ')) {
|
|
@@ -3672,7 +2914,6 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3672
2914
|
}
|
|
3673
2915
|
}
|
|
3674
2916
|
}
|
|
3675
|
-
// Ryunix format: "file.ryx:line" or "file.jsx:line"
|
|
3676
2917
|
if (!matchedPath) {
|
|
3677
2918
|
const exts = ['.ryx', '.jsx', '.js', '.ts', '.tsx'];
|
|
3678
2919
|
const c1 = line.lastIndexOf(':');
|
|
@@ -3780,7 +3021,6 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3780
3021
|
maxHeight: '150px',
|
|
3781
3022
|
height: 'auto',
|
|
3782
3023
|
};
|
|
3783
|
-
/** @param {boolean} isErrorLine */
|
|
3784
3024
|
const lineStyle = (isErrorLine) => ({
|
|
3785
3025
|
display: 'flex',
|
|
3786
3026
|
backgroundColor: isErrorLine ? 'rgba(239, 68, 68, 0.15)' : 'transparent',
|
|
@@ -3838,9 +3078,7 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3838
3078
|
}, createElement('path', {
|
|
3839
3079
|
d: 'M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z',
|
|
3840
3080
|
}), 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) => {
|
|
3081
|
+
createElement('div', { style: snippetContainerStyle }, createElement('div', { style: { display: 'flex', flexDirection: 'column' } }, ...snippetLines.map((lineText, index) => {
|
|
3844
3082
|
const currentLineNumber = startLine + index;
|
|
3845
3083
|
const isErrorLine = currentLineNumber === errorLine;
|
|
3846
3084
|
return createElement('div', { key: index, style: lineStyle(isErrorLine) }, createElement('span', {
|
|
@@ -3877,16 +3115,14 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3877
3115
|
flexDirection: 'column',
|
|
3878
3116
|
gap: '12px',
|
|
3879
3117
|
},
|
|
3880
|
-
}, ...stackLines.map((
|
|
3118
|
+
}, ...stackLines.map((line, i) => {
|
|
3881
3119
|
if (i === 0 &&
|
|
3882
3120
|
(line.startsWith('Error:') ||
|
|
3883
3121
|
line.startsWith('TypeError:')))
|
|
3884
3122
|
return null;
|
|
3885
|
-
// Deterministic string-based stack frame parsing (no polynomial regex)
|
|
3886
3123
|
const trimmed = line.trim();
|
|
3887
3124
|
let fnName = '<anonymous>';
|
|
3888
3125
|
let filePath = line;
|
|
3889
|
-
// V8 format: "at fnName (file:line:col)" or "at file:line:col"
|
|
3890
3126
|
if (trimmed.startsWith('at ')) {
|
|
3891
3127
|
const rest = trimmed.slice(3);
|
|
3892
3128
|
const parenOpen = rest.indexOf('(');
|
|
@@ -3897,7 +3133,6 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3897
3133
|
filePath = rest.slice(parenOpen + 1, parenClose);
|
|
3898
3134
|
}
|
|
3899
3135
|
else {
|
|
3900
|
-
// "at file:line:col" — no function name
|
|
3901
3136
|
if (rest.includes(':')) {
|
|
3902
3137
|
fnName = '<anonymous>';
|
|
3903
3138
|
}
|
|
@@ -3907,13 +3142,11 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3907
3142
|
filePath = rest;
|
|
3908
3143
|
}
|
|
3909
3144
|
}
|
|
3910
|
-
// Firefox format: "fnName@file:line:col"
|
|
3911
3145
|
else if (trimmed.includes('@')) {
|
|
3912
3146
|
const atIdx = trimmed.indexOf('@');
|
|
3913
3147
|
fnName = trimmed.slice(0, atIdx) || '<anonymous>';
|
|
3914
3148
|
filePath = trimmed.slice(atIdx + 1);
|
|
3915
3149
|
}
|
|
3916
|
-
// Ryunix format: "fnName file.ext:line"
|
|
3917
3150
|
else {
|
|
3918
3151
|
const exts = ['.ryx', '.jsx', '.js', '.ts', '.tsx'];
|
|
3919
3152
|
const parts = trimmed.split(/\s+/);
|
|
@@ -3960,37 +3193,290 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3960
3193
|
: createElement('div', { style: { color: '#6b7280', fontStyle: 'italic' } }, 'No stack trace available.'))))));
|
|
3961
3194
|
}
|
|
3962
3195
|
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3196
|
+
const DEFAULT_THEME_COOKIE_NAME = 'ryunix_theme';
|
|
3197
|
+
const THEME_PREFERENCES = ['light', 'system', 'dark'];
|
|
3198
|
+
function createThemeController(options = {}) {
|
|
3199
|
+
const cookieName = options.cookieName ?? DEFAULT_THEME_COOKIE_NAME;
|
|
3200
|
+
const defaultTheme = options.defaultTheme ?? 'dark';
|
|
3201
|
+
const darkClass = options.darkClass ?? 'dark';
|
|
3202
|
+
const maxAgeSeconds = options.maxAgeSeconds ?? 365 * 24 * 60 * 60;
|
|
3203
|
+
const isThemePreference = (theme) => THEME_PREFERENCES.includes(theme);
|
|
3204
|
+
const getThemeCookie = () => {
|
|
3205
|
+
if (typeof document === 'undefined')
|
|
3206
|
+
return null;
|
|
3207
|
+
const match = document.cookie.match(new RegExp(`(?:^|;\\s*)${cookieName}=(light|system|dark)(?:;|$)`));
|
|
3208
|
+
return match ? match[1] : null;
|
|
3209
|
+
};
|
|
3210
|
+
const setThemeCookie = (theme) => {
|
|
3211
|
+
if (typeof document === 'undefined' || !isThemePreference(theme))
|
|
3212
|
+
return;
|
|
3213
|
+
document.cookie = `${cookieName}=${theme}; path=/; max-age=${maxAgeSeconds}; SameSite=Lax`;
|
|
3214
|
+
};
|
|
3215
|
+
const resolveThemeFromCookie = () => getThemeCookie() || defaultTheme;
|
|
3216
|
+
const getSystemColorScheme = () => {
|
|
3217
|
+
if (typeof window === 'undefined')
|
|
3218
|
+
return 'dark';
|
|
3219
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
3220
|
+
? 'dark'
|
|
3221
|
+
: 'light';
|
|
3222
|
+
};
|
|
3223
|
+
const resolveEffectiveTheme = (theme) => {
|
|
3224
|
+
const choice = isThemePreference(theme) ? theme : defaultTheme;
|
|
3225
|
+
if (choice === 'system')
|
|
3226
|
+
return getSystemColorScheme();
|
|
3227
|
+
return choice;
|
|
3228
|
+
};
|
|
3229
|
+
const applyTheme = (theme) => {
|
|
3230
|
+
if (typeof document === 'undefined')
|
|
3231
|
+
return;
|
|
3232
|
+
const preference = isThemePreference(theme) ? theme : defaultTheme;
|
|
3233
|
+
const effective = resolveEffectiveTheme(preference);
|
|
3234
|
+
const root = document.documentElement;
|
|
3235
|
+
root.classList.toggle(darkClass, effective === 'dark');
|
|
3236
|
+
root.dataset.theme = preference;
|
|
3237
|
+
root.dataset.themeEffective = effective;
|
|
3238
|
+
root.style.colorScheme = effective === 'dark' ? 'dark' : 'light';
|
|
3239
|
+
};
|
|
3240
|
+
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}');}})();`;
|
|
3241
|
+
const watchSystemTheme = (onChange) => {
|
|
3242
|
+
if (typeof window === 'undefined')
|
|
3243
|
+
return () => { };
|
|
3244
|
+
const media = window.matchMedia('(prefers-color-scheme: dark)');
|
|
3245
|
+
const handler = () => onChange(getSystemColorScheme());
|
|
3246
|
+
media.addEventListener('change', handler);
|
|
3247
|
+
return () => media.removeEventListener('change', handler);
|
|
3248
|
+
};
|
|
3249
|
+
return {
|
|
3250
|
+
cookieName,
|
|
3251
|
+
defaultTheme,
|
|
3252
|
+
themes: THEME_PREFERENCES,
|
|
3253
|
+
getThemeCookie,
|
|
3254
|
+
setThemeCookie,
|
|
3255
|
+
resolveThemeFromCookie,
|
|
3256
|
+
getSystemColorScheme,
|
|
3257
|
+
resolveEffectiveTheme,
|
|
3258
|
+
applyTheme,
|
|
3259
|
+
getInitScript,
|
|
3260
|
+
watchSystemTheme,
|
|
3261
|
+
};
|
|
3262
|
+
}
|
|
3263
|
+
const themeController = createThemeController();
|
|
3264
|
+
const { cookieName: THEME_COOKIE_NAME, defaultTheme, themes, getThemeCookie, setThemeCookie, resolveThemeFromCookie, getSystemColorScheme, resolveEffectiveTheme, applyTheme, watchSystemTheme, } = themeController;
|
|
3265
|
+
const themeInitScript = themeController.getInitScript();
|
|
3266
|
+
|
|
3267
|
+
const SunIcon = ({ className = '' }) => createElement('svg', {
|
|
3268
|
+
xmlns: 'http://www.w3.org/2000/svg',
|
|
3269
|
+
viewBox: '0 0 24 24',
|
|
3270
|
+
fill: 'none',
|
|
3271
|
+
stroke: 'currentColor',
|
|
3272
|
+
strokeWidth: 1.75,
|
|
3273
|
+
strokeLinecap: 'round',
|
|
3274
|
+
strokeLinejoin: 'round',
|
|
3275
|
+
className,
|
|
3276
|
+
'aria-hidden': true,
|
|
3277
|
+
}, createElement('circle', { cx: 12, cy: 12, r: 4 }), createElement('path', {
|
|
3278
|
+
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',
|
|
3279
|
+
}));
|
|
3280
|
+
const MonitorIcon = ({ className = '' }) => createElement('svg', {
|
|
3281
|
+
xmlns: 'http://www.w3.org/2000/svg',
|
|
3282
|
+
viewBox: '0 0 24 24',
|
|
3283
|
+
fill: 'none',
|
|
3284
|
+
stroke: 'currentColor',
|
|
3285
|
+
strokeWidth: 1.75,
|
|
3286
|
+
strokeLinecap: 'round',
|
|
3287
|
+
strokeLinejoin: 'round',
|
|
3288
|
+
className,
|
|
3289
|
+
'aria-hidden': true,
|
|
3290
|
+
}, createElement('rect', { x: 2, y: 3, width: 20, height: 14, rx: 2 }), createElement('path', { d: 'M8 21h8M12 17v4' }));
|
|
3291
|
+
const MoonIcon = ({ className = '' }) => createElement('svg', {
|
|
3292
|
+
xmlns: 'http://www.w3.org/2000/svg',
|
|
3293
|
+
viewBox: '0 0 24 24',
|
|
3294
|
+
fill: 'none',
|
|
3295
|
+
stroke: 'currentColor',
|
|
3296
|
+
strokeWidth: 1.75,
|
|
3297
|
+
strokeLinecap: 'round',
|
|
3298
|
+
strokeLinejoin: 'round',
|
|
3299
|
+
className,
|
|
3300
|
+
'aria-hidden': true,
|
|
3301
|
+
}, createElement('path', {
|
|
3302
|
+
d: 'M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z',
|
|
3303
|
+
}));
|
|
3304
|
+
const ICONS = {
|
|
3305
|
+
light: SunIcon,
|
|
3306
|
+
system: MonitorIcon,
|
|
3307
|
+
dark: MoonIcon,
|
|
3308
|
+
};
|
|
3309
|
+
function ThemeToggle({ labels, className = '', controller = themeController, }) {
|
|
3310
|
+
const [theme, setTheme] = useStore(controller.defaultTheme);
|
|
3311
|
+
useEffect(() => {
|
|
3312
|
+
const saved = controller.resolveThemeFromCookie();
|
|
3313
|
+
setTheme(saved);
|
|
3314
|
+
controller.applyTheme(saved);
|
|
3315
|
+
}, []);
|
|
3316
|
+
useEffect(() => {
|
|
3317
|
+
if (theme !== 'system')
|
|
3318
|
+
return undefined;
|
|
3319
|
+
return controller.watchSystemTheme(() => controller.applyTheme('system'));
|
|
3320
|
+
}, [theme]);
|
|
3321
|
+
const selectTheme = (next) => {
|
|
3322
|
+
if (next === theme)
|
|
3323
|
+
return;
|
|
3324
|
+
setTheme(next);
|
|
3325
|
+
controller.setThemeCookie(next);
|
|
3326
|
+
controller.applyTheme(next);
|
|
3327
|
+
};
|
|
3328
|
+
return createElement('div', {
|
|
3329
|
+
className: `ryx-theme-segment ${className}`.trim(),
|
|
3330
|
+
role: 'radiogroup',
|
|
3331
|
+
'aria-label': labels.title,
|
|
3332
|
+
}, ...controller.themes.map((id) => {
|
|
3333
|
+
const active = theme === id;
|
|
3334
|
+
const Icon = ICONS[id];
|
|
3335
|
+
return createElement('button', {
|
|
3336
|
+
key: id,
|
|
3337
|
+
type: 'button',
|
|
3338
|
+
role: 'radio',
|
|
3339
|
+
'aria-checked': active ? 'true' : 'false',
|
|
3340
|
+
title: labels[id],
|
|
3341
|
+
className: `ryx-theme-segment-btn${active ? ' ryx-theme-segment-btn--active' : ''}`,
|
|
3342
|
+
onClick: () => selectTheme(id),
|
|
3343
|
+
}, Icon({ className: 'ryx-theme-segment-icon' }), createElement('span', { className: 'ryx-theme-segment-sr-only' }, labels[id]));
|
|
3344
|
+
}));
|
|
3345
|
+
}
|
|
3346
|
+
function ThemeInitScript({ controller = themeController, } = {}) {
|
|
3347
|
+
return createElement('script', {
|
|
3348
|
+
dangerouslySetInnerHTML: { __html: controller.getInitScript() },
|
|
3349
|
+
});
|
|
3350
|
+
}
|
|
3351
|
+
|
|
3352
|
+
const renderBrand = ({ image, title, href = '/', imageAlt, blockClass, linkClass, imageClass, titleClass, }) => {
|
|
3353
|
+
if (!image && !title)
|
|
3354
|
+
return null;
|
|
3355
|
+
const content = [
|
|
3356
|
+
image
|
|
3357
|
+
? createElement('img', {
|
|
3358
|
+
src: image,
|
|
3359
|
+
alt: imageAlt ?? title ?? '',
|
|
3360
|
+
className: imageClass,
|
|
3361
|
+
})
|
|
3362
|
+
: null,
|
|
3363
|
+
title ? createElement('span', { className: titleClass }, title) : null,
|
|
3364
|
+
].filter(Boolean);
|
|
3365
|
+
return createElement('div', { className: blockClass }, createElement('a', { href, className: linkClass }, ...content));
|
|
3366
|
+
};
|
|
3367
|
+
const renderChildren = (children) => {
|
|
3368
|
+
if (children == null || children === false)
|
|
3369
|
+
return [];
|
|
3370
|
+
return flattenArray(Array.isArray(children) ? children : [children]).filter((child) => child != null && child !== false);
|
|
3371
|
+
};
|
|
3372
|
+
const renderSlot = (className, children) => {
|
|
3373
|
+
const items = renderChildren(children);
|
|
3374
|
+
if (items.length === 0)
|
|
3375
|
+
return null;
|
|
3376
|
+
return createElement('div', { className }, ...items);
|
|
3377
|
+
};
|
|
3378
|
+
function Main({ maxWidth, className = '', innerClassName = '', children, }) {
|
|
3379
|
+
const innerStyle = maxWidth != null && maxWidth !== ''
|
|
3380
|
+
? { maxWidth, marginInline: 'auto', width: '100%' }
|
|
3381
|
+
: undefined;
|
|
3382
|
+
return createElement('main', {
|
|
3383
|
+
className: `ryx-main ${className}`.trim(),
|
|
3384
|
+
}, createElement('div', {
|
|
3385
|
+
className: `ryx-main-inner ${innerClassName}`.trim(),
|
|
3386
|
+
style: innerStyle,
|
|
3387
|
+
}, ...renderChildren(children)));
|
|
3388
|
+
}
|
|
3389
|
+
function Header({ image, title, href = '/', imageAlt, sticky = true, className = '', children, }) {
|
|
3390
|
+
const headerClass = [
|
|
3391
|
+
'ryx-header',
|
|
3392
|
+
sticky !== false ? 'ryx-header--sticky' : '',
|
|
3393
|
+
className,
|
|
3394
|
+
]
|
|
3395
|
+
.filter(Boolean)
|
|
3396
|
+
.join(' ');
|
|
3397
|
+
return createElement('header', {
|
|
3398
|
+
className: headerClass,
|
|
3399
|
+
}, createElement('div', { className: 'ryx-header-inner' }, createElement('div', { className: 'ryx-header-start' }, renderBrand({
|
|
3400
|
+
image,
|
|
3401
|
+
title,
|
|
3402
|
+
href,
|
|
3403
|
+
imageAlt,
|
|
3404
|
+
blockClass: 'ryx-header-brand',
|
|
3405
|
+
linkClass: 'ryx-header-brand-link',
|
|
3406
|
+
imageClass: 'ryx-header-brand-image',
|
|
3407
|
+
titleClass: 'ryx-header-brand-title',
|
|
3408
|
+
})), renderSlot('ryx-header-end', children)));
|
|
3409
|
+
}
|
|
3410
|
+
function Footer({ image, title, description, href = '/', imageAlt, className = '', children, bottomStart, bottomEnd, }) {
|
|
3411
|
+
const brand = renderBrand({
|
|
3412
|
+
image,
|
|
3413
|
+
title,
|
|
3414
|
+
href,
|
|
3415
|
+
imageAlt,
|
|
3416
|
+
blockClass: 'ryx-footer-brand',
|
|
3417
|
+
linkClass: 'ryx-footer-brand-link',
|
|
3418
|
+
imageClass: 'ryx-footer-brand-image',
|
|
3419
|
+
titleClass: 'ryx-footer-brand-title',
|
|
3420
|
+
});
|
|
3421
|
+
const brandColumn = brand || description
|
|
3422
|
+
? createElement('div', { className: 'ryx-footer-brand-column' }, brand, description
|
|
3423
|
+
? createElement('p', { className: 'ryx-footer-description' }, description)
|
|
3424
|
+
: null)
|
|
3425
|
+
: null;
|
|
3426
|
+
const columns = renderChildren(children).map((child, index) => createElement('div', { key: index, className: 'ryx-footer-column' }, child));
|
|
3427
|
+
const bottomStartSlot = renderSlot('ryx-footer-bottom-start', bottomStart);
|
|
3428
|
+
const bottomEndSlot = renderSlot('ryx-footer-bottom-end', bottomEnd);
|
|
3429
|
+
const bottomBar = bottomStartSlot || bottomEndSlot
|
|
3430
|
+
? createElement('div', { className: 'ryx-footer-bottom' }, bottomStartSlot, bottomEndSlot)
|
|
3431
|
+
: null;
|
|
3432
|
+
return createElement('footer', {
|
|
3433
|
+
className: `ryx-footer ${className}`.trim(),
|
|
3434
|
+
}, createElement('div', {
|
|
3435
|
+
className: 'ryx-footer-accent',
|
|
3436
|
+
'aria-hidden': 'true',
|
|
3437
|
+
}), createElement('div', {
|
|
3438
|
+
className: 'ryx-footer-glow',
|
|
3439
|
+
'aria-hidden': 'true',
|
|
3440
|
+
}), createElement('div', { className: 'ryx-footer-inner' }, brandColumn || columns.length > 0
|
|
3441
|
+
? createElement('div', { className: 'ryx-footer-grid' }, brandColumn, ...columns)
|
|
3442
|
+
: null, bottomBar));
|
|
3443
|
+
}
|
|
3969
3444
|
|
|
3970
3445
|
var Ryunix = /*#__PURE__*/Object.freeze({
|
|
3971
3446
|
__proto__: null,
|
|
3972
3447
|
Children: Children,
|
|
3448
|
+
DEFAULT_THEME_COOKIE_NAME: DEFAULT_THEME_COOKIE_NAME,
|
|
3973
3449
|
ErrorBoundary: ErrorBoundary,
|
|
3450
|
+
Footer: Footer,
|
|
3974
3451
|
Fragment: Fragment,
|
|
3975
|
-
|
|
3452
|
+
Header: Header,
|
|
3453
|
+
Hooks: index,
|
|
3976
3454
|
HydrationBoundary: HydrationBoundary,
|
|
3977
3455
|
Link: Link,
|
|
3456
|
+
Main: Main,
|
|
3978
3457
|
NavLink: NavLink,
|
|
3979
3458
|
Priority: Priority,
|
|
3980
3459
|
RouterProvider: RouterProvider,
|
|
3981
3460
|
RyunixDevOverlay: RyunixDevOverlay,
|
|
3982
3461
|
ServerBoundary: ServerBoundary,
|
|
3983
3462
|
Suspense: Suspense,
|
|
3463
|
+
THEME_PREFERENCES: THEME_PREFERENCES,
|
|
3464
|
+
ThemeInitScript: ThemeInitScript,
|
|
3465
|
+
ThemeToggle: ThemeToggle,
|
|
3466
|
+
applyTheme: applyTheme,
|
|
3984
3467
|
batchUpdates: batchUpdates,
|
|
3985
3468
|
cloneElement: cloneElement,
|
|
3986
3469
|
createActionProxy: createActionProxy,
|
|
3987
3470
|
createContext: createContext,
|
|
3988
3471
|
createElement: createElement,
|
|
3989
3472
|
createPortal: createPortal,
|
|
3473
|
+
createThemeController: createThemeController,
|
|
3990
3474
|
deepEqual: deepEqual,
|
|
3991
3475
|
escapeHtml: escapeHtml,
|
|
3992
3476
|
forwardRef: forwardRef,
|
|
3993
3477
|
getState: getState,
|
|
3478
|
+
getSystemColorScheme: getSystemColorScheme,
|
|
3479
|
+
getThemeCookie: getThemeCookie,
|
|
3994
3480
|
hydrate: hydrate,
|
|
3995
3481
|
init: init,
|
|
3996
3482
|
isValidElement: isValidElement,
|
|
@@ -4008,8 +3494,13 @@ var Ryunix = /*#__PURE__*/Object.freeze({
|
|
|
4008
3494
|
renderToString: renderToString,
|
|
4009
3495
|
renderToStringAsync: renderToStringAsync,
|
|
4010
3496
|
resetIdCounter: resetIdCounter,
|
|
3497
|
+
resolveEffectiveTheme: resolveEffectiveTheme,
|
|
3498
|
+
resolveThemeFromCookie: resolveThemeFromCookie,
|
|
4011
3499
|
safeRender: safeRender,
|
|
3500
|
+
setThemeCookie: setThemeCookie,
|
|
4012
3501
|
shallowEqual: shallowEqual,
|
|
3502
|
+
themeController: themeController,
|
|
3503
|
+
themeInitScript: themeInitScript,
|
|
4013
3504
|
useCallback: useCallback,
|
|
4014
3505
|
useDebounce: useDebounce,
|
|
4015
3506
|
useDeferredValue: useDeferredValue,
|
|
@@ -4033,10 +3524,79 @@ var Ryunix = /*#__PURE__*/Object.freeze({
|
|
|
4033
3524
|
useSwitch: useSwitch,
|
|
4034
3525
|
useThrottle: useThrottle,
|
|
4035
3526
|
useTransition: useTransition,
|
|
3527
|
+
watchSystemTheme: watchSystemTheme,
|
|
4036
3528
|
withProfiler: withProfiler
|
|
4037
3529
|
});
|
|
4038
3530
|
|
|
4039
|
-
|
|
3531
|
+
const { Provider: MDXProvider, useContext: useMDXComponents } = createContext('ryunix.mdx', {});
|
|
3532
|
+
const getMDXComponents = (components) => {
|
|
3533
|
+
const contextComponents = useMDXComponents();
|
|
3534
|
+
return {
|
|
3535
|
+
...contextComponents,
|
|
3536
|
+
...components,
|
|
3537
|
+
};
|
|
3538
|
+
};
|
|
3539
|
+
const RYUNIX_STYLE_ENABLED = globalThis.process && String(globalThis.process.env?.RYUNIX_STYLE) === 'true';
|
|
3540
|
+
const ryxProps = (props) => {
|
|
3541
|
+
const { unstyled, ...rest } = props;
|
|
3542
|
+
if (unstyled || rest['data-ryx-unstyled']) {
|
|
3543
|
+
return { ...rest, 'data-ryx-unstyled': true };
|
|
3544
|
+
}
|
|
3545
|
+
return rest;
|
|
3546
|
+
};
|
|
3547
|
+
const mergeClassName = (existing, base) => {
|
|
3548
|
+
if (!existing)
|
|
3549
|
+
return base;
|
|
3550
|
+
if (Array.isArray(existing))
|
|
3551
|
+
return [...existing, base].join(' ');
|
|
3552
|
+
return `${existing} ${base}`;
|
|
3553
|
+
};
|
|
3554
|
+
const styledMdxHost = (tag, ryxClass, props) => {
|
|
3555
|
+
const next = ryxProps(props);
|
|
3556
|
+
if (!RYUNIX_STYLE_ENABLED || !ryxClass || next['data-ryx-unstyled']) {
|
|
3557
|
+
return createElement(tag, next);
|
|
3558
|
+
}
|
|
3559
|
+
return createElement(tag, {
|
|
3560
|
+
...next,
|
|
3561
|
+
className: mergeClassName(next.className, ryxClass),
|
|
3562
|
+
});
|
|
3563
|
+
};
|
|
3564
|
+
const defaultComponents = {
|
|
3565
|
+
h1: (props) => styledMdxHost('h1', 'ryx-h1', props),
|
|
3566
|
+
h2: (props) => styledMdxHost('h2', 'ryx-h2', props),
|
|
3567
|
+
h3: (props) => styledMdxHost('h3', 'ryx-h3', props),
|
|
3568
|
+
h4: (props) => styledMdxHost('h4', 'ryx-h4', props),
|
|
3569
|
+
h5: (props) => styledMdxHost('h5', 'ryx-h5', props),
|
|
3570
|
+
h6: (props) => styledMdxHost('h6', 'ryx-h6', props),
|
|
3571
|
+
p: (props) => styledMdxHost('p', 'ryx-p', props),
|
|
3572
|
+
a: (props) => styledMdxHost('a', 'ryx-a', props),
|
|
3573
|
+
strong: (props) => styledMdxHost('strong', 'ryx-strong', props),
|
|
3574
|
+
em: (props) => styledMdxHost('em', 'ryx-em', props),
|
|
3575
|
+
code: (props) => styledMdxHost('code', 'ryx-code', props),
|
|
3576
|
+
ul: (props) => styledMdxHost('ul', 'ryx-ul', props),
|
|
3577
|
+
ol: (props) => styledMdxHost('ol', 'ryx-ol', props),
|
|
3578
|
+
li: (props) => styledMdxHost('li', 'ryx-li', props),
|
|
3579
|
+
blockquote: (props) => styledMdxHost('blockquote', 'ryx-blockquote', props),
|
|
3580
|
+
pre: (props) => styledMdxHost('pre', 'ryx-pre', props),
|
|
3581
|
+
hr: (props) => styledMdxHost('hr', 'ryx-hr', props),
|
|
3582
|
+
table: (props) => styledMdxHost('table', 'ryx-table', props),
|
|
3583
|
+
thead: (props) => styledMdxHost('thead', 'ryx-thead', props),
|
|
3584
|
+
tbody: (props) => styledMdxHost('tbody', 'ryx-tbody', props),
|
|
3585
|
+
tr: (props) => styledMdxHost('tr', 'ryx-tr', props),
|
|
3586
|
+
th: (props) => styledMdxHost('th', 'ryx-th', props),
|
|
3587
|
+
td: (props) => styledMdxHost('td', 'ryx-td', props),
|
|
3588
|
+
img: (props) => styledMdxHost('img', 'ryx-img', props),
|
|
3589
|
+
};
|
|
3590
|
+
const Image = ({ src, ...props }) => {
|
|
3591
|
+
return createElement('img', { ...props, src });
|
|
3592
|
+
};
|
|
3593
|
+
const MDXContent = ({ children, components = {} }) => {
|
|
3594
|
+
const mergedComponents = getMDXComponents(components);
|
|
3595
|
+
return createElement(MDXProvider, { value: mergedComponents }, createElement('div', null, children));
|
|
3596
|
+
};
|
|
3597
|
+
|
|
3598
|
+
if (typeof window !== 'undefined')
|
|
3599
|
+
window.Ryunix = Ryunix;
|
|
4040
3600
|
|
|
4041
|
-
export { Children, ErrorBoundary, Fragment,
|
|
3601
|
+
export { Children, DEFAULT_THEME_COOKIE_NAME, ErrorBoundary, Footer, Fragment, Header, index as Hooks, HydrationBoundary, Image, Link, MDXContent, MDXProvider, Main, NavLink, Priority, RouterProvider, RyunixDevOverlay, ServerBoundary, Suspense, THEME_PREFERENCES, ThemeInitScript, ThemeToggle, applyTheme, batchUpdates, cloneElement, createActionProxy, createContext, createElement, createPortal, createThemeController, deepEqual, Ryunix as default, defaultComponents, escapeHtml, forwardRef, getMDXComponents, getState, getSystemColorScheme, getThemeCookie, hydrate, init, isValidElement, lazy, logHydrationBoundaryMismatch, logHydrationBoundaryRecovery, logHydrationFatal, logHydrationInfo, logHydrationRecoverable, memo, preload, profiler, render, renderToReadableStream, renderToString, renderToStringAsync, resetIdCounter, resolveEffectiveTheme, resolveThemeFromCookie, ryxProps, safeRender, setThemeCookie, shallowEqual, themeController, themeInitScript, useCallback, useDebounce, useDeferredValue, useEffect, useHash, useId, useLayoutEffect, useMDXComponents, useMemo, useMetadata, usePathname, usePersistentStore, usePersistentStore as usePersitentStore, useProfiler, useQuery, useReducer, useRef, useRouter, useSearchParams, useStore, useStorePriority, useSwitch, useThrottle, useTransition, watchSystemTheme, withProfiler };
|
|
4042
3602
|
//# sourceMappingURL=Ryunix.esm.js.map
|