melina 1.3.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/jsx-dom.ts +48 -4
- package/src/runtime.ts +9 -21
- package/src/web.ts +4 -3
package/package.json
CHANGED
package/src/jsx-dom.ts
CHANGED
|
@@ -25,7 +25,45 @@ export function jsx(
|
|
|
25
25
|
return tag(finalProps);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
// SVG elements must be created with the SVG namespace
|
|
29
|
+
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
30
|
+
const SVG_TAGS = new Set([
|
|
31
|
+
'svg', 'path', 'circle', 'ellipse', 'line', 'polyline', 'polygon',
|
|
32
|
+
'rect', 'g', 'defs', 'use', 'text', 'tspan', 'clipPath', 'mask',
|
|
33
|
+
'image', 'pattern', 'linearGradient', 'radialGradient', 'stop',
|
|
34
|
+
'filter', 'feGaussianBlur', 'feOffset', 'feMerge', 'feMergeNode',
|
|
35
|
+
'foreignObject', 'marker', 'symbol', 'animate', 'animateTransform',
|
|
36
|
+
]);
|
|
37
|
+
const isSVG = SVG_TAGS.has(tag);
|
|
38
|
+
const el = isSVG
|
|
39
|
+
? document.createElementNS(SVG_NS, tag)
|
|
40
|
+
: document.createElement(tag);
|
|
41
|
+
|
|
42
|
+
// React camelCase → SVG dash-case attribute mapping
|
|
43
|
+
const SVG_ATTR_MAP: Record<string, string> = {
|
|
44
|
+
strokeWidth: 'stroke-width',
|
|
45
|
+
strokeLinecap: 'stroke-linecap',
|
|
46
|
+
strokeLinejoin: 'stroke-linejoin',
|
|
47
|
+
strokeDasharray: 'stroke-dasharray',
|
|
48
|
+
strokeDashoffset: 'stroke-dashoffset',
|
|
49
|
+
strokeMiterlimit: 'stroke-miterlimit',
|
|
50
|
+
strokeOpacity: 'stroke-opacity',
|
|
51
|
+
fillOpacity: 'fill-opacity',
|
|
52
|
+
fillRule: 'fill-rule',
|
|
53
|
+
clipRule: 'clip-rule',
|
|
54
|
+
clipPath: 'clip-path',
|
|
55
|
+
fontFamily: 'font-family',
|
|
56
|
+
fontSize: 'font-size',
|
|
57
|
+
fontWeight: 'font-weight',
|
|
58
|
+
textAnchor: 'text-anchor',
|
|
59
|
+
dominantBaseline: 'dominant-baseline',
|
|
60
|
+
colorInterpolation: 'color-interpolation',
|
|
61
|
+
colorInterpolationFilters: 'color-interpolation-filters',
|
|
62
|
+
floodColor: 'flood-color',
|
|
63
|
+
floodOpacity: 'flood-opacity',
|
|
64
|
+
lightingColor: 'lighting-color',
|
|
65
|
+
baselineShift: 'baseline-shift',
|
|
66
|
+
};
|
|
29
67
|
|
|
30
68
|
// Set attributes/properties
|
|
31
69
|
if (props) {
|
|
@@ -34,9 +72,13 @@ export function jsx(
|
|
|
34
72
|
if (value === null || value === undefined || value === false) continue;
|
|
35
73
|
|
|
36
74
|
if (key === 'style' && typeof value === 'object') {
|
|
37
|
-
Object.assign(el.style, value);
|
|
75
|
+
Object.assign((el as HTMLElement).style, value);
|
|
38
76
|
} else if (key === 'className' || key === 'class') {
|
|
39
|
-
|
|
77
|
+
if (isSVG) {
|
|
78
|
+
el.setAttribute('class', String(value));
|
|
79
|
+
} else {
|
|
80
|
+
(el as HTMLElement).className = String(value);
|
|
81
|
+
}
|
|
40
82
|
} else if (key === 'htmlFor') {
|
|
41
83
|
el.setAttribute('for', String(value));
|
|
42
84
|
} else if (key === 'dangerouslySetInnerHTML') {
|
|
@@ -50,7 +92,9 @@ export function jsx(
|
|
|
50
92
|
} else if (value === true) {
|
|
51
93
|
el.setAttribute(key, '');
|
|
52
94
|
} else {
|
|
53
|
-
|
|
95
|
+
// Map React camelCase SVG attributes to dash-case
|
|
96
|
+
const attr = isSVG ? (SVG_ATTR_MAP[key] || key) : key;
|
|
97
|
+
el.setAttribute(attr, String(value));
|
|
54
98
|
}
|
|
55
99
|
}
|
|
56
100
|
|
package/src/runtime.ts
CHANGED
|
@@ -149,17 +149,12 @@ async function navigate(href: string) {
|
|
|
149
149
|
window.scrollTo(0, 0);
|
|
150
150
|
};
|
|
151
151
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
} else {
|
|
159
|
-
updateDOM();
|
|
160
|
-
mountPage(didPartialSwap);
|
|
161
|
-
window.dispatchEvent(new CustomEvent('melina:navigated'));
|
|
162
|
-
}
|
|
152
|
+
// Run the DOM update directly — no View Transitions for partial swaps.
|
|
153
|
+
// View Transitions snapshot the entire viewport, causing untouched
|
|
154
|
+
// layout elements (like messenger) to visually flicker.
|
|
155
|
+
updateDOM();
|
|
156
|
+
mountPage(didPartialSwap);
|
|
157
|
+
window.dispatchEvent(new CustomEvent('melina:navigated'));
|
|
163
158
|
|
|
164
159
|
} catch (error) {
|
|
165
160
|
window.location.href = href;
|
|
@@ -226,16 +221,9 @@ function initializeLinkInterception() {
|
|
|
226
221
|
}
|
|
227
222
|
};
|
|
228
223
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
window.dispatchEvent(new CustomEvent('melina:navigated'));
|
|
233
|
-
});
|
|
234
|
-
} else {
|
|
235
|
-
updateDOM();
|
|
236
|
-
mountPage(didPartialSwap);
|
|
237
|
-
window.dispatchEvent(new CustomEvent('melina:navigated'));
|
|
238
|
-
}
|
|
224
|
+
updateDOM();
|
|
225
|
+
mountPage(didPartialSwap);
|
|
226
|
+
window.dispatchEvent(new CustomEvent('melina:navigated'));
|
|
239
227
|
})
|
|
240
228
|
.catch(err => {
|
|
241
229
|
window.location.reload();
|
package/src/web.ts
CHANGED
|
@@ -1268,8 +1268,8 @@ export function createAppRouter(options: AppRouterOptions = {}): Handler {
|
|
|
1268
1268
|
|
|
1269
1269
|
if (isStringMode) {
|
|
1270
1270
|
// ─── String mode: components return raw HTML strings ───
|
|
1271
|
-
// Page content is already a string, wrap
|
|
1272
|
-
let content = pageContent as string
|
|
1271
|
+
// Page content is already a string, wrap in #melina-page-content then layouts
|
|
1272
|
+
let content = `<div id="melina-page-content" style="display:contents">${pageContent as string}</div>`;
|
|
1273
1273
|
for (let i = match.route.layouts.length - 1; i >= 0; i--) {
|
|
1274
1274
|
const layoutPath = match.route.layouts[i];
|
|
1275
1275
|
const layoutModule = await importSSR(layoutPath);
|
|
@@ -1289,7 +1289,8 @@ export function createAppRouter(options: AppRouterOptions = {}): Handler {
|
|
|
1289
1289
|
const React = await import('react');
|
|
1290
1290
|
const ReactDOMServer = await import('react-dom/server');
|
|
1291
1291
|
|
|
1292
|
-
|
|
1292
|
+
// Wrap page content in #melina-page-content for partial swap
|
|
1293
|
+
let tree: any = React.createElement('div', { id: 'melina-page-content', style: { display: 'contents' } }, pageContent);
|
|
1293
1294
|
|
|
1294
1295
|
// Wrap with layouts (innermost to outermost)
|
|
1295
1296
|
for (let i = match.route.layouts.length - 1; i >= 0; i--) {
|