@unsetsoft/ryunixjs 1.2.5-canary.10 → 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 +1437 -1884
- package/dist/Ryunix.esm.js.map +1 -1
- package/dist/Ryunix.umd.js +1460 -1889
- 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,33 +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
|
-
if (state.hydrationFailed)
|
|
897
|
-
if (process.env.NODE_ENV !== 'production' && process.env.RYUNIX_DEBUG) {
|
|
898
|
-
console.log('[Ryunix Debug] Hydration failed. Clearing container.');
|
|
899
|
-
}
|
|
900
|
-
const container = state.containerRoot || finishedWork.dom;
|
|
901
|
-
if (container) {
|
|
902
|
-
container.textContent = '';
|
|
903
|
-
}
|
|
904
|
-
}
|
|
725
|
+
if (state.hydrationFailed) ;
|
|
905
726
|
else {
|
|
906
|
-
// If there is a cursor left, it means these are SSR nodes that weren't matched
|
|
907
|
-
// by any client fiber. We must remove them to avoid duplication.
|
|
908
727
|
let cursor = state.hydrateCursor;
|
|
909
728
|
let removed = 0;
|
|
910
729
|
if (cursor &&
|
|
@@ -927,23 +746,17 @@ function commitRoot() {
|
|
|
927
746
|
state.hydrateCursor = null;
|
|
928
747
|
}
|
|
929
748
|
commitWork(finishedWork.child);
|
|
930
|
-
// If wipRoot was not reassigned by a synchronous dispatch during effects, clear it
|
|
931
749
|
if (state.wipRoot === finishedWork) {
|
|
932
750
|
state.wipRoot = null;
|
|
933
751
|
}
|
|
934
752
|
}
|
|
935
|
-
/**
|
|
936
|
-
* @param {RyunixFiber | null | undefined} fiber
|
|
937
|
-
*/
|
|
938
753
|
function commitWork(fiber) {
|
|
939
754
|
if (!fiber) {
|
|
940
755
|
return;
|
|
941
756
|
}
|
|
942
|
-
// Handle portal fibers — they render into a different container
|
|
943
757
|
if (fiber.type === RYUNIX_PORTAL || fiber._isPortal) {
|
|
944
758
|
const portalContainer = fiber.containerInfo;
|
|
945
759
|
if (portalContainer) {
|
|
946
|
-
// Process portal children into the portal container
|
|
947
760
|
const portalFiber = fiber.child;
|
|
948
761
|
if (portalFiber) {
|
|
949
762
|
commitPortalWork(portalFiber, portalContainer);
|
|
@@ -967,9 +780,7 @@ function commitWork(fiber) {
|
|
|
967
780
|
}
|
|
968
781
|
domParent.appendChild(fiber.dom);
|
|
969
782
|
}
|
|
970
|
-
// Layout effects run synchronously during commit
|
|
971
783
|
runLayoutEffects(fiber);
|
|
972
|
-
// Normal effects run after paint
|
|
973
784
|
runNormalEffects(fiber);
|
|
974
785
|
}
|
|
975
786
|
else if (fiber.effectTag === EFFECT_TAGS.UPDATE) {
|
|
@@ -986,8 +797,6 @@ function commitWork(fiber) {
|
|
|
986
797
|
if (process.env.NODE_ENV !== 'production' && process.env.RYUNIX_DEBUG) {
|
|
987
798
|
console.log('[Ryunix Debug] Hydration fallback PLACEMENT:', fiber.type);
|
|
988
799
|
}
|
|
989
|
-
// Since container is cleared on fallback, treat as normal placement
|
|
990
|
-
// No need to check fiber.dom.parentNode !== domParent because the container was cleared.
|
|
991
800
|
if (fiber.dom != null) {
|
|
992
801
|
domParent.appendChild(fiber.dom);
|
|
993
802
|
}
|
|
@@ -1006,7 +815,6 @@ function commitWork(fiber) {
|
|
|
1006
815
|
}
|
|
1007
816
|
}
|
|
1008
817
|
else if (fiber.effectTag === EFFECT_TAGS.DELETION) {
|
|
1009
|
-
// Run cleanups BEFORE removing DOM to allow cleanup functions to read DOM state
|
|
1010
818
|
cancelEffectsDeep(fiber);
|
|
1011
819
|
commitDeletion(fiber);
|
|
1012
820
|
return;
|
|
@@ -1014,11 +822,6 @@ function commitWork(fiber) {
|
|
|
1014
822
|
commitWork(fiber.child);
|
|
1015
823
|
commitWork(fiber.sibling);
|
|
1016
824
|
}
|
|
1017
|
-
/**
|
|
1018
|
-
* Commit work for portal children into a specific container
|
|
1019
|
-
* @param {RyunixFiber | null | undefined} fiber
|
|
1020
|
-
* @param {Element | DocumentFragment} portalContainer
|
|
1021
|
-
*/
|
|
1022
825
|
const commitPortalWork = (fiber, portalContainer) => {
|
|
1023
826
|
if (!fiber)
|
|
1024
827
|
return;
|
|
@@ -1045,10 +848,6 @@ const commitPortalWork = (fiber, portalContainer) => {
|
|
|
1045
848
|
commitPortalWork(fiber.child, portalContainer);
|
|
1046
849
|
commitPortalWork(fiber.sibling, portalContainer);
|
|
1047
850
|
};
|
|
1048
|
-
/**
|
|
1049
|
-
* @param {RyunixFiber} fiber
|
|
1050
|
-
* @param {Node} domParent
|
|
1051
|
-
*/
|
|
1052
851
|
const commitDeletion = (fiber, domParent) => {
|
|
1053
852
|
if (fiber.dom) {
|
|
1054
853
|
if (fiber.dom.parentNode) {
|
|
@@ -1138,852 +937,635 @@ const reconcileChildren = (wipFiber, elements) => {
|
|
|
1138
937
|
});
|
|
1139
938
|
};
|
|
1140
939
|
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
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
|
+
};
|
|
1151
954
|
};
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
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;
|
|
1162
972
|
}
|
|
973
|
+
return null;
|
|
1163
974
|
};
|
|
1164
|
-
|
|
1165
|
-
let
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
try {
|
|
1171
|
-
callback();
|
|
1172
|
-
}
|
|
1173
|
-
finally {
|
|
1174
|
-
isBatching = wasBatching;
|
|
1175
|
-
if (!isBatching && pendingUpdates.length > 0) {
|
|
1176
|
-
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;
|
|
1177
981
|
}
|
|
982
|
+
current = current.parentNode;
|
|
1178
983
|
}
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
if (!
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
return;
|
|
1189
|
-
const updates = pendingUpdates;
|
|
1190
|
-
pendingUpdates = [];
|
|
1191
|
-
updates.forEach((update) => update());
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
/**
|
|
1195
|
-
* @typedef {import('../types/internal.js').RyunixComponent} RyunixComponent
|
|
1196
|
-
* @typedef {import('../types/internal.js').RyunixFiber} RyunixFiber
|
|
1197
|
-
*/
|
|
1198
|
-
/**
|
|
1199
|
-
* Development warnings
|
|
1200
|
-
*/
|
|
1201
|
-
process.env.NODE_ENV !== 'production';
|
|
1202
|
-
/**
|
|
1203
|
-
* Hook call validation
|
|
1204
|
-
*/
|
|
1205
|
-
const validateHookContext = (hookName = 'A hook') => {
|
|
1206
|
-
const state = getState();
|
|
1207
|
-
if (!state.wipFiber) {
|
|
1208
|
-
throw new Error(`${hookName} can only be called inside function components. ` +
|
|
1209
|
-
'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;
|
|
1210
993
|
}
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
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;
|
|
1214
1002
|
}
|
|
1003
|
+
return null;
|
|
1215
1004
|
};
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
* @typedef {import('../types/internal.js').RyunixRootFiber} RyunixRootFiber
|
|
1225
|
-
* @typedef {import('../types/internal.js').RyunixComponent} RyunixComponent
|
|
1226
|
-
*/
|
|
1227
|
-
/**
|
|
1228
|
-
* @typedef {{ route: RyunixRoute | { component: RyunixComponent | null }, params: Record<string, string | string[]> }} RouteMatch
|
|
1229
|
-
*/
|
|
1230
|
-
/**
|
|
1231
|
-
* @param {unknown[] | undefined} oldDeps
|
|
1232
|
-
* @param {unknown[] | undefined} newDeps
|
|
1233
|
-
* @returns {boolean}
|
|
1234
|
-
*/
|
|
1235
|
-
const haveDepsChanged = (oldDeps, newDeps) => {
|
|
1236
|
-
if (!oldDeps || !newDeps)
|
|
1237
|
-
return true;
|
|
1238
|
-
if (oldDeps.length !== newDeps.length)
|
|
1239
|
-
return true;
|
|
1240
|
-
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;
|
|
1241
1013
|
};
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
* @returns {[unknown, (action: unknown, priority?: number) => void]}
|
|
1246
|
-
*/
|
|
1247
|
-
const useStore = (initialState, priority = getCurrentPriority()) => {
|
|
1248
|
-
// SSR safety check - more reliable than state.isServerRendering
|
|
1249
|
-
if (typeof window === 'undefined') {
|
|
1250
|
-
return [
|
|
1251
|
-
is.function(initialState)
|
|
1252
|
-
? /** @type {() => unknown} */ initialState()
|
|
1253
|
-
: initialState,
|
|
1254
|
-
() => { },
|
|
1255
|
-
];
|
|
1256
|
-
}
|
|
1014
|
+
const enqueueScopedRecovery = (boundaryFiber, boundaryDom, resumeCursor) => {
|
|
1015
|
+
if (!boundaryFiber || !boundaryDom)
|
|
1016
|
+
return;
|
|
1257
1017
|
const state = getState();
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
* @param {unknown} action
|
|
1269
|
-
*/
|
|
1270
|
-
const reducer = (state, action) => is.function(action) ? action(state) : action;
|
|
1271
|
-
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;
|
|
1272
1028
|
};
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
* @param {unknown} initialState
|
|
1276
|
-
* @param {((initial: unknown) => unknown)=} [init]
|
|
1277
|
-
* @param {number} [defaultPriority]
|
|
1278
|
-
* @returns {[unknown, (action: unknown, priority?: number) => void]}
|
|
1279
|
-
*/
|
|
1280
|
-
const useReducer = (reducer, initialState, init, defaultPriority = getCurrentPriority()) => {
|
|
1281
|
-
// SSR safety check - more reliable than state.isServerRendering
|
|
1282
|
-
if (typeof window === 'undefined') {
|
|
1283
|
-
return [init ? init(initialState) : initialState, () => { }];
|
|
1284
|
-
}
|
|
1029
|
+
|
|
1030
|
+
const updateFunctionComponent = (fiber) => {
|
|
1285
1031
|
const state = getState();
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
const wipFiber = /** @type {RyunixFiber} */ state.wipFiber;
|
|
1292
|
-
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
1293
|
-
const hook = {
|
|
1294
|
-
hookID: hookIndex,
|
|
1295
|
-
type: RYUNIX_TYPES.RYUNIX_STORE,
|
|
1296
|
-
state: oldHook ? oldHook.state : init ? init(initialState) : initialState,
|
|
1297
|
-
queue: /** @type {unknown[]} */ [],
|
|
1298
|
-
};
|
|
1299
|
-
if (oldHook?.queue) {
|
|
1300
|
-
oldHook.queue.forEach(
|
|
1301
|
-
/** @param {unknown} action */ (action) => {
|
|
1302
|
-
try {
|
|
1303
|
-
hook.state = reducer(hook.state, action);
|
|
1304
|
-
}
|
|
1305
|
-
catch (error) {
|
|
1306
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
1307
|
-
console.error('Error in reducer:', error);
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
});
|
|
1032
|
+
state.wipFiber = fiber;
|
|
1033
|
+
state.hookIndex = 0;
|
|
1034
|
+
state.wipFiber.hooks = [];
|
|
1035
|
+
if (state.isHydrating) {
|
|
1036
|
+
fiber.effectTag = EFFECT_TAGS.HYDRATE;
|
|
1311
1037
|
}
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
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;
|
|
1317
1048
|
}
|
|
1318
1049
|
return;
|
|
1319
1050
|
}
|
|
1320
|
-
hook.queue.push(action);
|
|
1321
|
-
const currentState = getState();
|
|
1322
|
-
const activeRoot =
|
|
1323
|
-
/** @type {RyunixRootFiber | null | undefined} */ currentState.currentRoot ||
|
|
1324
|
-
currentState.wipRoot;
|
|
1325
|
-
if (!activeRoot)
|
|
1326
|
-
return;
|
|
1327
|
-
const newRoot = /** @type {RyunixRootFiber} */ {
|
|
1328
|
-
dom: activeRoot.dom,
|
|
1329
|
-
props: activeRoot.props,
|
|
1330
|
-
alternate:
|
|
1331
|
-
/** @type {RyunixRootFiber | null} */ currentState.currentRoot || null,
|
|
1332
|
-
};
|
|
1333
|
-
queueUpdate(() => scheduleWork$1(newRoot, priority));
|
|
1334
|
-
};
|
|
1335
|
-
wipFiber.hooks[hookIndex] = hook;
|
|
1336
|
-
state.hookIndex++;
|
|
1337
|
-
return [hook.state, dispatch];
|
|
1338
|
-
};
|
|
1339
|
-
/**
|
|
1340
|
-
* The `useEffect` function in JavaScript is used to manage side effects in functional components by
|
|
1341
|
-
* comparing dependencies and executing a callback function when dependencies change.
|
|
1342
|
-
* @param callback - The `callback` parameter in the `useEffect` function is a function that will be
|
|
1343
|
-
* executed as the effect. This function can perform side effects like data fetching, subscriptions, or
|
|
1344
|
-
* DOM manipulations.
|
|
1345
|
-
* @param deps - The `deps` parameter in the `useEffect` function stands for dependencies. It is an
|
|
1346
|
-
* optional array that contains values that the effect depends on. The effect will only re-run if any
|
|
1347
|
-
* of the values in the `deps` array have changed since the last render. If the `deps` array
|
|
1348
|
-
* @param {() => void | (() => void)} callback
|
|
1349
|
-
* @param {unknown[] | undefined} deps
|
|
1350
|
-
* @returns {void}
|
|
1351
|
-
*/
|
|
1352
|
-
const useEffect = (callback, deps) => {
|
|
1353
|
-
// SSR safety check - more reliable than state.isServerRendering
|
|
1354
|
-
if (typeof window === 'undefined') {
|
|
1355
|
-
return;
|
|
1356
|
-
}
|
|
1357
|
-
const state = getState();
|
|
1358
|
-
if (state.isServerRendering) {
|
|
1359
|
-
return;
|
|
1360
|
-
}
|
|
1361
|
-
validateHookContext();
|
|
1362
|
-
if (!is.function(callback)) {
|
|
1363
|
-
throw new Error('useEffect callback must be a function');
|
|
1364
1051
|
}
|
|
1365
|
-
|
|
1366
|
-
|
|
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;
|
|
1367
1058
|
}
|
|
1368
|
-
|
|
1369
|
-
const wipFiber = /** @type {RyunixFiber} */ state.wipFiber;
|
|
1370
|
-
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
1371
|
-
const hasChanged = haveDepsChanged(oldHook?.deps, deps);
|
|
1372
|
-
const hook = {
|
|
1373
|
-
hookID: hookIndex,
|
|
1374
|
-
type: RYUNIX_TYPES.RYUNIX_EFFECT,
|
|
1375
|
-
deps,
|
|
1376
|
-
effect: hasChanged ? callback : null,
|
|
1377
|
-
cancel: oldHook?.cancel,
|
|
1378
|
-
};
|
|
1379
|
-
wipFiber.hooks[hookIndex] = hook;
|
|
1380
|
-
state.hookIndex++;
|
|
1059
|
+
reconcileChildren(fiber, children);
|
|
1381
1060
|
};
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
* @returns {{ current: unknown }}
|
|
1389
|
-
*/
|
|
1390
|
-
const useRef = (initialValue) => {
|
|
1391
|
-
// SSR safety check - more reliable than state.isServerRendering
|
|
1392
|
-
if (typeof window === 'undefined') {
|
|
1393
|
-
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;
|
|
1394
1067
|
}
|
|
1068
|
+
return false;
|
|
1069
|
+
};
|
|
1070
|
+
const updateHostComponent = (fiber) => {
|
|
1395
1071
|
const state = getState();
|
|
1396
|
-
if (
|
|
1397
|
-
|
|
1072
|
+
if (fiber.type === RYUNIX_TYPES.RYUNIX_CONTEXT) {
|
|
1073
|
+
fiber._contextId =
|
|
1074
|
+
fiber.props?._contextId;
|
|
1075
|
+
fiber._contextValue = fiber.props?.value;
|
|
1398
1076
|
}
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
hookID: hookIndex,
|
|
1405
|
-
type: RYUNIX_TYPES.RYUNIX_REF,
|
|
1406
|
-
value: oldHook
|
|
1407
|
-
? /** @type {{ value: { current: unknown } }} */ oldHook.value
|
|
1408
|
-
: { current: initialValue },
|
|
1409
|
-
};
|
|
1410
|
-
wipFiber.hooks[hookIndex] = hook;
|
|
1411
|
-
state.hookIndex++;
|
|
1412
|
-
return /** @type {{ current: unknown }} */ hook.value;
|
|
1413
|
-
};
|
|
1414
|
-
/**
|
|
1415
|
-
* The useMemo function in JavaScript is used to memoize the result of a computation based on
|
|
1416
|
-
* dependencies.
|
|
1417
|
-
* @param compute - The `compute` parameter in the `useMemo` function is a callback function that
|
|
1418
|
-
* calculates the value that `useMemo` will memoize and return. This function will be called to compute
|
|
1419
|
-
* the memoized value when necessary.
|
|
1420
|
-
* @param deps - The `deps` parameter in the `useMemo` function refers to an array of dependencies.
|
|
1421
|
-
* These dependencies are used to determine whether the memoized value needs to be recalculated or if
|
|
1422
|
-
* the previously calculated value can be reused. The `useMemo` hook will recompute the memoized value
|
|
1423
|
-
* only if
|
|
1424
|
-
* @param {() => unknown} compute
|
|
1425
|
-
* @param {unknown[]} deps
|
|
1426
|
-
* @returns {unknown}
|
|
1427
|
-
*/
|
|
1428
|
-
const useMemo = (compute, deps) => {
|
|
1429
|
-
// SSR safety check - more reliable than state.isServerRendering
|
|
1430
|
-
if (typeof window === 'undefined') {
|
|
1431
|
-
return compute();
|
|
1432
|
-
}
|
|
1433
|
-
const state = getState();
|
|
1434
|
-
if (state.isServerRendering) {
|
|
1435
|
-
return compute();
|
|
1436
|
-
}
|
|
1437
|
-
validateHookContext();
|
|
1438
|
-
if (!is.function(compute)) {
|
|
1439
|
-
throw new Error('useMemo callback must be a function');
|
|
1440
|
-
}
|
|
1441
|
-
if (!Array.isArray(deps)) {
|
|
1442
|
-
throw new Error('useMemo requires a dependencies array');
|
|
1443
|
-
}
|
|
1444
|
-
const { hookIndex } = state;
|
|
1445
|
-
const wipFiber = /** @type {RyunixFiber} */ state.wipFiber;
|
|
1446
|
-
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
1447
|
-
let value;
|
|
1448
|
-
if (oldHook && !haveDepsChanged(oldHook.deps, deps)) {
|
|
1449
|
-
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;
|
|
1450
1082
|
}
|
|
1451
|
-
else {
|
|
1452
|
-
|
|
1453
|
-
|
|
1083
|
+
else if (state.isHydrating && isUnderClientOnlyBoundary(fiber)) {
|
|
1084
|
+
if (!fiber.dom) {
|
|
1085
|
+
fiber.dom = createDom(fiber);
|
|
1086
|
+
fiber.effectTag = EFFECT_TAGS.PLACEMENT;
|
|
1454
1087
|
}
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
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);
|
|
1110
|
+
}
|
|
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
|
+
}
|
|
1458
1139
|
}
|
|
1459
|
-
|
|
1140
|
+
}
|
|
1141
|
+
else {
|
|
1142
|
+
fiber.dom = createDom(fiber);
|
|
1460
1143
|
}
|
|
1461
1144
|
}
|
|
1462
|
-
const
|
|
1463
|
-
|
|
1464
|
-
type: RYUNIX_TYPES.RYUNIX_MEMO,
|
|
1465
|
-
value,
|
|
1466
|
-
deps,
|
|
1467
|
-
};
|
|
1468
|
-
wipFiber.hooks[hookIndex] = hook;
|
|
1469
|
-
state.hookIndex++;
|
|
1470
|
-
return value;
|
|
1145
|
+
const children = fiber.props?.children || [];
|
|
1146
|
+
reconcileChildren(fiber, children);
|
|
1471
1147
|
};
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
const
|
|
1485
|
-
if (
|
|
1486
|
-
|
|
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);
|
|
1487
1163
|
}
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
* @param {unknown} [defaultValue]
|
|
1501
|
-
* @returns {{ Provider: RyunixComponent & { _contextId?: string | symbol }, useContext: (ctxID?: string | symbol) => unknown }}
|
|
1502
|
-
*/
|
|
1503
|
-
const createContext = (contextId = RYUNIX_TYPES.RYUNIX_CONTEXT, defaultValue = {}) => {
|
|
1504
|
-
/** @param {{ value?: unknown, children?: import('../types/internal.js').RyunixNode }} props */
|
|
1505
|
-
const Provider = ({ value, children }) => {
|
|
1506
|
-
return createElement(RYUNIX_TYPES.RYUNIX_CONTEXT, { value, children, _contextId: contextId }, ...flattenArray([children]));
|
|
1507
|
-
};
|
|
1508
|
-
Provider._contextId = contextId;
|
|
1509
|
-
/** @param {string | symbol} [ctxID] */
|
|
1510
|
-
const useContext = (ctxID = contextId) => {
|
|
1511
|
-
const state = getState();
|
|
1512
|
-
if (state.isServerRendering) {
|
|
1513
|
-
const ssrContexts =
|
|
1514
|
-
/** @type {Record<string | symbol, unknown> | undefined} */ state.ssrContexts;
|
|
1515
|
-
return ssrContexts && ssrContexts[ctxID] !== undefined
|
|
1516
|
-
? ssrContexts[ctxID]
|
|
1517
|
-
: defaultValue;
|
|
1518
|
-
}
|
|
1519
|
-
validateHookContext();
|
|
1520
|
-
/** @type {RyunixFiber | null | undefined} */
|
|
1521
|
-
let fiber = /** @type {RyunixFiber} */ state.wipFiber;
|
|
1522
|
-
while (fiber) {
|
|
1523
|
-
if (fiber._contextId === ctxID && fiber._contextValue !== undefined) {
|
|
1524
|
-
return fiber._contextValue;
|
|
1525
|
-
}
|
|
1526
|
-
const fiberType = fiber.type;
|
|
1527
|
-
if (fiberType?._contextId === ctxID && fiber.props?.value !== undefined) {
|
|
1528
|
-
return fiber.props.value;
|
|
1529
|
-
}
|
|
1530
|
-
fiber = fiber.parent;
|
|
1531
|
-
}
|
|
1532
|
-
return defaultValue;
|
|
1533
|
-
};
|
|
1534
|
-
return {
|
|
1535
|
-
Provider:
|
|
1536
|
-
/** @type {RyunixComponent & { _contextId?: string | symbol }} */ Provider,
|
|
1537
|
-
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,
|
|
1538
1176
|
};
|
|
1177
|
+
scheduleWork$1(root, undefined);
|
|
1539
1178
|
};
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
const query = {};
|
|
1550
|
-
for (const [key, value] of searchParams.entries()) {
|
|
1551
|
-
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);
|
|
1552
1188
|
}
|
|
1553
|
-
return query;
|
|
1554
1189
|
};
|
|
1555
|
-
|
|
1556
|
-
* The function `useHash` in JavaScript is used to manage and update the hash portion of the URL in a
|
|
1557
|
-
* web application.
|
|
1558
|
-
* @returns {string}
|
|
1559
|
-
*/
|
|
1560
|
-
const useHash = () => {
|
|
1561
|
-
if (typeof window === 'undefined')
|
|
1562
|
-
return '';
|
|
1563
|
-
const [hash, setHash] = useStore(window.location.hash);
|
|
1564
|
-
useEffect(() => {
|
|
1565
|
-
const onHashChange = () => setHash(window.location.hash);
|
|
1566
|
-
window.addEventListener('hashchange', onHashChange);
|
|
1567
|
-
return () => window.removeEventListener('hashchange', onHashChange);
|
|
1568
|
-
}, []);
|
|
1569
|
-
return /** @type {string} */ hash;
|
|
1570
|
-
};
|
|
1571
|
-
/**
|
|
1572
|
-
* The `useMetadata` function in JavaScript is used to dynamically update metadata tags in the document
|
|
1573
|
-
* head based on provided tags and options.
|
|
1574
|
-
* @param [tags] - The `tags` parameter in the `useMetadata` function is an object that contains
|
|
1575
|
-
* metadata information for the webpage. It can include properties like `pageTitle`, `canonical`, and
|
|
1576
|
-
* other custom metadata tags like `og:title`, `og:description`, `twitter:title`,
|
|
1577
|
-
* `twitter:description`, etc. These tags
|
|
1578
|
-
* @param [options] - The `options` parameter in the `useMetadata` function is an object that can
|
|
1579
|
-
* contain the following properties:
|
|
1580
|
-
* - `title`: An object that can have the following properties:
|
|
1581
|
-
* - `template`: A string that defines the template for the page title. It can include a placeholder
|
|
1582
|
-
* `%s` that will be replaced with the actual page title.
|
|
1583
|
-
* - `prefix`: A string that will be used as the default title if no specific page title is provided.
|
|
1584
|
-
* This hook can't be reached by google crawler.
|
|
1585
|
-
* @param {RyunixMetadataTags} [tags]
|
|
1586
|
-
* @param {RyunixMetadataOptions} [options]
|
|
1587
|
-
* @returns {void}
|
|
1588
|
-
*/
|
|
1589
|
-
const useMetadata = (tags = {}, options = {}) => {
|
|
1190
|
+
const recoverHydrationFailureIfNeeded = () => {
|
|
1590
1191
|
const state = getState();
|
|
1591
|
-
if (state.
|
|
1592
|
-
state.ssrMetadata = { ...state.ssrMetadata, ...tags };
|
|
1192
|
+
if (!state.hydrationFailed || state.hydrationRecover)
|
|
1593
1193
|
return;
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
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);
|
|
1607
1220
|
}
|
|
1608
1221
|
else {
|
|
1609
|
-
|
|
1222
|
+
updateHostComponent(fiber);
|
|
1610
1223
|
}
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
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
|
+
}
|
|
1618
1240
|
}
|
|
1619
|
-
|
|
1241
|
+
catch (e) { }
|
|
1620
1242
|
}
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
}
|
|
1632
|
-
meta.setAttribute('content', value);
|
|
1633
|
-
});
|
|
1634
|
-
}, [JSON.stringify(tags), JSON.stringify(options)]);
|
|
1635
|
-
};
|
|
1636
|
-
// Router Context
|
|
1637
|
-
/** @type {ReturnType<typeof createContext>} */
|
|
1638
|
-
const RouterContext = createContext('ryunix.navigation',
|
|
1639
|
-
/** @type {RyunixRouterContextValue} */ {
|
|
1640
|
-
location: '/',
|
|
1641
|
-
params: {},
|
|
1642
|
-
query: {},
|
|
1643
|
-
/** @param {string} _path */
|
|
1644
|
-
navigate: (_path) => { },
|
|
1645
|
-
route: null,
|
|
1646
|
-
});
|
|
1647
|
-
/**
|
|
1648
|
-
* @param {RyunixRoute[]} routes
|
|
1649
|
-
* @param {string} path
|
|
1650
|
-
* @returns {RouteMatch}
|
|
1651
|
-
*/
|
|
1652
|
-
const findRoute = (routes, path) => {
|
|
1653
|
-
const pathname = path.split('?')[0].split('#')[0];
|
|
1654
|
-
const notFoundRoute = routes.find((route) => route.NotFound);
|
|
1655
|
-
const notFound = notFoundRoute
|
|
1656
|
-
? { route: { component: notFoundRoute.NotFound }, params: {} }
|
|
1657
|
-
: { route: { component: null }, params: {} };
|
|
1658
|
-
for (const route of routes) {
|
|
1659
|
-
if (route.subRoutes) {
|
|
1660
|
-
const childRoute = findRoute(route.subRoutes, path);
|
|
1661
|
-
if (childRoute)
|
|
1662
|
-
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;
|
|
1663
1253
|
}
|
|
1664
|
-
if (
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
return
|
|
1676
|
-
})}$`);
|
|
1677
|
-
const matchPath = pathname.match(pattern);
|
|
1678
|
-
if (matchPath) {
|
|
1679
|
-
const params = keys.reduce((acc, keyObj, index) => {
|
|
1680
|
-
const val = matchPath[index + 1];
|
|
1681
|
-
acc[keyObj.key] = keyObj.isCatchAll && val ? val.split('/') : val;
|
|
1682
|
-
return acc;
|
|
1683
|
-
},
|
|
1684
|
-
/** @type {Record<string, string | string[]>} */ {});
|
|
1685
|
-
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;
|
|
1686
1266
|
}
|
|
1687
1267
|
}
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
/**
|
|
1691
|
-
* @returns {string}
|
|
1692
|
-
*/
|
|
1693
|
-
const getSsrPathname = () => {
|
|
1694
|
-
const pathname = globalThis?.window?.location?.pathname;
|
|
1695
|
-
if (typeof pathname === 'string' && pathname) {
|
|
1696
|
-
return pathname.split('?')[0].split('#')[0];
|
|
1268
|
+
if (fiber.child) {
|
|
1269
|
+
return fiber.child;
|
|
1697
1270
|
}
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
// SSR: Return server-safe version without hooks
|
|
1708
|
-
if (typeof window === 'undefined') {
|
|
1709
|
-
const location = getSsrPathname();
|
|
1710
|
-
const currentRouteData = findRoute(routes, location);
|
|
1711
|
-
/** @type {RyunixRouterContextValue} */
|
|
1712
|
-
const contextValue = {
|
|
1713
|
-
location,
|
|
1714
|
-
params: currentRouteData.params || {},
|
|
1715
|
-
query: {},
|
|
1716
|
-
navigate: () => { },
|
|
1717
|
-
route: currentRouteData.route,
|
|
1718
|
-
};
|
|
1719
|
-
return createElement(
|
|
1720
|
-
/** @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;
|
|
1721
1280
|
}
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
window.location.assign(path);
|
|
1737
|
-
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
|
+
}
|
|
1738
1295
|
}
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
};
|
|
1742
|
-
const currentRouteData = findRoute(routes, location);
|
|
1743
|
-
const query = useQuery();
|
|
1744
|
-
/** @type {RyunixRouterContextValue} */
|
|
1745
|
-
const contextValue = {
|
|
1746
|
-
location,
|
|
1747
|
-
params: currentRouteData.params || {},
|
|
1748
|
-
query,
|
|
1749
|
-
navigate,
|
|
1750
|
-
route: currentRouteData.route,
|
|
1751
|
-
};
|
|
1752
|
-
return createElement(
|
|
1753
|
-
/** @type {string | symbol | Function} */ RouterContext.Provider, { value: contextValue }, Fragment({ children }));
|
|
1754
|
-
};
|
|
1755
|
-
/**
|
|
1756
|
-
* The function `useRouter` returns the context of the Router for navigation in a Ryunix application.
|
|
1757
|
-
* @returns {RyunixRouterContextValue}
|
|
1758
|
-
*/
|
|
1759
|
-
const useRouter = () => {
|
|
1760
|
-
return RouterContext.useContext('ryunix.navigation');
|
|
1761
|
-
};
|
|
1762
|
-
/**
|
|
1763
|
-
* The `Children` function in JavaScript uses router hooks to handle scrolling to a specific element
|
|
1764
|
-
* based on the hash in the URL.
|
|
1765
|
-
* @returns {import('./createElement.js').RyunixElement | null}
|
|
1766
|
-
*/
|
|
1767
|
-
const Children = () => {
|
|
1768
|
-
const { route, params, query, location } = useRouter();
|
|
1769
|
-
if (!route || !route.component)
|
|
1770
|
-
return null;
|
|
1771
|
-
const hash = useHash();
|
|
1772
|
-
useEffect(() => {
|
|
1773
|
-
if (hash) {
|
|
1774
|
-
const id = /** @type {string} */ hash.slice(1);
|
|
1775
|
-
const el = document.getElementById(id);
|
|
1776
|
-
if (el)
|
|
1777
|
-
el.scrollIntoView({ block: 'start', behavior: 'smooth' });
|
|
1296
|
+
if (state.nextUnitOfWork) {
|
|
1297
|
+
state.nextUnitOfWork = performUnitOfWork(state.nextUnitOfWork);
|
|
1778
1298
|
}
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
}
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
* @returns {string}
|
|
1792
|
-
*/
|
|
1793
|
-
const usePathname = () => {
|
|
1794
|
-
const { location } = useRouter();
|
|
1795
|
-
return location.split('?')[0].split('#')[0];
|
|
1796
|
-
};
|
|
1797
|
-
/**
|
|
1798
|
-
* useSearchParams - Returns the current URLSearchParams object
|
|
1799
|
-
* @returns {URLSearchParams}
|
|
1800
|
-
*/
|
|
1801
|
-
const useSearchParams = () => {
|
|
1802
|
-
const { query } = useRouter();
|
|
1803
|
-
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
|
+
}
|
|
1804
1311
|
};
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
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;
|
|
1817
1324
|
}
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
onMouseEnter: handleMouseEnter,
|
|
1828
|
-
className: props.className || props['ryunix-class'],
|
|
1829
|
-
...cleanedProps,
|
|
1830
|
-
}, props.children);
|
|
1831
|
-
};
|
|
1832
|
-
/**
|
|
1833
|
-
* The NavLink function in JavaScript is a component that generates a link element with customizable
|
|
1834
|
-
* classes and active state based on the current location.
|
|
1835
|
-
* @param {{ to: string, exact?: boolean, children?: import('../types/internal.js').RyunixNode } & Record<string, unknown>} props
|
|
1836
|
-
* @returns {import('./createElement.js').RyunixElement}
|
|
1837
|
-
*/
|
|
1838
|
-
const NavLink = ({ to, exact = false, ...props }) => {
|
|
1839
|
-
const { location, navigate } = useRouter();
|
|
1840
|
-
const isActive = exact ? location === to : location.startsWith(to);
|
|
1841
|
-
/** @param {string | ((args: { isActive: boolean }) => string) | undefined} cls */
|
|
1842
|
-
const resolveClass = (cls) => typeof cls === 'function' ? cls({ isActive }) : cls || '';
|
|
1843
|
-
/** @param {MouseEvent} e */
|
|
1844
|
-
const handleClick = (e) => {
|
|
1845
|
-
if (e.button !== 0 || e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) {
|
|
1846
|
-
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
|
+
});
|
|
1847
1334
|
}
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
const classAttrValue = resolveClass(
|
|
1853
|
-
/** @type {string | ((args: { isActive: boolean }) => string) | undefined} */ props['ryunix-class'] || props.className);
|
|
1854
|
-
const { ['ryunix-class']: _omitRyunix, className: _omitClassName, ...cleanedProps } = props;
|
|
1855
|
-
return createElement('a', {
|
|
1856
|
-
href: to,
|
|
1857
|
-
onClick: handleClick,
|
|
1858
|
-
[classAttrName]: classAttrValue,
|
|
1859
|
-
...cleanedProps,
|
|
1860
|
-
}, props.children);
|
|
1335
|
+
else {
|
|
1336
|
+
rIC(workLoop);
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1861
1339
|
};
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
const
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
priority,
|
|
1878
|
-
};
|
|
1879
|
-
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,
|
|
1880
1355
|
};
|
|
1881
|
-
|
|
1356
|
+
scheduleWork(root);
|
|
1357
|
+
return root;
|
|
1882
1358
|
};
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
callback();
|
|
1895
|
-
setIsPending(false, Priority.LOW);
|
|
1896
|
-
});
|
|
1897
|
-
}, 0);
|
|
1898
|
-
};
|
|
1899
|
-
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;
|
|
1900
1370
|
};
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
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;
|
|
1915
1387
|
};
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
* value that will be used if there is no data stored in the local storage under the specified `key`.
|
|
1924
|
-
* It serves as the default value for the state if no data is retrieved from the local storage.
|
|
1925
|
-
* @param {string} key
|
|
1926
|
-
* @param {unknown} [initialState]
|
|
1927
|
-
* @returns {[unknown, (value: unknown) => void]}
|
|
1928
|
-
*/
|
|
1929
|
-
const usePersistentStore = (key, initialState = '') => {
|
|
1930
|
-
const [state, dispatch] = useStore(() => {
|
|
1931
|
-
try {
|
|
1932
|
-
const item = window.localStorage.getItem(key);
|
|
1933
|
-
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.`);
|
|
1934
1395
|
}
|
|
1935
|
-
|
|
1936
|
-
|
|
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}`);
|
|
1937
1407
|
}
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
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}`);
|
|
1951
1420
|
}
|
|
1952
|
-
|
|
1953
|
-
|
|
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);
|
|
1954
1436
|
}
|
|
1955
|
-
|
|
1956
|
-
|
|
1437
|
+
if (onError)
|
|
1438
|
+
onError(error);
|
|
1439
|
+
return null;
|
|
1440
|
+
}
|
|
1957
1441
|
};
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
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: [],
|
|
1975
1530
|
};
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
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) => {
|
|
1987
1569
|
if (typeof window === 'undefined') {
|
|
1988
1570
|
return;
|
|
1989
1571
|
}
|
|
@@ -1993,13 +1575,13 @@ const useLayoutEffect = (callback, deps) => {
|
|
|
1993
1575
|
}
|
|
1994
1576
|
validateHookContext();
|
|
1995
1577
|
if (!is.function(callback)) {
|
|
1996
|
-
throw new Error('
|
|
1578
|
+
throw new Error('useEffect callback must be a function');
|
|
1997
1579
|
}
|
|
1998
1580
|
if (deps !== undefined && !Array.isArray(deps)) {
|
|
1999
|
-
throw new Error('
|
|
1581
|
+
throw new Error('useEffect dependencies must be an array or undefined');
|
|
2000
1582
|
}
|
|
2001
1583
|
const { hookIndex } = state;
|
|
2002
|
-
const wipFiber =
|
|
1584
|
+
const wipFiber = state.wipFiber;
|
|
2003
1585
|
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
2004
1586
|
const hasChanged = haveDepsChanged(oldHook?.deps, deps);
|
|
2005
1587
|
const hook = {
|
|
@@ -2008,788 +1590,498 @@ const useLayoutEffect = (callback, deps) => {
|
|
|
2008
1590
|
deps,
|
|
2009
1591
|
effect: hasChanged ? callback : null,
|
|
2010
1592
|
cancel: oldHook?.cancel,
|
|
2011
|
-
isLayout: true, // Flag to run synchronously during commit
|
|
2012
1593
|
};
|
|
2013
|
-
wipFiber.hooks[hookIndex] =
|
|
1594
|
+
wipFiber.hooks[hookIndex] =
|
|
1595
|
+
hook;
|
|
2014
1596
|
state.hookIndex++;
|
|
2015
1597
|
};
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
* to ensure deterministic IDs across multiple renders
|
|
2021
|
-
* @returns {void}
|
|
2022
|
-
*/
|
|
2023
|
-
const resetIdCounter = () => {
|
|
2024
|
-
idCounter = 0;
|
|
2025
|
-
};
|
|
2026
|
-
/**
|
|
2027
|
-
* useId - Generate a deterministic, unique ID that is stable across SSR and hydration.
|
|
2028
|
-
* @returns {string} A unique ID string
|
|
2029
|
-
*/
|
|
2030
|
-
const useId = () => {
|
|
1598
|
+
const useRef = (initialValue) => {
|
|
1599
|
+
if (typeof window === 'undefined') {
|
|
1600
|
+
return { current: initialValue };
|
|
1601
|
+
}
|
|
2031
1602
|
const state = getState();
|
|
2032
1603
|
if (state.isServerRendering) {
|
|
2033
|
-
|
|
2034
|
-
return `:r${idCounter++}:`;
|
|
1604
|
+
return { current: initialValue };
|
|
2035
1605
|
}
|
|
2036
1606
|
validateHookContext();
|
|
2037
1607
|
const { hookIndex } = state;
|
|
2038
|
-
const wipFiber =
|
|
1608
|
+
const wipFiber = state.wipFiber;
|
|
2039
1609
|
const oldHook = wipFiber.alternate?.hooks?.[hookIndex];
|
|
2040
1610
|
const hook = {
|
|
2041
1611
|
hookID: hookIndex,
|
|
2042
1612
|
type: RYUNIX_TYPES.RYUNIX_REF,
|
|
2043
1613
|
value: oldHook
|
|
2044
|
-
?
|
|
2045
|
-
:
|
|
1614
|
+
? oldHook.value
|
|
1615
|
+
: { current: initialValue },
|
|
2046
1616
|
};
|
|
2047
|
-
wipFiber.hooks[hookIndex] =
|
|
1617
|
+
wipFiber.hooks[hookIndex] =
|
|
1618
|
+
hook;
|
|
2048
1619
|
state.hookIndex++;
|
|
2049
|
-
return
|
|
2050
|
-
};
|
|
2051
|
-
/**
|
|
2052
|
-
* useDebounce - Returns a debounced version of the value that only updates
|
|
2053
|
-
* after the specified delay has passed since the last change.
|
|
2054
|
-
* @param {unknown} value - Value to debounce
|
|
2055
|
-
* @param {number} delay - Delay in milliseconds (default: 300)
|
|
2056
|
-
* @returns {unknown} Debounced value
|
|
2057
|
-
*/
|
|
2058
|
-
const useDebounce = (value, delay = 300) => {
|
|
2059
|
-
const [debouncedValue, setDebouncedValue] = useStore(value);
|
|
2060
|
-
useEffect(() => {
|
|
2061
|
-
const timer = setTimeout(() => {
|
|
2062
|
-
setDebouncedValue(value);
|
|
2063
|
-
}, delay);
|
|
2064
|
-
return () => clearTimeout(timer);
|
|
2065
|
-
}, [value, delay]);
|
|
2066
|
-
return debouncedValue;
|
|
1620
|
+
return hook.value;
|
|
2067
1621
|
};
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
* @param {unknown} value - Value to throttle
|
|
2072
|
-
* @param {number} interval - Minimum interval in milliseconds (default: 300)
|
|
2073
|
-
* @returns {unknown} Throttled value
|
|
2074
|
-
*/
|
|
2075
|
-
const useThrottle = (value, interval = 300) => {
|
|
2076
|
-
const [throttledValue, setThrottledValue] = useStore(value);
|
|
2077
|
-
const lastUpdated = useRef(Date.now());
|
|
2078
|
-
useEffect(() => {
|
|
2079
|
-
const now = Date.now();
|
|
2080
|
-
const elapsed = now - lastUpdated.current;
|
|
2081
|
-
if (elapsed >= interval) {
|
|
2082
|
-
lastUpdated.current = now;
|
|
2083
|
-
setThrottledValue(value);
|
|
2084
|
-
}
|
|
2085
|
-
else {
|
|
2086
|
-
const timer = setTimeout(() => {
|
|
2087
|
-
lastUpdated.current = Date.now();
|
|
2088
|
-
setThrottledValue(value);
|
|
2089
|
-
}, interval - elapsed);
|
|
2090
|
-
return () => clearTimeout(timer);
|
|
2091
|
-
}
|
|
2092
|
-
}, [value, interval]);
|
|
2093
|
-
return throttledValue;
|
|
2094
|
-
};
|
|
2095
|
-
|
|
2096
|
-
var hooks = /*#__PURE__*/Object.freeze({
|
|
2097
|
-
__proto__: null,
|
|
2098
|
-
Children: Children,
|
|
2099
|
-
Link: Link,
|
|
2100
|
-
NavLink: NavLink,
|
|
2101
|
-
RouterProvider: RouterProvider,
|
|
2102
|
-
createContext: createContext,
|
|
2103
|
-
resetIdCounter: resetIdCounter,
|
|
2104
|
-
useCallback: useCallback,
|
|
2105
|
-
useDebounce: useDebounce,
|
|
2106
|
-
useDeferredValue: useDeferredValue,
|
|
2107
|
-
useEffect: useEffect,
|
|
2108
|
-
useHash: useHash,
|
|
2109
|
-
useId: useId,
|
|
2110
|
-
useLayoutEffect: useLayoutEffect,
|
|
2111
|
-
useMemo: useMemo,
|
|
2112
|
-
useMetadata: useMetadata,
|
|
2113
|
-
usePathname: usePathname,
|
|
2114
|
-
usePersistentStore: usePersistentStore,
|
|
2115
|
-
usePersitentStore: usePersistentStore,
|
|
2116
|
-
useQuery: useQuery,
|
|
2117
|
-
useReducer: useReducer,
|
|
2118
|
-
useRef: useRef,
|
|
2119
|
-
useRouter: useRouter,
|
|
2120
|
-
useSearchParams: useSearchParams,
|
|
2121
|
-
useStore: useStore,
|
|
2122
|
-
useStorePriority: useStorePriority,
|
|
2123
|
-
useSwitch: useSwitch,
|
|
2124
|
-
useThrottle: useThrottle,
|
|
2125
|
-
useTransition: useTransition
|
|
2126
|
-
});
|
|
2127
|
-
|
|
2128
|
-
const getHydrationPolicy = () => {
|
|
2129
|
-
const recoverRaw = process.env.RYUNIX_HYDRATION_RECOVER || 'boundary';
|
|
2130
|
-
const boundariesRaw = process.env.RYUNIX_HYDRATION_BOUNDARIES || 'route';
|
|
2131
|
-
const strict = process.env.RYUNIX_HYDRATION_STRICT === 'true';
|
|
2132
|
-
const recover = recoverRaw === 'none' || recoverRaw === 'root' ? recoverRaw : 'boundary';
|
|
2133
|
-
const boundaries = boundariesRaw === 'server-only' || boundariesRaw === 'all-layouts'
|
|
2134
|
-
? boundariesRaw
|
|
2135
|
-
: 'route';
|
|
2136
|
-
return {
|
|
2137
|
-
recover,
|
|
2138
|
-
boundaries,
|
|
2139
|
-
strict,
|
|
2140
|
-
};
|
|
2141
|
-
};
|
|
2142
|
-
/**
|
|
2143
|
-
*/
|
|
2144
|
-
const findNearestHydrationBoundary = (fiber) => {
|
|
2145
|
-
let current = fiber || null;
|
|
2146
|
-
while (current) {
|
|
2147
|
-
const props = current.props;
|
|
2148
|
-
if (props &&
|
|
2149
|
-
Object.prototype.hasOwnProperty.call(props, 'data-ryunix-hydrate-boundary')) {
|
|
2150
|
-
return current;
|
|
2151
|
-
}
|
|
2152
|
-
const type = current.type;
|
|
2153
|
-
const maybeTyped = type;
|
|
2154
|
-
if (type &&
|
|
2155
|
-
typeof type === 'function' &&
|
|
2156
|
-
(maybeTyped?.ryunix_type === 'RYUNIX_HYDRATION_BOUNDARY' ||
|
|
2157
|
-
maybeTyped?.ryunix_type === 'RYUNIX_SERVER_BOUNDARY')) {
|
|
2158
|
-
return current;
|
|
2159
|
-
}
|
|
2160
|
-
current = current.parent || null;
|
|
1622
|
+
const useMemo = (compute, deps) => {
|
|
1623
|
+
if (typeof window === 'undefined') {
|
|
1624
|
+
return compute();
|
|
2161
1625
|
}
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
*/
|
|
2166
|
-
const getBoundaryDom = (fiber) => {
|
|
2167
|
-
if (!fiber)
|
|
2168
|
-
return null;
|
|
2169
|
-
if (fiber.dom && fiber.dom.nodeType === 1) {
|
|
2170
|
-
const el = fiber.dom;
|
|
2171
|
-
if (el.hasAttribute('data-ryunix-hydrate-boundary'))
|
|
2172
|
-
return el;
|
|
1626
|
+
const state = getState();
|
|
1627
|
+
if (state.isServerRendering) {
|
|
1628
|
+
return compute();
|
|
2173
1629
|
}
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
const el = child.dom;
|
|
2178
|
-
if (el.hasAttribute('data-ryunix-hydrate-boundary'))
|
|
2179
|
-
return el;
|
|
2180
|
-
}
|
|
2181
|
-
child = child.child || child.sibling || null;
|
|
1630
|
+
validateHookContext();
|
|
1631
|
+
if (!is.function(compute)) {
|
|
1632
|
+
throw new Error('useMemo callback must be a function');
|
|
2182
1633
|
}
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
/**
|
|
2186
|
-
*/
|
|
2187
|
-
const skipHydrationSubtree = (cursor, boundaryRoot) => {
|
|
2188
|
-
if (!cursor || !boundaryRoot)
|
|
2189
|
-
return cursor;
|
|
2190
|
-
if (cursor === boundaryRoot)
|
|
2191
|
-
return boundaryRoot.nextSibling;
|
|
2192
|
-
if (boundaryRoot.contains(cursor))
|
|
2193
|
-
return boundaryRoot.nextSibling;
|
|
2194
|
-
return cursor;
|
|
2195
|
-
};
|
|
2196
|
-
/**
|
|
2197
|
-
*/
|
|
2198
|
-
const enqueueScopedRecovery = (boundaryFiber, boundaryDom, resumeCursor) => {
|
|
2199
|
-
if (!boundaryFiber || !boundaryDom)
|
|
2200
|
-
return;
|
|
2201
|
-
const state = getState();
|
|
2202
|
-
const queue = state.scopedRecoveryQueue || [];
|
|
2203
|
-
queue.push({
|
|
2204
|
-
boundaryFiber,
|
|
2205
|
-
boundaryDom,
|
|
2206
|
-
resumeCursor,
|
|
2207
|
-
element: (Array.isArray(boundaryFiber.props?.children)
|
|
2208
|
-
? boundaryFiber.props.children[0]
|
|
2209
|
-
: boundaryFiber.props?.children),
|
|
2210
|
-
});
|
|
2211
|
-
state.scopedRecoveryQueue = queue;
|
|
2212
|
-
};
|
|
2213
|
-
|
|
2214
|
-
/**
|
|
2215
|
-
* @typedef {import('../types/internal.js').RyunixFiber} RyunixFiber
|
|
2216
|
-
* @typedef {import('../types/internal.js').RyunixComponent} RyunixComponent
|
|
2217
|
-
* @typedef {import('../types/internal.js').RyunixNode} RyunixNode
|
|
2218
|
-
*/
|
|
2219
|
-
/**
|
|
2220
|
-
* @param {RyunixFiber} fiber
|
|
2221
|
-
*/
|
|
2222
|
-
const updateFunctionComponent = (fiber) => {
|
|
2223
|
-
const state = getState();
|
|
2224
|
-
state.wipFiber = fiber;
|
|
2225
|
-
state.hookIndex = 0;
|
|
2226
|
-
/** @type {RyunixFiber} */ state.wipFiber.hooks = [];
|
|
2227
|
-
if (state.isHydrating) {
|
|
2228
|
-
fiber.effectTag = EFFECT_TAGS.HYDRATE;
|
|
1634
|
+
if (!Array.isArray(deps)) {
|
|
1635
|
+
throw new Error('useMemo requires a dependencies array');
|
|
2229
1636
|
}
|
|
2230
|
-
|
|
2231
|
-
const
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
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);
|
|
2242
1651
|
}
|
|
2243
|
-
|
|
1652
|
+
throw error;
|
|
2244
1653
|
}
|
|
2245
1654
|
}
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
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;
|
|
2254
1665
|
};
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
*/
|
|
2259
|
-
const isUnderClientOnlyBoundary = (fiber) => {
|
|
2260
|
-
let current = fiber?.parent || null;
|
|
2261
|
-
while (current) {
|
|
2262
|
-
if (current._hydrateClientOnly)
|
|
2263
|
-
return true;
|
|
2264
|
-
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');
|
|
2265
1669
|
}
|
|
2266
|
-
return
|
|
1670
|
+
return useMemo(() => callback, deps);
|
|
2267
1671
|
};
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
fiber.type === Symbol.for('ryunix.portal');
|
|
2281
|
-
if (state.isHydrating && isPassthrough) {
|
|
2282
|
-
fiber.effectTag = EFFECT_TAGS.HYDRATE;
|
|
2283
|
-
}
|
|
2284
|
-
else if (state.isHydrating && isUnderClientOnlyBoundary(fiber)) {
|
|
2285
|
-
if (!fiber.dom) {
|
|
2286
|
-
fiber.dom = /** @type {HTMLElement | Text | null} */ createDom(fiber);
|
|
2287
|
-
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;
|
|
2288
1684
|
}
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
const isElement = typeof fiber.type === 'string' &&
|
|
2295
|
-
domNode.nodeType === 1 &&
|
|
2296
|
-
domNode.tagName.toLowerCase() === fiber.type.toLowerCase();
|
|
2297
|
-
if (isText || isElement) {
|
|
2298
|
-
fiber.dom = /** @type {HTMLElement | Text} */ domNode;
|
|
2299
|
-
fiber.effectTag = EFFECT_TAGS.HYDRATE;
|
|
2300
|
-
if (isText &&
|
|
2301
|
-
fiber.props?.nodeValue != null &&
|
|
2302
|
-
domNode.nodeValue !== String(fiber.props.nodeValue)) {
|
|
2303
|
-
domNode.nodeValue = String(fiber.props.nodeValue);
|
|
2304
|
-
logHydrationRecoverable('text');
|
|
2305
|
-
}
|
|
2306
|
-
if (isElement &&
|
|
2307
|
-
domNode.hasAttribute('data-ryunix-hydrate-boundary')) {
|
|
2308
|
-
fiber._hydrateClientOnly = true;
|
|
2309
|
-
}
|
|
2310
|
-
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;
|
|
2311
1690
|
}
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
const boundaryFiber = findNearestHydrationBoundary(fiber);
|
|
2316
|
-
const boundaryDom = boundaryFiber ? getBoundaryDom(boundaryFiber) : null;
|
|
2317
|
-
if (policy.recover === 'boundary' && boundaryFiber && boundaryDom) {
|
|
2318
|
-
logHydrationBoundaryMismatch(detail);
|
|
2319
|
-
enqueueScopedRecovery(boundaryFiber, boundaryDom, state.hydrateCursor ?? null);
|
|
2320
|
-
state.hydrateCursor = skipHydrationSubtree(state.hydrateCursor ?? null, boundaryDom);
|
|
2321
|
-
fiber.dom = /** @type {HTMLElement | Text | null} */ createDom(fiber);
|
|
2322
|
-
fiber.effectTag = EFFECT_TAGS.PLACEMENT;
|
|
2323
|
-
}
|
|
2324
|
-
else if (policy.recover === 'none') {
|
|
2325
|
-
logHydrationFatal(detail);
|
|
2326
|
-
state.isHydrating = false;
|
|
2327
|
-
state.hydrateCursor = null;
|
|
2328
|
-
fiber.dom = /** @type {HTMLElement | Text | null} */ createDom(fiber);
|
|
2329
|
-
fiber.effectTag = EFFECT_TAGS.PLACEMENT;
|
|
2330
|
-
}
|
|
2331
|
-
else {
|
|
2332
|
-
logHydrationMismatch(detail);
|
|
2333
|
-
state.isHydrating = false;
|
|
2334
|
-
state.hydrationFailed = true;
|
|
2335
|
-
state.hydrateCursor = null;
|
|
2336
|
-
fiber.dom = /** @type {HTMLElement | Text | null} */ createDom(fiber);
|
|
2337
|
-
fiber.effectTag = EFFECT_TAGS.PLACEMENT;
|
|
2338
|
-
}
|
|
1691
|
+
const fiberType = fiber.type;
|
|
1692
|
+
if (fiberType?._contextId === ctxID && fiber.props?.value !== undefined) {
|
|
1693
|
+
return fiber.props.value;
|
|
2339
1694
|
}
|
|
1695
|
+
fiber = fiber.parent;
|
|
2340
1696
|
}
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
}
|
|
2344
|
-
}
|
|
2345
|
-
const children = fiber.props?.children || [];
|
|
2346
|
-
reconcileChildren(fiber, children);
|
|
2347
|
-
};
|
|
2348
|
-
/**
|
|
2349
|
-
* @param {string | symbol | RyunixComponent | object} type
|
|
2350
|
-
* @returns {string}
|
|
2351
|
-
*/
|
|
2352
|
-
const getTypeLabel = (type) => {
|
|
2353
|
-
if (typeof type === 'symbol')
|
|
2354
|
-
return type.description || type.toString();
|
|
2355
|
-
if (typeof type === 'function')
|
|
2356
|
-
return type.name || 'anonymous';
|
|
2357
|
-
return String(type);
|
|
2358
|
-
};
|
|
2359
|
-
/**
|
|
2360
|
-
* The Component `Image` takes in a `src` and other props, and returns an `img` element with the
|
|
2361
|
-
* specified `src` and props.
|
|
2362
|
-
* @returns The `Image` component is being returned. It is a functional component that renders an `img`
|
|
2363
|
-
* element with the specified `src` and other props passed to it.
|
|
2364
|
-
*/
|
|
2365
|
-
/**
|
|
2366
|
-
* @param {{ src: string } & Record<string, unknown>} props
|
|
2367
|
-
* @returns {import('./createElement.js').RyunixElement}
|
|
2368
|
-
*/
|
|
2369
|
-
const Image = ({ src, ...props }) => {
|
|
2370
|
-
return createElement('img', { ...props, src });
|
|
2371
|
-
};
|
|
2372
|
-
const { Provider: MDXProvider, useContext: useMDXComponents } = createContext('ryunix.mdx',
|
|
2373
|
-
/** @type {Record<string, RyunixComponent>} */ {});
|
|
2374
|
-
/**
|
|
2375
|
-
* Get merged MDX components from context and provided components
|
|
2376
|
-
* @param {Record<string, RyunixComponent>} [components] - Additional components to merge
|
|
2377
|
-
* @returns {Record<string, RyunixComponent>} Merged components object
|
|
2378
|
-
*/
|
|
2379
|
-
const getMDXComponents = (components) => {
|
|
2380
|
-
const contextComponents = useMDXComponents();
|
|
1697
|
+
return defaultValue;
|
|
1698
|
+
};
|
|
2381
1699
|
return {
|
|
2382
|
-
|
|
2383
|
-
|
|
1700
|
+
Provider: Provider,
|
|
1701
|
+
useContext,
|
|
2384
1702
|
};
|
|
2385
1703
|
};
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
const
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
* @type {Record<string, (props: Record<string, unknown>) => RyunixNode>}
|
|
2396
|
-
*/
|
|
2397
|
-
const defaultComponents = {
|
|
2398
|
-
// Headings
|
|
2399
|
-
h1: (props) => mdxHost('h1', props),
|
|
2400
|
-
h2: (props) => mdxHost('h2', props),
|
|
2401
|
-
h3: (props) => mdxHost('h3', props),
|
|
2402
|
-
h4: (props) => mdxHost('h4', props),
|
|
2403
|
-
h5: (props) => mdxHost('h5', props),
|
|
2404
|
-
h6: (props) => mdxHost('h6', props),
|
|
2405
|
-
// Text
|
|
2406
|
-
p: (props) => mdxHost('p', props),
|
|
2407
|
-
a: (props) => mdxHost('a', props),
|
|
2408
|
-
strong: (props) => mdxHost('strong', props),
|
|
2409
|
-
em: (props) => mdxHost('em', props),
|
|
2410
|
-
code: (props) => mdxHost('code', props),
|
|
2411
|
-
// Lists
|
|
2412
|
-
ul: (props) => mdxHost('ul', props),
|
|
2413
|
-
ol: (props) => mdxHost('ol', props),
|
|
2414
|
-
li: (props) => mdxHost('li', props),
|
|
2415
|
-
// Blocks
|
|
2416
|
-
blockquote: (props) => mdxHost('blockquote', props),
|
|
2417
|
-
pre: (props) => mdxHost('pre', props),
|
|
2418
|
-
hr: (props) => mdxHost('hr', props),
|
|
2419
|
-
// Tables
|
|
2420
|
-
table: (props) => mdxHost('table', props),
|
|
2421
|
-
thead: (props) => mdxHost('thead', props),
|
|
2422
|
-
tbody: (props) => mdxHost('tbody', props),
|
|
2423
|
-
tr: (props) => mdxHost('tr', props),
|
|
2424
|
-
th: (props) => mdxHost('th', props),
|
|
2425
|
-
td: (props) => mdxHost('td', props),
|
|
2426
|
-
// Media
|
|
2427
|
-
img: (props) => mdxHost('img', props),
|
|
2428
|
-
};
|
|
2429
|
-
/**
|
|
2430
|
-
* MDX Wrapper component
|
|
2431
|
-
* Provides default styling and components for MDX content
|
|
2432
|
-
*/
|
|
2433
|
-
/**
|
|
2434
|
-
* @param {{ children?: RyunixNode, components?: Record<string, RyunixComponent> }} props
|
|
2435
|
-
* @returns {import('./createElement.js').RyunixElement}
|
|
2436
|
-
*/
|
|
2437
|
-
const MDXContent = ({ children, components = {} }) => {
|
|
2438
|
-
const mergedComponents = getMDXComponents(components);
|
|
2439
|
-
return createElement(
|
|
2440
|
-
/** @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;
|
|
2441
1713
|
};
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
const
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
props: { children: [element] },
|
|
2453
|
-
isHydrating: false,
|
|
2454
|
-
hydrateCursor: null,
|
|
2455
|
-
};
|
|
2456
|
-
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;
|
|
2457
1724
|
};
|
|
2458
|
-
const
|
|
1725
|
+
const useMetadata = (tags = {}, options = {}) => {
|
|
2459
1726
|
const state = getState();
|
|
2460
|
-
|
|
2461
|
-
|
|
1727
|
+
if (state.isServerRendering) {
|
|
1728
|
+
state.ssrMetadata = { ...state.ssrMetadata, ...tags };
|
|
2462
1729
|
return;
|
|
2463
|
-
state.scopedRecoveryQueue = [];
|
|
2464
|
-
for (const item of queue) {
|
|
2465
|
-
logHydrationBoundaryRecovery();
|
|
2466
|
-
renderSubtree(item.element, item.boundaryDom);
|
|
2467
1730
|
}
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
return;
|
|
2480
|
-
state.hydrationRecover = true;
|
|
2481
|
-
state.hydrationFailed = false;
|
|
2482
|
-
logHydrationFailure('');
|
|
2483
|
-
logHydrationRecovery();
|
|
2484
|
-
renderSubtree(/** @type {RyunixNode} */ element, container);
|
|
2485
|
-
};
|
|
2486
|
-
const runHydrationRecovery = () => {
|
|
2487
|
-
recoverScopedHydrationFailures();
|
|
2488
|
-
recoverHydrationFailureIfNeeded();
|
|
2489
|
-
};
|
|
2490
|
-
|
|
2491
|
-
/**
|
|
2492
|
-
* @typedef {import('../types/internal.js').RyunixFiber} RyunixFiber
|
|
2493
|
-
* @typedef {import('../types/internal.js').RyunixRootFiber} RyunixRootFiber
|
|
2494
|
-
*/
|
|
2495
|
-
/** @type {RyunixRootFiber[]} */
|
|
2496
|
-
let workQueue = [];
|
|
2497
|
-
/** @type {boolean} */
|
|
2498
|
-
let isWorkLoopScheduled = false;
|
|
2499
|
-
/**
|
|
2500
|
-
* @param {RyunixFiber} fiber
|
|
2501
|
-
* @returns {RyunixFiber | null}
|
|
2502
|
-
*/
|
|
2503
|
-
function performUnitOfWork(fiber) {
|
|
2504
|
-
const state = getState();
|
|
2505
|
-
const isFunctionComponent = fiber.type instanceof Function || typeof fiber.type === 'function';
|
|
2506
|
-
try {
|
|
2507
|
-
if (isFunctionComponent) {
|
|
2508
|
-
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;
|
|
2509
1742
|
}
|
|
2510
1743
|
else {
|
|
2511
|
-
|
|
2512
|
-
}
|
|
2513
|
-
}
|
|
2514
|
-
catch (error) {
|
|
2515
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
2516
|
-
console.error('[Ryunix ErrorBoundary] Caught error during render:', error);
|
|
2517
|
-
try {
|
|
2518
|
-
// Attempt to attach original JSX source map for DevOverlay lookup
|
|
2519
|
-
const src = fiber.props && fiber.props.__source;
|
|
2520
|
-
if (src && error && typeof error === 'object') {
|
|
2521
|
-
error.__ryunix_source = src;
|
|
2522
|
-
}
|
|
2523
|
-
let targetFiber = fiber;
|
|
2524
|
-
while (!error.__ryunix_source && targetFiber) {
|
|
2525
|
-
if (targetFiber.props && targetFiber.props.__source) {
|
|
2526
|
-
error.__ryunix_source = targetFiber.props.__source;
|
|
2527
|
-
}
|
|
2528
|
-
targetFiber = targetFiber.parent;
|
|
2529
|
-
}
|
|
2530
|
-
}
|
|
2531
|
-
catch (e) { }
|
|
1744
|
+
finalTitle = defaultTitle;
|
|
2532
1745
|
}
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
foundBoundary = true;
|
|
2541
|
-
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);
|
|
2542
1753
|
}
|
|
2543
|
-
|
|
1754
|
+
link.setAttribute('href', tags.canonical);
|
|
2544
1755
|
}
|
|
2545
|
-
|
|
2546
|
-
if (
|
|
2547
|
-
|
|
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);
|
|
2548
1766
|
}
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
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;
|
|
2556
1789
|
}
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
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 };
|
|
2562
1807
|
}
|
|
2563
1808
|
}
|
|
2564
|
-
|
|
2565
|
-
|
|
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];
|
|
2566
1815
|
}
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
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 }));
|
|
2580
1830
|
}
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
if (nextRoot.isHydrating !== undefined) {
|
|
2596
|
-
state.isHydrating = nextRoot.isHydrating;
|
|
2597
|
-
state.hydrateCursor = nextRoot.hydrateCursor;
|
|
2598
|
-
}
|
|
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;
|
|
2599
1845
|
}
|
|
2600
|
-
|
|
2601
|
-
|
|
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' });
|
|
2602
1874
|
}
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
}
|
|
2612
|
-
else {
|
|
2613
|
-
isWorkLoopScheduled = false;
|
|
2614
|
-
}
|
|
1875
|
+
}, [hash]);
|
|
1876
|
+
return createElement(route.component, {
|
|
1877
|
+
key: location,
|
|
1878
|
+
params,
|
|
1879
|
+
query,
|
|
1880
|
+
hash,
|
|
1881
|
+
location,
|
|
1882
|
+
});
|
|
2615
1883
|
};
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
const
|
|
2621
|
-
const
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
// Set immediate hydration state
|
|
2630
|
-
if (root.isHydrating !== undefined) {
|
|
2631
|
-
state.isHydrating = root.isHydrating;
|
|
2632
|
-
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;
|
|
2633
1897
|
}
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
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);
|
|
2644
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;
|
|
2645
1975
|
}
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
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);
|
|
2649
1987
|
}
|
|
2650
|
-
}
|
|
2651
|
-
};
|
|
2652
|
-
setScheduleWork(scheduleWork);
|
|
2653
|
-
|
|
2654
|
-
/**
|
|
2655
|
-
* @typedef {import('./createElement.js').RyunixNode} RyunixNode
|
|
2656
|
-
* @typedef {import('../types/internal.js').RyunixRootFiber} RyunixRootFiber
|
|
2657
|
-
* @typedef {import('../types/internal.js').RyunixComponent} RyunixComponent
|
|
2658
|
-
*/
|
|
2659
|
-
/**
|
|
2660
|
-
* The `render` function in JavaScript updates the DOM with a new element and schedules work to be done
|
|
2661
|
-
* on the element.
|
|
2662
|
-
* @param {RyunixNode} element
|
|
2663
|
-
* @param {Element | DocumentFragment} container
|
|
2664
|
-
* @returns {RyunixRootFiber}
|
|
2665
|
-
*/
|
|
2666
|
-
const render = (element, container) => {
|
|
2667
|
-
const state = getState();
|
|
2668
|
-
// Clear container before CSR render to avoid duplication
|
|
2669
|
-
clearContainer(/** @type {HTMLElement} */ container);
|
|
2670
|
-
/** @type {RyunixRootFiber} */
|
|
2671
|
-
const root = {
|
|
2672
|
-
dom: container,
|
|
2673
|
-
props: {
|
|
2674
|
-
children: [
|
|
2675
|
-
/** @type {import('../types/internal.js').RyunixNode} */ element,
|
|
2676
|
-
],
|
|
2677
|
-
},
|
|
2678
|
-
alternate: state.currentRoot,
|
|
2679
|
-
isHydrating: false,
|
|
2680
|
-
hydrateCursor: /** @type {ChildNode | null} */ null,
|
|
2681
1988
|
};
|
|
2682
|
-
|
|
2683
|
-
return root;
|
|
2684
|
-
};
|
|
2685
|
-
/**
|
|
2686
|
-
* @param {ChildNode | null} node
|
|
2687
|
-
* @returns {ChildNode | null}
|
|
2688
|
-
*/
|
|
2689
|
-
const nextValidSibling = (node) => {
|
|
2690
|
-
let next = node;
|
|
2691
|
-
while (next &&
|
|
2692
|
-
((next.nodeType === 3 && !next.nodeValue.trim()) ||
|
|
2693
|
-
next.nodeType === 8 ||
|
|
2694
|
-
(next.nodeType === 1 &&
|
|
2695
|
-
/** @type {Element} */ next.hasAttribute('data-ryunix-ssr')))) {
|
|
2696
|
-
next = next.nextSibling;
|
|
2697
|
-
}
|
|
2698
|
-
return next;
|
|
1989
|
+
return [state, setValue];
|
|
2699
1990
|
};
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
* @param {RyunixNode} element
|
|
2705
|
-
* @param {Element | DocumentFragment} container
|
|
2706
|
-
* @returns {RyunixRootFiber}
|
|
2707
|
-
*/
|
|
2708
|
-
const hydrate = (element, container) => {
|
|
2709
|
-
const state = getState();
|
|
2710
|
-
state.containerRoot = container;
|
|
2711
|
-
/** @type {RyunixRootFiber} */
|
|
2712
|
-
const root = {
|
|
2713
|
-
dom: container,
|
|
2714
|
-
props: {
|
|
2715
|
-
children: [
|
|
2716
|
-
/** @type {import('../types/internal.js').RyunixNode} */ element,
|
|
2717
|
-
],
|
|
2718
|
-
},
|
|
2719
|
-
alternate: state.currentRoot,
|
|
2720
|
-
isHydrating: true,
|
|
2721
|
-
hydrateCursor: nextValidSibling(container.firstChild),
|
|
1991
|
+
const useSwitch = (initialState = false) => {
|
|
1992
|
+
const [state, dispatch] = useStore(initialState);
|
|
1993
|
+
const toggle = () => {
|
|
1994
|
+
dispatch((prev) => !prev);
|
|
2722
1995
|
};
|
|
2723
|
-
|
|
2724
|
-
return root;
|
|
1996
|
+
return [state, toggle];
|
|
2725
1997
|
};
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
* @returns {RyunixRootFiber | undefined}
|
|
2731
|
-
*/
|
|
2732
|
-
const init = (MainElement, root = '__ryunix', components = {}) => {
|
|
1998
|
+
const useLayoutEffect = (callback, deps) => {
|
|
1999
|
+
if (typeof window === 'undefined') {
|
|
2000
|
+
return;
|
|
2001
|
+
}
|
|
2733
2002
|
const state = getState();
|
|
2734
|
-
state.
|
|
2735
|
-
|
|
2736
|
-
state.hydrationPolicy = getHydrationPolicy();
|
|
2737
|
-
state.scopedRecoveryQueue = [];
|
|
2738
|
-
state.hydrationRecover = false;
|
|
2739
|
-
// Reset any stale hydration flags
|
|
2740
|
-
state.isHydrating = false;
|
|
2741
|
-
state.hydrationFailed = false;
|
|
2742
|
-
// Auto-detect SSR based on child nodes - no need to manually set process.env.RYUNIX_SSR
|
|
2743
|
-
const hasChildNodes = state.containerRoot && state.containerRoot.hasChildNodes();
|
|
2744
|
-
if (process.env.NODE_ENV !== 'production' && process.env.RYUNIX_DEBUG) {
|
|
2745
|
-
console.log(`[Ryunix Debug] init: hasChildNodes=${hasChildNodes}, has SSR content detected.`);
|
|
2003
|
+
if (state.isServerRendering) {
|
|
2004
|
+
return;
|
|
2746
2005
|
}
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
if (hasChildNodes && ssrEnabled) {
|
|
2751
|
-
if (process.env.NODE_ENV !== 'production' && process.env.RYUNIX_DEBUG) {
|
|
2752
|
-
console.log(`[Ryunix Debug] init: SSR content detected. Starting hydration on #${root}`);
|
|
2753
|
-
}
|
|
2754
|
-
const res = hydrate(MainElement, state.containerRoot);
|
|
2755
|
-
return res;
|
|
2006
|
+
validateHookContext();
|
|
2007
|
+
if (!is.function(callback)) {
|
|
2008
|
+
throw new Error('useLayoutEffect callback must be a function');
|
|
2756
2009
|
}
|
|
2757
|
-
if (
|
|
2758
|
-
|
|
2010
|
+
if (deps !== undefined && !Array.isArray(deps)) {
|
|
2011
|
+
throw new Error('useLayoutEffect dependencies must be an array or undefined');
|
|
2759
2012
|
}
|
|
2760
|
-
const
|
|
2761
|
-
|
|
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++;
|
|
2762
2028
|
};
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
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++}:`;
|
|
2772
2037
|
}
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
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);
|
|
2776
2073
|
}
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
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;
|
|
2781
2083
|
};
|
|
2782
2084
|
|
|
2783
|
-
/**
|
|
2784
|
-
* @typedef {import('./createElement.js').RyunixNode} RyunixNode
|
|
2785
|
-
* @typedef {import('./createElement.js').RyunixElement} RyunixElement
|
|
2786
|
-
* @typedef {import('../types/internal.js').RyunixRenderToStringOptions} RyunixRenderToStringOptions
|
|
2787
|
-
* @typedef {Promise<{ success: boolean, id: string, content: string, error?: unknown }>} RyunixSuspenseTask
|
|
2788
|
-
*/
|
|
2789
|
-
/**
|
|
2790
|
-
* @param {unknown} unsafe
|
|
2791
|
-
* @returns {string}
|
|
2792
|
-
*/
|
|
2793
2085
|
const escapeHtml = (unsafe) => {
|
|
2794
2086
|
if (typeof unsafe !== 'string')
|
|
2795
2087
|
return String(unsafe);
|
|
@@ -2800,10 +2092,6 @@ const escapeHtml = (unsafe) => {
|
|
|
2800
2092
|
.replace(/"/g, '"')
|
|
2801
2093
|
.replace(/'/g, ''');
|
|
2802
2094
|
};
|
|
2803
|
-
/**
|
|
2804
|
-
* @param {Record<string, unknown>} styleObj
|
|
2805
|
-
* @returns {string}
|
|
2806
|
-
*/
|
|
2807
2095
|
const renderStyle = (styleObj) => {
|
|
2808
2096
|
if (!is.object(styleObj) || is.null(styleObj))
|
|
2809
2097
|
return '';
|
|
@@ -2828,10 +2116,6 @@ const VOID_ELEMENTS = new Set([
|
|
|
2828
2116
|
'track',
|
|
2829
2117
|
'wbr',
|
|
2830
2118
|
]);
|
|
2831
|
-
/**
|
|
2832
|
-
* @param {RyunixNode | RyunixNode[]} element
|
|
2833
|
-
* @returns {string}
|
|
2834
|
-
*/
|
|
2835
2119
|
const renderToStringImpl = (element) => {
|
|
2836
2120
|
if (element == null || typeof element === 'boolean') {
|
|
2837
2121
|
return '';
|
|
@@ -2842,11 +2126,9 @@ const renderToStringImpl = (element) => {
|
|
|
2842
2126
|
if (Array.isArray(element)) {
|
|
2843
2127
|
return element.map((child) => renderToStringImpl(child)).join('');
|
|
2844
2128
|
}
|
|
2845
|
-
/** @type {RyunixElement} */
|
|
2846
2129
|
const vnode = element;
|
|
2847
2130
|
if (vnode.type === RYUNIX_TYPES.TEXT_ELEMENT) {
|
|
2848
|
-
return escapeHtml(
|
|
2849
|
-
/** @type {import('./createElement.js').RyunixTextElement} */ vnode.props
|
|
2131
|
+
return escapeHtml(vnode.props
|
|
2850
2132
|
.nodeValue);
|
|
2851
2133
|
}
|
|
2852
2134
|
if (vnode.type === RYUNIX_TYPES.RYUNIX_FRAGMENT) {
|
|
@@ -2854,11 +2136,9 @@ const renderToStringImpl = (element) => {
|
|
|
2854
2136
|
return children.map((child) => renderToStringImpl(child)).join('');
|
|
2855
2137
|
}
|
|
2856
2138
|
if (vnode.type === RYUNIX_TYPES.RYUNIX_CONTEXT) {
|
|
2857
|
-
// Context Providers just render their children transparently on the server
|
|
2858
2139
|
const state = getState();
|
|
2859
2140
|
state.ssrContexts = state.ssrContexts || {};
|
|
2860
|
-
const ctxProps =
|
|
2861
|
-
/** @type {{ _contextId?: string, value?: unknown, children?: RyunixNode | RyunixNode[] }} */ vnode.props ||
|
|
2141
|
+
const ctxProps = vnode.props ||
|
|
2862
2142
|
{};
|
|
2863
2143
|
const ctxId = ctxProps._contextId;
|
|
2864
2144
|
const prevCtx = state.ssrContexts[ctxId];
|
|
@@ -2881,11 +2161,9 @@ const renderToStringImpl = (element) => {
|
|
|
2881
2161
|
if (typeof vnode.type === 'function') {
|
|
2882
2162
|
const type = vnode.type;
|
|
2883
2163
|
const props = vnode.props || {};
|
|
2884
|
-
const renderedElement =
|
|
2885
|
-
/** @type {(props: Record<string, unknown>) => RyunixNode} */ type(props);
|
|
2164
|
+
const renderedElement = type(props);
|
|
2886
2165
|
return renderToStringImpl(renderedElement);
|
|
2887
2166
|
}
|
|
2888
|
-
// It's a standard host element
|
|
2889
2167
|
const type = String(vnode.type);
|
|
2890
2168
|
const props = vnode.props || {};
|
|
2891
2169
|
let attributes = '';
|
|
@@ -2897,7 +2175,7 @@ const renderToStringImpl = (element) => {
|
|
|
2897
2175
|
htmlChildren = value.map((child) => renderToStringImpl(child)).join('');
|
|
2898
2176
|
}
|
|
2899
2177
|
else {
|
|
2900
|
-
htmlChildren = renderToStringImpl(
|
|
2178
|
+
htmlChildren = renderToStringImpl(value);
|
|
2901
2179
|
}
|
|
2902
2180
|
}
|
|
2903
2181
|
else if (key === 'dangerouslySetInnerHTML') {
|
|
@@ -2907,8 +2185,7 @@ const renderToStringImpl = (element) => {
|
|
|
2907
2185
|
}
|
|
2908
2186
|
}
|
|
2909
2187
|
else if (key === STRINGS.STYLE || key === OLD_STRINGS.STYLE) {
|
|
2910
|
-
const styleString = renderStyle(
|
|
2911
|
-
/** @type {Record<string, unknown>} */ value);
|
|
2188
|
+
const styleString = renderStyle(value);
|
|
2912
2189
|
if (styleString) {
|
|
2913
2190
|
attributes += ` style="${escapeHtml(styleString)}"`;
|
|
2914
2191
|
}
|
|
@@ -2919,6 +2196,8 @@ const renderToStringImpl = (element) => {
|
|
|
2919
2196
|
}
|
|
2920
2197
|
}
|
|
2921
2198
|
else if (!key.startsWith('on') &&
|
|
2199
|
+
key !== 'key' &&
|
|
2200
|
+
key !== 'ref' &&
|
|
2922
2201
|
key !== '__source' &&
|
|
2923
2202
|
key !== '__self') {
|
|
2924
2203
|
if (typeof value === 'boolean') {
|
|
@@ -2950,17 +2229,10 @@ function $RC(id, templateId) {
|
|
|
2950
2229
|
`
|
|
2951
2230
|
.replace(/\s+/g, ' ')
|
|
2952
2231
|
.trim();
|
|
2953
|
-
/**
|
|
2954
|
-
* @param {RyunixNode | RyunixNode[]} element
|
|
2955
|
-
* @param {(chunk: string) => void} push
|
|
2956
|
-
* @param {RyunixSuspenseTask[]} [suspenseTasks]
|
|
2957
|
-
* @returns {Promise<void>}
|
|
2958
|
-
*/
|
|
2959
2232
|
const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
2960
2233
|
if (element == null || typeof element === 'boolean') {
|
|
2961
2234
|
return;
|
|
2962
2235
|
}
|
|
2963
|
-
// Await the element if it's a promise (e.g. from an async Server Component directly rendered)
|
|
2964
2236
|
if (element instanceof Promise) {
|
|
2965
2237
|
element = await element;
|
|
2966
2238
|
if (element == null || typeof element === 'boolean')
|
|
@@ -2976,11 +2248,9 @@ const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
|
2976
2248
|
}
|
|
2977
2249
|
return;
|
|
2978
2250
|
}
|
|
2979
|
-
/** @type {RyunixElement} */
|
|
2980
2251
|
const vnode = element;
|
|
2981
2252
|
if (vnode.type === RYUNIX_TYPES.TEXT_ELEMENT) {
|
|
2982
|
-
push(escapeHtml(
|
|
2983
|
-
/** @type {import('./createElement.js').RyunixTextElement} */ vnode
|
|
2253
|
+
push(escapeHtml(vnode
|
|
2984
2254
|
.props.nodeValue));
|
|
2985
2255
|
return;
|
|
2986
2256
|
}
|
|
@@ -2994,8 +2264,7 @@ const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
|
2994
2264
|
if (vnode.type === RYUNIX_TYPES.RYUNIX_CONTEXT) {
|
|
2995
2265
|
const state = getState();
|
|
2996
2266
|
state.ssrContexts = state.ssrContexts || {};
|
|
2997
|
-
const ctxProps =
|
|
2998
|
-
/** @type {{ _contextId?: string, value?: unknown, children?: RyunixNode | RyunixNode[] }} */ vnode.props ||
|
|
2267
|
+
const ctxProps = vnode.props ||
|
|
2999
2268
|
{};
|
|
3000
2269
|
const ctxId = ctxProps._contextId;
|
|
3001
2270
|
const prevCtx = state.ssrContexts[ctxId];
|
|
@@ -3016,29 +2285,23 @@ const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
|
3016
2285
|
}
|
|
3017
2286
|
return;
|
|
3018
2287
|
}
|
|
3019
|
-
// Handle Suspense specifically
|
|
3020
2288
|
const suspenseType = vnode.type;
|
|
3021
2289
|
const isSuspenseBoundary = vnode.type === RYUNIX_TYPES.RYUNIX_SUSPENSE ||
|
|
3022
2290
|
(typeof suspenseType === 'object' &&
|
|
3023
2291
|
suspenseType != null &&
|
|
3024
|
-
|
|
2292
|
+
suspenseType.type ===
|
|
3025
2293
|
RYUNIX_TYPES.RYUNIX_SUSPENSE);
|
|
3026
2294
|
if (isSuspenseBoundary) {
|
|
3027
|
-
const suspenseProps =
|
|
3028
|
-
/** @type {{ fallback?: RyunixNode, children?: RyunixNode | RyunixNode[] }} */ vnode.props ||
|
|
2295
|
+
const suspenseProps = vnode.props ||
|
|
3029
2296
|
{};
|
|
3030
2297
|
const { fallback, children } = suspenseProps;
|
|
3031
2298
|
const id = `s-${Math.random().toString(36).slice(2, 9)}`;
|
|
3032
|
-
// In universal mode, Suspense renders children if ready, or fallback if pending.
|
|
3033
|
-
// BUT we want to force a background task for the REAL children if we hit a lazy component.
|
|
3034
2299
|
push(`<!--$?--><template id="B:${id}"></template><div id="S:${id}">`);
|
|
3035
|
-
// 1. Start rendering the actual content in the background
|
|
3036
2300
|
const task = (async () => {
|
|
3037
2301
|
const state = getState();
|
|
3038
2302
|
const wasBackground = state.isSuspenseBackground;
|
|
3039
2303
|
state.isSuspenseBackground = true;
|
|
3040
2304
|
let content = '';
|
|
3041
|
-
/** @param {string} chunk */
|
|
3042
2305
|
const subPush = (chunk) => {
|
|
3043
2306
|
content += chunk;
|
|
3044
2307
|
};
|
|
@@ -3054,7 +2317,6 @@ const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
|
3054
2317
|
}
|
|
3055
2318
|
})();
|
|
3056
2319
|
suspenseTasks.push(task);
|
|
3057
|
-
// 2. Render fallback immediately for the main stream
|
|
3058
2320
|
await renderToStreamImpl(fallback, push, suspenseTasks);
|
|
3059
2321
|
push(`</div><!--$/-->`);
|
|
3060
2322
|
return;
|
|
@@ -3065,11 +2327,10 @@ const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
|
3065
2327
|
if (process.env.RYUNIX_DEBUG) {
|
|
3066
2328
|
console.log('[SSR Debug] Rendering function:', type.name || 'anonymous');
|
|
3067
2329
|
}
|
|
3068
|
-
const renderedElement = await
|
|
2330
|
+
const renderedElement = await type(props);
|
|
3069
2331
|
await renderToStreamImpl(renderedElement, push, suspenseTasks);
|
|
3070
2332
|
return;
|
|
3071
2333
|
}
|
|
3072
|
-
// It's a standard host element
|
|
3073
2334
|
const hostTag = String(type);
|
|
3074
2335
|
let attributes = '';
|
|
3075
2336
|
let innerHTML = null;
|
|
@@ -3083,8 +2344,7 @@ const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
|
3083
2344
|
}
|
|
3084
2345
|
}
|
|
3085
2346
|
else if (key === STRINGS.STYLE || key === OLD_STRINGS.STYLE) {
|
|
3086
|
-
const styleString = renderStyle(
|
|
3087
|
-
/** @type {Record<string, unknown>} */ value);
|
|
2347
|
+
const styleString = renderStyle(value);
|
|
3088
2348
|
if (styleString) {
|
|
3089
2349
|
attributes += ` style="${escapeHtml(styleString)}"`;
|
|
3090
2350
|
}
|
|
@@ -3095,6 +2355,8 @@ const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
|
3095
2355
|
}
|
|
3096
2356
|
}
|
|
3097
2357
|
else if (!key.startsWith('on') &&
|
|
2358
|
+
key !== 'key' &&
|
|
2359
|
+
key !== 'ref' &&
|
|
3098
2360
|
key !== '__source' &&
|
|
3099
2361
|
key !== '__self') {
|
|
3100
2362
|
if (typeof value === 'boolean') {
|
|
@@ -3126,34 +2388,21 @@ const renderToStreamImpl = async (element, push, suspenseTasks = []) => {
|
|
|
3126
2388
|
push(`</${hostTag}>`);
|
|
3127
2389
|
}
|
|
3128
2390
|
};
|
|
3129
|
-
/**
|
|
3130
|
-
* @param {RyunixNode} element
|
|
3131
|
-
* @param {RyunixRenderToStringOptions} [options]
|
|
3132
|
-
* @returns {ReadableStream<Uint8Array>}
|
|
3133
|
-
*/
|
|
3134
2391
|
const renderToReadableStream = (element, options = {}) => {
|
|
3135
2392
|
const state = getState();
|
|
3136
2393
|
const encoder = new TextEncoder();
|
|
3137
|
-
// Reset idCounter for deterministic useId values
|
|
3138
2394
|
resetIdCounter();
|
|
3139
2395
|
return new ReadableStream({
|
|
3140
2396
|
async start(controller) {
|
|
3141
2397
|
const wasServerRendering = state.isServerRendering;
|
|
3142
2398
|
state.isServerRendering = true;
|
|
3143
2399
|
state.ssrMetadata = {};
|
|
3144
|
-
/** @param {string} text */
|
|
3145
2400
|
const push = (text) => controller.enqueue(encoder.encode(text));
|
|
3146
|
-
/** @type {RyunixSuspenseTask[]} */
|
|
3147
2401
|
const suspenseTasks = [];
|
|
3148
2402
|
try {
|
|
3149
|
-
// 0. Inject RC helper script first
|
|
3150
2403
|
const nonceAttr = options.nonce ? ` nonce="${options.nonce}"` : '';
|
|
3151
2404
|
push(`<script${nonceAttr} data-ryunix-ssr>${RC_SCRIPT}</script>`);
|
|
3152
|
-
// 1. Render initial tree (with fallbacks)
|
|
3153
2405
|
await renderToStreamImpl(element, push, suspenseTasks);
|
|
3154
|
-
// 2. Process suspense tasks as they complete
|
|
3155
|
-
// For now, we wait for all, but in a real streaming scenario,
|
|
3156
|
-
// we could push them as they resolve.
|
|
3157
2406
|
while (suspenseTasks.length > 0) {
|
|
3158
2407
|
const task = suspenseTasks.shift();
|
|
3159
2408
|
const res = await task;
|
|
@@ -3173,17 +2422,11 @@ const renderToReadableStream = (element, options = {}) => {
|
|
|
3173
2422
|
},
|
|
3174
2423
|
});
|
|
3175
2424
|
};
|
|
3176
|
-
/**
|
|
3177
|
-
* @param {RyunixNode} element
|
|
3178
|
-
* @param {RyunixRenderToStringOptions} [options]
|
|
3179
|
-
* @returns {string}
|
|
3180
|
-
*/
|
|
3181
2425
|
const renderToString = (element, options = {}) => {
|
|
3182
2426
|
const state = getState();
|
|
3183
2427
|
const wasServerRendering = state.isServerRendering;
|
|
3184
2428
|
state.isServerRendering = true;
|
|
3185
2429
|
state.ssrMetadata = {};
|
|
3186
|
-
// Reset idCounter for deterministic useId values
|
|
3187
2430
|
resetIdCounter();
|
|
3188
2431
|
try {
|
|
3189
2432
|
return renderToStringImpl(element);
|
|
@@ -3192,11 +2435,6 @@ const renderToString = (element, options = {}) => {
|
|
|
3192
2435
|
state.isServerRendering = wasServerRendering;
|
|
3193
2436
|
}
|
|
3194
2437
|
};
|
|
3195
|
-
/**
|
|
3196
|
-
* @param {RyunixNode} element
|
|
3197
|
-
* @param {RyunixRenderToStringOptions} [options]
|
|
3198
|
-
* @returns {Promise<string>}
|
|
3199
|
-
*/
|
|
3200
2438
|
const renderToStringAsync = async (element, options = {}) => {
|
|
3201
2439
|
const stream = renderToReadableStream(element, options);
|
|
3202
2440
|
const reader = stream.getReader();
|
|
@@ -3243,6 +2481,21 @@ function deepEqual(a, b) {
|
|
|
3243
2481
|
return keysA.every((key) => deepEqual(a[key], b[key]));
|
|
3244
2482
|
}
|
|
3245
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
|
+
|
|
3246
2499
|
const SUSPENSE_STATUS = {
|
|
3247
2500
|
PENDING: 'pending',
|
|
3248
2501
|
RESOLVED: 'resolved',
|
|
@@ -3335,6 +2588,45 @@ function preload(importFn) {
|
|
|
3335
2588
|
return importFn();
|
|
3336
2589
|
}
|
|
3337
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
|
+
|
|
3338
2630
|
const perfNow = () => typeof performance !== 'undefined' ? performance.now() : Date.now();
|
|
3339
2631
|
class Profiler {
|
|
3340
2632
|
enabled;
|
|
@@ -3456,43 +2748,10 @@ function withProfiler(Component, name) {
|
|
|
3456
2748
|
return Profiled;
|
|
3457
2749
|
}
|
|
3458
2750
|
|
|
3459
|
-
function forwardRef(render) {
|
|
3460
|
-
if (typeof render !== 'function') {
|
|
3461
|
-
throw new Error('forwardRef requires a render function');
|
|
3462
|
-
}
|
|
3463
|
-
const ForwardRefComponent = ((props) => {
|
|
3464
|
-
const { ref, ...restProps } = props || {};
|
|
3465
|
-
return render(restProps, ref ?? null);
|
|
3466
|
-
});
|
|
3467
|
-
const named = render;
|
|
3468
|
-
ForwardRefComponent.displayName = `ForwardRef(${named.displayName || named.name || 'Component'})`;
|
|
3469
|
-
ForwardRefComponent._isForwardRef = true;
|
|
3470
|
-
ForwardRefComponent._render = render;
|
|
3471
|
-
return ForwardRefComponent;
|
|
3472
|
-
}
|
|
3473
|
-
|
|
3474
|
-
/**
|
|
3475
|
-
* Wraps content rendered exclusively on the server. During hydration Ryunix
|
|
3476
|
-
* preserves the server HTML inside this boundary.
|
|
3477
|
-
*
|
|
3478
|
-
* @param {object} props
|
|
3479
|
-
* @param {import('./createElement.js').RyunixNode} [props.children]
|
|
3480
|
-
* @param {string} [props.id]
|
|
3481
|
-
* @returns {import('./createElement.js').RyunixElement}
|
|
3482
|
-
*/
|
|
3483
2751
|
function ServerBoundary({ children, id }) {
|
|
3484
2752
|
return createElement('div', { 'data-ryunix-server': id, style: { display: 'contents' } }, children);
|
|
3485
2753
|
}
|
|
3486
2754
|
ServerBoundary.ryunix_type = 'RYUNIX_SERVER_BOUNDARY';
|
|
3487
|
-
/**
|
|
3488
|
-
* Marks a DOM subtree for scoped hydration recovery. Mismatches inside this
|
|
3489
|
-
* boundary can be recovered locally without remounting the full app root.
|
|
3490
|
-
*
|
|
3491
|
-
* @param {object} props
|
|
3492
|
-
* @param {import('./createElement.js').RyunixNode} [props.children]
|
|
3493
|
-
* @param {string} [props.id]
|
|
3494
|
-
* @returns {import('./createElement.js').RyunixElement}
|
|
3495
|
-
*/
|
|
3496
2755
|
function HydrationBoundary({ children, id }) {
|
|
3497
2756
|
return createElement('div', {
|
|
3498
2757
|
'data-ryunix-hydrate-boundary': id ?? '',
|
|
@@ -3517,11 +2776,6 @@ function ErrorBoundary({ children, fallback, }) {
|
|
|
3517
2776
|
ErrorBoundary.ryunix_type =
|
|
3518
2777
|
'RYUNIX_ERROR_BOUNDARY';
|
|
3519
2778
|
|
|
3520
|
-
/**
|
|
3521
|
-
* Client proxy for a compiled server action.
|
|
3522
|
-
* @param {string} actionId - Build-time action identifier
|
|
3523
|
-
* @returns {(...args: unknown[]) => Promise<unknown>}
|
|
3524
|
-
*/
|
|
3525
2779
|
function createActionProxy(actionId) {
|
|
3526
2780
|
return async function (...args) {
|
|
3527
2781
|
const response = await fetch('/_ryunix/action', {
|
|
@@ -3540,80 +2794,66 @@ function createActionProxy(actionId) {
|
|
|
3540
2794
|
};
|
|
3541
2795
|
}
|
|
3542
2796
|
|
|
3543
|
-
/**
|
|
3544
|
-
* @typedef {object} OverlayError
|
|
3545
|
-
* @property {string} [name]
|
|
3546
|
-
* @property {string} [message]
|
|
3547
|
-
* @property {string | string[]} [stack]
|
|
3548
|
-
* @property {{ fileName?: string, lineNumber?: number }} [__ryunix_source]
|
|
3549
|
-
*/
|
|
3550
|
-
/**
|
|
3551
|
-
* @param {unknown} propsOrError
|
|
3552
|
-
*/
|
|
3553
2797
|
function RyunixDevOverlay(propsOrError) {
|
|
3554
|
-
|
|
3555
|
-
const propsInput =
|
|
3556
|
-
/** @type {Record<string, unknown> | Error | null | undefined} */ propsOrError;
|
|
3557
|
-
// If propsOrError is an event or wrapped object, try to extract error
|
|
2798
|
+
const propsInput = propsOrError;
|
|
3558
2799
|
const rawError = propsInput &&
|
|
3559
2800
|
typeof propsInput === 'object' &&
|
|
3560
2801
|
!(propsInput instanceof Error) &&
|
|
3561
2802
|
propsInput.nativeEvent
|
|
3562
2803
|
? propsInput.error
|
|
3563
2804
|
: propsInput;
|
|
3564
|
-
/** @type {OverlayError | null} */
|
|
3565
2805
|
let error = null;
|
|
3566
2806
|
if (rawError instanceof Error) {
|
|
3567
2807
|
error = rawError;
|
|
3568
2808
|
}
|
|
3569
2809
|
else if (rawError && typeof rawError === 'object') {
|
|
3570
2810
|
if ('message' in rawError) {
|
|
3571
|
-
error =
|
|
2811
|
+
error = rawError;
|
|
3572
2812
|
}
|
|
3573
2813
|
else if ('error' in rawError) {
|
|
3574
|
-
const nested =
|
|
2814
|
+
const nested = rawError.error;
|
|
3575
2815
|
error =
|
|
3576
2816
|
nested && typeof nested === 'object'
|
|
3577
|
-
?
|
|
2817
|
+
? nested
|
|
3578
2818
|
: null;
|
|
3579
2819
|
}
|
|
3580
2820
|
else {
|
|
3581
|
-
error =
|
|
2821
|
+
error = rawError;
|
|
3582
2822
|
}
|
|
3583
2823
|
}
|
|
3584
|
-
// Debug string if error is broken
|
|
3585
2824
|
const debugObjectStr = JSON.stringify(propsInput, Object.getOwnPropertyNames(propsInput || {}));
|
|
3586
2825
|
const [snippetState, setSnippet] = useStore(null);
|
|
3587
2826
|
const [startLineState, setStartLine] = useStore(1);
|
|
3588
2827
|
const [errorFileState, setErrorFile] = useStore('');
|
|
3589
2828
|
const [errorLineState, setErrorLine] = useStore(0);
|
|
3590
|
-
const snippet =
|
|
3591
|
-
const startLine =
|
|
3592
|
-
const errorFile =
|
|
3593
|
-
const errorLine =
|
|
3594
|
-
// Normalize stack ensuring we have lines
|
|
3595
|
-
/** @type {string[]} */
|
|
2829
|
+
const snippet = snippetState;
|
|
2830
|
+
const startLine = startLineState;
|
|
2831
|
+
const errorFile = errorFileState;
|
|
2832
|
+
const errorLine = errorLineState;
|
|
3596
2833
|
let stackLines = [];
|
|
3597
2834
|
if (error && error.stack) {
|
|
3598
2835
|
stackLines =
|
|
3599
2836
|
typeof error.stack === 'string'
|
|
3600
|
-
? error.stack.split('\n').filter((
|
|
2837
|
+
? error.stack.split('\n').filter((line) => {
|
|
3601
2838
|
const trimmed = line.trim();
|
|
3602
2839
|
if (!trimmed)
|
|
3603
2840
|
return false;
|
|
3604
2841
|
if (trimmed.includes('node_modules'))
|
|
3605
2842
|
return false;
|
|
3606
|
-
// Filter out internal Ryunix core framework files to isolate user code
|
|
3607
2843
|
const isInternal = [
|
|
3608
|
-
'
|
|
2844
|
+
'fiber-update.js',
|
|
3609
2845
|
'workers.js',
|
|
3610
2846
|
'reconciler.js',
|
|
3611
2847
|
'commits.js',
|
|
2848
|
+
'hooks/hooks.js',
|
|
3612
2849
|
'hooks.js',
|
|
2850
|
+
'error-boundary.js',
|
|
3613
2851
|
'errorBoundary.js',
|
|
2852
|
+
'boundaries.js',
|
|
3614
2853
|
'serverBoundary.js',
|
|
3615
2854
|
'app-router.js',
|
|
3616
2855
|
'app-router-server.js',
|
|
2856
|
+
'render/render.js',
|
|
3617
2857
|
'render.js',
|
|
3618
2858
|
'createElement.js',
|
|
3619
2859
|
'index.js',
|
|
@@ -3621,7 +2861,7 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3621
2861
|
return !isInternal;
|
|
3622
2862
|
})
|
|
3623
2863
|
: Array.isArray(error.stack)
|
|
3624
|
-
?
|
|
2864
|
+
? error.stack
|
|
3625
2865
|
: [];
|
|
3626
2866
|
}
|
|
3627
2867
|
const errorName = error && typeof error.name === 'string' ? error.name : 'Unknown Error Type';
|
|
@@ -3631,22 +2871,18 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3631
2871
|
useEffect(() => {
|
|
3632
2872
|
let targetPath = null;
|
|
3633
2873
|
let targetLine = null;
|
|
3634
|
-
// 1. Direct JSX __source mapping (injected by Webpack/SWC)
|
|
3635
2874
|
const ryunixSource = error?.__ryunix_source;
|
|
3636
2875
|
if (ryunixSource?.fileName) {
|
|
3637
2876
|
targetPath = ryunixSource.fileName;
|
|
3638
2877
|
targetLine = ryunixSource.lineNumber ?? null;
|
|
3639
2878
|
}
|
|
3640
|
-
// 2. Fallback to Regex Stack Parsing
|
|
3641
2879
|
if (!targetPath || !targetLine) {
|
|
3642
2880
|
for (let i = 0; i < stackLines.length; i++) {
|
|
3643
2881
|
const line = stackLines[i];
|
|
3644
2882
|
if (!line.includes(':'))
|
|
3645
2883
|
continue;
|
|
3646
|
-
// Deterministic string-based parsing (no regex on uncontrolled data)
|
|
3647
2884
|
let matchedPath = null;
|
|
3648
2885
|
let matchedLine = null;
|
|
3649
|
-
// V8 format: "at fn (file:line:col)" — extract content between parens
|
|
3650
2886
|
const parenOpen = line.indexOf('(');
|
|
3651
2887
|
const parenClose = line.lastIndexOf(')');
|
|
3652
2888
|
if (parenOpen !== -1 && parenClose > parenOpen) {
|
|
@@ -3662,7 +2898,6 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3662
2898
|
}
|
|
3663
2899
|
}
|
|
3664
2900
|
}
|
|
3665
|
-
// V8 format without parens: "at file:line:col"
|
|
3666
2901
|
if (!matchedPath) {
|
|
3667
2902
|
const trimmed = line.trim();
|
|
3668
2903
|
if (trimmed.startsWith('at ')) {
|
|
@@ -3679,7 +2914,6 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3679
2914
|
}
|
|
3680
2915
|
}
|
|
3681
2916
|
}
|
|
3682
|
-
// Ryunix format: "file.ryx:line" or "file.jsx:line"
|
|
3683
2917
|
if (!matchedPath) {
|
|
3684
2918
|
const exts = ['.ryx', '.jsx', '.js', '.ts', '.tsx'];
|
|
3685
2919
|
const c1 = line.lastIndexOf(':');
|
|
@@ -3787,7 +3021,6 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3787
3021
|
maxHeight: '150px',
|
|
3788
3022
|
height: 'auto',
|
|
3789
3023
|
};
|
|
3790
|
-
/** @param {boolean} isErrorLine */
|
|
3791
3024
|
const lineStyle = (isErrorLine) => ({
|
|
3792
3025
|
display: 'flex',
|
|
3793
3026
|
backgroundColor: isErrorLine ? 'rgba(239, 68, 68, 0.15)' : 'transparent',
|
|
@@ -3845,9 +3078,7 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3845
3078
|
}, createElement('path', {
|
|
3846
3079
|
d: 'M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z',
|
|
3847
3080
|
}), createElement('polyline', { points: '13 2 13 9 20 9' })), errorFile, ':', errorLine), snippet &&
|
|
3848
|
-
createElement('div', { style: snippetContainerStyle }, createElement('div', { style: { display: 'flex', flexDirection: 'column' } }, ...snippetLines.map((
|
|
3849
|
-
/** @type {string} */ lineText,
|
|
3850
|
-
/** @type {number} */ index) => {
|
|
3081
|
+
createElement('div', { style: snippetContainerStyle }, createElement('div', { style: { display: 'flex', flexDirection: 'column' } }, ...snippetLines.map((lineText, index) => {
|
|
3851
3082
|
const currentLineNumber = startLine + index;
|
|
3852
3083
|
const isErrorLine = currentLineNumber === errorLine;
|
|
3853
3084
|
return createElement('div', { key: index, style: lineStyle(isErrorLine) }, createElement('span', {
|
|
@@ -3884,16 +3115,14 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3884
3115
|
flexDirection: 'column',
|
|
3885
3116
|
gap: '12px',
|
|
3886
3117
|
},
|
|
3887
|
-
}, ...stackLines.map((
|
|
3118
|
+
}, ...stackLines.map((line, i) => {
|
|
3888
3119
|
if (i === 0 &&
|
|
3889
3120
|
(line.startsWith('Error:') ||
|
|
3890
3121
|
line.startsWith('TypeError:')))
|
|
3891
3122
|
return null;
|
|
3892
|
-
// Deterministic string-based stack frame parsing (no polynomial regex)
|
|
3893
3123
|
const trimmed = line.trim();
|
|
3894
3124
|
let fnName = '<anonymous>';
|
|
3895
3125
|
let filePath = line;
|
|
3896
|
-
// V8 format: "at fnName (file:line:col)" or "at file:line:col"
|
|
3897
3126
|
if (trimmed.startsWith('at ')) {
|
|
3898
3127
|
const rest = trimmed.slice(3);
|
|
3899
3128
|
const parenOpen = rest.indexOf('(');
|
|
@@ -3904,7 +3133,6 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3904
3133
|
filePath = rest.slice(parenOpen + 1, parenClose);
|
|
3905
3134
|
}
|
|
3906
3135
|
else {
|
|
3907
|
-
// "at file:line:col" — no function name
|
|
3908
3136
|
if (rest.includes(':')) {
|
|
3909
3137
|
fnName = '<anonymous>';
|
|
3910
3138
|
}
|
|
@@ -3914,13 +3142,11 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3914
3142
|
filePath = rest;
|
|
3915
3143
|
}
|
|
3916
3144
|
}
|
|
3917
|
-
// Firefox format: "fnName@file:line:col"
|
|
3918
3145
|
else if (trimmed.includes('@')) {
|
|
3919
3146
|
const atIdx = trimmed.indexOf('@');
|
|
3920
3147
|
fnName = trimmed.slice(0, atIdx) || '<anonymous>';
|
|
3921
3148
|
filePath = trimmed.slice(atIdx + 1);
|
|
3922
3149
|
}
|
|
3923
|
-
// Ryunix format: "fnName file.ext:line"
|
|
3924
3150
|
else {
|
|
3925
3151
|
const exts = ['.ryx', '.jsx', '.js', '.ts', '.tsx'];
|
|
3926
3152
|
const parts = trimmed.split(/\s+/);
|
|
@@ -3967,37 +3193,290 @@ function RyunixDevOverlay(propsOrError) {
|
|
|
3967
3193
|
: createElement('div', { style: { color: '#6b7280', fontStyle: 'italic' } }, 'No stack trace available.'))))));
|
|
3968
3194
|
}
|
|
3969
3195
|
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
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
|
+
}
|
|
3976
3444
|
|
|
3977
3445
|
var Ryunix = /*#__PURE__*/Object.freeze({
|
|
3978
3446
|
__proto__: null,
|
|
3979
3447
|
Children: Children,
|
|
3448
|
+
DEFAULT_THEME_COOKIE_NAME: DEFAULT_THEME_COOKIE_NAME,
|
|
3980
3449
|
ErrorBoundary: ErrorBoundary,
|
|
3450
|
+
Footer: Footer,
|
|
3981
3451
|
Fragment: Fragment,
|
|
3982
|
-
|
|
3452
|
+
Header: Header,
|
|
3453
|
+
Hooks: index,
|
|
3983
3454
|
HydrationBoundary: HydrationBoundary,
|
|
3984
3455
|
Link: Link,
|
|
3456
|
+
Main: Main,
|
|
3985
3457
|
NavLink: NavLink,
|
|
3986
3458
|
Priority: Priority,
|
|
3987
3459
|
RouterProvider: RouterProvider,
|
|
3988
3460
|
RyunixDevOverlay: RyunixDevOverlay,
|
|
3989
3461
|
ServerBoundary: ServerBoundary,
|
|
3990
3462
|
Suspense: Suspense,
|
|
3463
|
+
THEME_PREFERENCES: THEME_PREFERENCES,
|
|
3464
|
+
ThemeInitScript: ThemeInitScript,
|
|
3465
|
+
ThemeToggle: ThemeToggle,
|
|
3466
|
+
applyTheme: applyTheme,
|
|
3991
3467
|
batchUpdates: batchUpdates,
|
|
3992
3468
|
cloneElement: cloneElement,
|
|
3993
3469
|
createActionProxy: createActionProxy,
|
|
3994
3470
|
createContext: createContext,
|
|
3995
3471
|
createElement: createElement,
|
|
3996
3472
|
createPortal: createPortal,
|
|
3473
|
+
createThemeController: createThemeController,
|
|
3997
3474
|
deepEqual: deepEqual,
|
|
3998
3475
|
escapeHtml: escapeHtml,
|
|
3999
3476
|
forwardRef: forwardRef,
|
|
4000
3477
|
getState: getState,
|
|
3478
|
+
getSystemColorScheme: getSystemColorScheme,
|
|
3479
|
+
getThemeCookie: getThemeCookie,
|
|
4001
3480
|
hydrate: hydrate,
|
|
4002
3481
|
init: init,
|
|
4003
3482
|
isValidElement: isValidElement,
|
|
@@ -4015,8 +3494,13 @@ var Ryunix = /*#__PURE__*/Object.freeze({
|
|
|
4015
3494
|
renderToString: renderToString,
|
|
4016
3495
|
renderToStringAsync: renderToStringAsync,
|
|
4017
3496
|
resetIdCounter: resetIdCounter,
|
|
3497
|
+
resolveEffectiveTheme: resolveEffectiveTheme,
|
|
3498
|
+
resolveThemeFromCookie: resolveThemeFromCookie,
|
|
4018
3499
|
safeRender: safeRender,
|
|
3500
|
+
setThemeCookie: setThemeCookie,
|
|
4019
3501
|
shallowEqual: shallowEqual,
|
|
3502
|
+
themeController: themeController,
|
|
3503
|
+
themeInitScript: themeInitScript,
|
|
4020
3504
|
useCallback: useCallback,
|
|
4021
3505
|
useDebounce: useDebounce,
|
|
4022
3506
|
useDeferredValue: useDeferredValue,
|
|
@@ -4040,10 +3524,79 @@ var Ryunix = /*#__PURE__*/Object.freeze({
|
|
|
4040
3524
|
useSwitch: useSwitch,
|
|
4041
3525
|
useThrottle: useThrottle,
|
|
4042
3526
|
useTransition: useTransition,
|
|
3527
|
+
watchSystemTheme: watchSystemTheme,
|
|
4043
3528
|
withProfiler: withProfiler
|
|
4044
3529
|
});
|
|
4045
3530
|
|
|
4046
|
-
|
|
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;
|
|
4047
3600
|
|
|
4048
|
-
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 };
|
|
4049
3602
|
//# sourceMappingURL=Ryunix.esm.js.map
|