@barefootjs/client 0.1.3 → 0.3.0
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/runtime/apply-rest-attrs.d.ts.map +1 -1
- package/dist/runtime/component.d.ts.map +1 -1
- package/dist/runtime/index.js +219 -111
- package/dist/runtime/map-array.d.ts.map +1 -1
- package/dist/runtime/render.d.ts.map +1 -1
- package/dist/runtime/spread-attrs.d.ts +4 -7
- package/dist/runtime/spread-attrs.d.ts.map +1 -1
- package/dist/runtime/standalone.js +219 -111
- package/package.json +3 -4
- package/src/runtime/apply-rest-attrs.ts +28 -42
- package/src/runtime/component.ts +31 -20
- package/src/runtime/hydrate.ts +6 -0
- package/src/runtime/map-array.ts +11 -0
- package/src/runtime/render.ts +32 -10
- package/src/runtime/spread-attrs.ts +13 -63
|
@@ -314,6 +314,156 @@ var BF_KEY = "data-key";
|
|
|
314
314
|
var BF_ASYNC = "bf-async";
|
|
315
315
|
var BF_ASYNC_RESOLVE = "bf-async-resolve";
|
|
316
316
|
var BF_PARENT_SCOPE_PLACEHOLDER = "__BF_PARENT_SCOPE__";
|
|
317
|
+
// ../shared/src/dom-prop.ts
|
|
318
|
+
var BOOLEAN_ATTRS = new Set([
|
|
319
|
+
"checked",
|
|
320
|
+
"disabled",
|
|
321
|
+
"readonly",
|
|
322
|
+
"selected",
|
|
323
|
+
"required",
|
|
324
|
+
"hidden",
|
|
325
|
+
"autofocus",
|
|
326
|
+
"autoplay",
|
|
327
|
+
"controls",
|
|
328
|
+
"loop",
|
|
329
|
+
"muted",
|
|
330
|
+
"open",
|
|
331
|
+
"multiple",
|
|
332
|
+
"novalidate",
|
|
333
|
+
"formnovalidate"
|
|
334
|
+
]);
|
|
335
|
+
var SVG_CAMEL_TO_KEBAB = {
|
|
336
|
+
strokeWidth: "stroke-width",
|
|
337
|
+
strokeLinecap: "stroke-linecap",
|
|
338
|
+
strokeLinejoin: "stroke-linejoin",
|
|
339
|
+
strokeDasharray: "stroke-dasharray",
|
|
340
|
+
strokeDashoffset: "stroke-dashoffset",
|
|
341
|
+
strokeMiterlimit: "stroke-miterlimit",
|
|
342
|
+
strokeOpacity: "stroke-opacity",
|
|
343
|
+
fillOpacity: "fill-opacity",
|
|
344
|
+
fillRule: "fill-rule",
|
|
345
|
+
stopColor: "stop-color",
|
|
346
|
+
stopOpacity: "stop-opacity",
|
|
347
|
+
textAnchor: "text-anchor",
|
|
348
|
+
dominantBaseline: "dominant-baseline",
|
|
349
|
+
alignmentBaseline: "alignment-baseline",
|
|
350
|
+
fontFamily: "font-family",
|
|
351
|
+
fontSize: "font-size",
|
|
352
|
+
fontWeight: "font-weight",
|
|
353
|
+
fontStyle: "font-style",
|
|
354
|
+
letterSpacing: "letter-spacing",
|
|
355
|
+
wordSpacing: "word-spacing",
|
|
356
|
+
pointerEvents: "pointer-events",
|
|
357
|
+
vectorEffect: "vector-effect",
|
|
358
|
+
colorInterpolation: "color-interpolation",
|
|
359
|
+
clipPath: "clip-path",
|
|
360
|
+
clipRule: "clip-rule",
|
|
361
|
+
markerStart: "marker-start",
|
|
362
|
+
markerMid: "marker-mid",
|
|
363
|
+
markerEnd: "marker-end"
|
|
364
|
+
};
|
|
365
|
+
var SVG_XML_CAMEL_ATTRS = new Set([
|
|
366
|
+
"allowReorder",
|
|
367
|
+
"attributeName",
|
|
368
|
+
"attributeType",
|
|
369
|
+
"autoReverse",
|
|
370
|
+
"baseFrequency",
|
|
371
|
+
"baseProfile",
|
|
372
|
+
"calcMode",
|
|
373
|
+
"clipPathUnits",
|
|
374
|
+
"contentScriptType",
|
|
375
|
+
"contentStyleType",
|
|
376
|
+
"diffuseConstant",
|
|
377
|
+
"edgeMode",
|
|
378
|
+
"externalResourcesRequired",
|
|
379
|
+
"filterRes",
|
|
380
|
+
"filterUnits",
|
|
381
|
+
"glyphRef",
|
|
382
|
+
"gradientTransform",
|
|
383
|
+
"gradientUnits",
|
|
384
|
+
"kernelMatrix",
|
|
385
|
+
"kernelUnitLength",
|
|
386
|
+
"keyPoints",
|
|
387
|
+
"keySplines",
|
|
388
|
+
"keyTimes",
|
|
389
|
+
"lengthAdjust",
|
|
390
|
+
"limitingConeAngle",
|
|
391
|
+
"markerHeight",
|
|
392
|
+
"markerUnits",
|
|
393
|
+
"markerWidth",
|
|
394
|
+
"maskContentUnits",
|
|
395
|
+
"maskUnits",
|
|
396
|
+
"numOctaves",
|
|
397
|
+
"pathLength",
|
|
398
|
+
"patternContentUnits",
|
|
399
|
+
"patternTransform",
|
|
400
|
+
"patternUnits",
|
|
401
|
+
"pointsAtX",
|
|
402
|
+
"pointsAtY",
|
|
403
|
+
"pointsAtZ",
|
|
404
|
+
"preserveAlpha",
|
|
405
|
+
"preserveAspectRatio",
|
|
406
|
+
"primitiveUnits",
|
|
407
|
+
"refX",
|
|
408
|
+
"refY",
|
|
409
|
+
"repeatCount",
|
|
410
|
+
"repeatDur",
|
|
411
|
+
"requiredExtensions",
|
|
412
|
+
"requiredFeatures",
|
|
413
|
+
"specularConstant",
|
|
414
|
+
"specularExponent",
|
|
415
|
+
"spreadMethod",
|
|
416
|
+
"startOffset",
|
|
417
|
+
"stdDeviation",
|
|
418
|
+
"stitchTiles",
|
|
419
|
+
"surfaceScale",
|
|
420
|
+
"systemLanguage",
|
|
421
|
+
"tableValues",
|
|
422
|
+
"targetX",
|
|
423
|
+
"targetY",
|
|
424
|
+
"textLength",
|
|
425
|
+
"viewBox",
|
|
426
|
+
"viewTarget",
|
|
427
|
+
"xChannelSelector",
|
|
428
|
+
"yChannelSelector",
|
|
429
|
+
"zoomAndPan"
|
|
430
|
+
]);
|
|
431
|
+
function isEventProp(key) {
|
|
432
|
+
return key.length > 2 && key[0] === "o" && key[1] === "n" && key[2] >= "A" && key[2] <= "Z";
|
|
433
|
+
}
|
|
434
|
+
function classifyDOMProp(key) {
|
|
435
|
+
if (key === "children")
|
|
436
|
+
return { kind: "skip", attrName: key };
|
|
437
|
+
if (key === "ref")
|
|
438
|
+
return { kind: "ref", attrName: key };
|
|
439
|
+
if (isEventProp(key))
|
|
440
|
+
return { kind: "event", attrName: key };
|
|
441
|
+
const attrName = toHTMLAttrNameRuntime(key);
|
|
442
|
+
if (attrName === "style")
|
|
443
|
+
return { kind: "style", attrName };
|
|
444
|
+
if (attrName === "value")
|
|
445
|
+
return { kind: "property", attrName };
|
|
446
|
+
if (attrName === "checked")
|
|
447
|
+
return { kind: "property", attrName };
|
|
448
|
+
if (BOOLEAN_ATTRS.has(attrName.toLowerCase()))
|
|
449
|
+
return { kind: "boolean", attrName };
|
|
450
|
+
return { kind: "attr", attrName };
|
|
451
|
+
}
|
|
452
|
+
function toHTMLAttrNameRuntime(key) {
|
|
453
|
+
if (key === "className")
|
|
454
|
+
return "class";
|
|
455
|
+
if (key === "htmlFor")
|
|
456
|
+
return "for";
|
|
457
|
+
const svgKebab = SVG_CAMEL_TO_KEBAB[key];
|
|
458
|
+
if (svgKebab !== undefined)
|
|
459
|
+
return svgKebab;
|
|
460
|
+
if (SVG_XML_CAMEL_ATTRS.has(key))
|
|
461
|
+
return key;
|
|
462
|
+
if (key.startsWith("data") || key.startsWith("aria")) {
|
|
463
|
+
return key.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
464
|
+
}
|
|
465
|
+
return key;
|
|
466
|
+
}
|
|
317
467
|
// src/context.ts
|
|
318
468
|
function createContext(defaultValue) {
|
|
319
469
|
return {
|
|
@@ -957,6 +1107,8 @@ function hydrateCommentScope(comment) {
|
|
|
957
1107
|
const proxyEl = nextElementSibling2(comment) ?? comment.parentElement;
|
|
958
1108
|
if (!proxyEl)
|
|
959
1109
|
return;
|
|
1110
|
+
if (hydratedScopes.has(proxyEl))
|
|
1111
|
+
return;
|
|
960
1112
|
commentScopeRegistry.set(proxyEl, { commentNode: comment, scopeId });
|
|
961
1113
|
const parsed = parseProps(propsJson || null, `comment scope ${scopeId}`);
|
|
962
1114
|
const props = parsed[name] ?? {};
|
|
@@ -1010,9 +1162,14 @@ function createComponent(nameOrDef, props, key, slot) {
|
|
|
1010
1162
|
}
|
|
1011
1163
|
return result;
|
|
1012
1164
|
});
|
|
1165
|
+
const def = getRegisteredDef(name);
|
|
1166
|
+
const isCommentWrapper = def?.comment === true;
|
|
1167
|
+
const scopeId = isCommentWrapper ? null : `${name}_${generateId()}`;
|
|
1013
1168
|
const prevParentScopeId = _parentScopeId;
|
|
1014
1169
|
if (slot?.parent) {
|
|
1015
1170
|
_parentScopeId = slot.parent;
|
|
1171
|
+
} else if (scopeId) {
|
|
1172
|
+
_parentScopeId = scopeId;
|
|
1016
1173
|
}
|
|
1017
1174
|
let html;
|
|
1018
1175
|
try {
|
|
@@ -1025,10 +1182,8 @@ function createComponent(nameOrDef, props, key, slot) {
|
|
|
1025
1182
|
console.warn(`[BarefootJS] Template returned empty HTML for component: ${name}`);
|
|
1026
1183
|
return createPlaceholder(name, key);
|
|
1027
1184
|
}
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
if (!isCommentWrapper) {
|
|
1031
|
-
element.setAttribute(BF_SCOPE, `${name}_${generateId()}`);
|
|
1185
|
+
if (scopeId) {
|
|
1186
|
+
element.setAttribute(BF_SCOPE, scopeId);
|
|
1032
1187
|
}
|
|
1033
1188
|
if (slot) {
|
|
1034
1189
|
if (slot.parent)
|
|
@@ -1672,6 +1827,17 @@ function mapArray(accessor, container, getKey, renderItem, markerId) {
|
|
|
1672
1827
|
scopes.set(key, scope);
|
|
1673
1828
|
insertScope(scope, container, anchor);
|
|
1674
1829
|
}
|
|
1830
|
+
for (let i = items.length;i < existingRanges.length; i++) {
|
|
1831
|
+
const range = existingRanges[i];
|
|
1832
|
+
if (range.startMarker?.parentNode)
|
|
1833
|
+
range.startMarker.remove();
|
|
1834
|
+
if (range.primaryEl.parentNode)
|
|
1835
|
+
range.primaryEl.remove();
|
|
1836
|
+
for (const ex of range.extras) {
|
|
1837
|
+
if (ex.parentNode)
|
|
1838
|
+
ex.remove();
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1675
1841
|
return;
|
|
1676
1842
|
}
|
|
1677
1843
|
}
|
|
@@ -1766,13 +1932,6 @@ function styleToCss(value) {
|
|
|
1766
1932
|
}
|
|
1767
1933
|
|
|
1768
1934
|
// src/runtime/apply-rest-attrs.ts
|
|
1769
|
-
function toAttrName(key) {
|
|
1770
|
-
if (key === "className")
|
|
1771
|
-
return "class";
|
|
1772
|
-
if (key === "htmlFor")
|
|
1773
|
-
return "for";
|
|
1774
|
-
return key.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
1775
|
-
}
|
|
1776
1935
|
var jsxToDomEventMap = { doubleclick: "dblclick" };
|
|
1777
1936
|
function toEventName(jsxPropName) {
|
|
1778
1937
|
const raw = (jsxPropName[2].toLowerCase() + jsxPropName.slice(3)).toLowerCase();
|
|
@@ -1780,127 +1939,61 @@ function toEventName(jsxPropName) {
|
|
|
1780
1939
|
}
|
|
1781
1940
|
function applyRestAttrs(el, source, excludeKeys) {
|
|
1782
1941
|
const exclude = new Set(excludeKeys);
|
|
1942
|
+
const classified = [];
|
|
1783
1943
|
for (const key of Object.keys(source)) {
|
|
1784
1944
|
if (exclude.has(key))
|
|
1785
1945
|
continue;
|
|
1786
|
-
|
|
1946
|
+
classified.push({ key, c: classifyDOMProp(key) });
|
|
1947
|
+
}
|
|
1948
|
+
for (const { key, c } of classified) {
|
|
1949
|
+
if (c.kind === "ref") {
|
|
1787
1950
|
const ref = source[key];
|
|
1788
1951
|
if (typeof ref === "function")
|
|
1789
1952
|
ref(el);
|
|
1790
|
-
|
|
1791
|
-
}
|
|
1792
|
-
if (key.startsWith("on") && key.length > 2 && key[2] === key[2].toUpperCase()) {
|
|
1953
|
+
} else if (c.kind === "event") {
|
|
1793
1954
|
const handler = source[key];
|
|
1794
1955
|
if (typeof handler === "function") {
|
|
1795
1956
|
el.addEventListener(toEventName(key), handler);
|
|
1796
1957
|
}
|
|
1797
1958
|
}
|
|
1798
1959
|
}
|
|
1960
|
+
const attrEntries = classified.filter(({ c }) => c.kind !== "ref" && c.kind !== "event" && c.kind !== "skip");
|
|
1799
1961
|
createEffect(() => {
|
|
1800
|
-
for (const key of
|
|
1801
|
-
if (exclude.has(key))
|
|
1802
|
-
continue;
|
|
1803
|
-
if (key === "ref")
|
|
1804
|
-
continue;
|
|
1805
|
-
if (key === "children")
|
|
1806
|
-
continue;
|
|
1807
|
-
if (key.startsWith("on") && key.length > 2 && key[2] === key[2].toUpperCase())
|
|
1808
|
-
continue;
|
|
1962
|
+
for (const { key, c } of attrEntries) {
|
|
1809
1963
|
const value = source[key];
|
|
1810
|
-
const attr = toAttrName(key);
|
|
1811
1964
|
if (value != null && value !== false) {
|
|
1812
|
-
if (
|
|
1965
|
+
if (c.kind === "property" && c.attrName === "value" && "value" in el) {
|
|
1813
1966
|
const strVal = String(value);
|
|
1814
1967
|
if (el.value !== strVal)
|
|
1815
1968
|
el.value = strVal;
|
|
1816
|
-
} else if (
|
|
1969
|
+
} else if (c.kind === "property" && c.attrName === "checked" && "checked" in el) {
|
|
1817
1970
|
el.checked = !!value;
|
|
1818
|
-
} else if (
|
|
1971
|
+
} else if (c.kind === "boolean") {
|
|
1972
|
+
el.setAttribute(c.attrName, "");
|
|
1973
|
+
} else if (c.kind === "style") {
|
|
1819
1974
|
const css = styleToCss(value);
|
|
1820
1975
|
if (css == null)
|
|
1821
1976
|
el.removeAttribute("style");
|
|
1822
1977
|
else
|
|
1823
1978
|
el.setAttribute("style", css);
|
|
1824
1979
|
} else {
|
|
1825
|
-
el.setAttribute(
|
|
1980
|
+
el.setAttribute(c.attrName, String(value));
|
|
1826
1981
|
}
|
|
1827
1982
|
} else {
|
|
1828
|
-
if (
|
|
1983
|
+
if (c.kind === "property" && c.attrName === "value" && "value" in el) {
|
|
1984
|
+
el.value = "";
|
|
1985
|
+
} else if (c.kind === "property" && c.attrName === "checked" && "checked" in el) {
|
|
1829
1986
|
el.checked = false;
|
|
1987
|
+
} else if (c.kind === "boolean") {
|
|
1988
|
+
el.removeAttribute(c.attrName);
|
|
1830
1989
|
} else {
|
|
1831
|
-
el.removeAttribute(
|
|
1990
|
+
el.removeAttribute(c.attrName);
|
|
1832
1991
|
}
|
|
1833
1992
|
}
|
|
1834
1993
|
}
|
|
1835
1994
|
});
|
|
1836
1995
|
}
|
|
1837
1996
|
// src/runtime/spread-attrs.ts
|
|
1838
|
-
var SVG_CAMEL_CASE_ATTRS = new Set([
|
|
1839
|
-
"allowReorder",
|
|
1840
|
-
"attributeName",
|
|
1841
|
-
"attributeType",
|
|
1842
|
-
"autoReverse",
|
|
1843
|
-
"baseFrequency",
|
|
1844
|
-
"baseProfile",
|
|
1845
|
-
"calcMode",
|
|
1846
|
-
"clipPathUnits",
|
|
1847
|
-
"contentScriptType",
|
|
1848
|
-
"contentStyleType",
|
|
1849
|
-
"diffuseConstant",
|
|
1850
|
-
"edgeMode",
|
|
1851
|
-
"externalResourcesRequired",
|
|
1852
|
-
"filterRes",
|
|
1853
|
-
"filterUnits",
|
|
1854
|
-
"glyphRef",
|
|
1855
|
-
"gradientTransform",
|
|
1856
|
-
"gradientUnits",
|
|
1857
|
-
"kernelMatrix",
|
|
1858
|
-
"kernelUnitLength",
|
|
1859
|
-
"keyPoints",
|
|
1860
|
-
"keySplines",
|
|
1861
|
-
"keyTimes",
|
|
1862
|
-
"lengthAdjust",
|
|
1863
|
-
"limitingConeAngle",
|
|
1864
|
-
"markerHeight",
|
|
1865
|
-
"markerUnits",
|
|
1866
|
-
"markerWidth",
|
|
1867
|
-
"maskContentUnits",
|
|
1868
|
-
"maskUnits",
|
|
1869
|
-
"numOctaves",
|
|
1870
|
-
"pathLength",
|
|
1871
|
-
"patternContentUnits",
|
|
1872
|
-
"patternTransform",
|
|
1873
|
-
"patternUnits",
|
|
1874
|
-
"pointsAtX",
|
|
1875
|
-
"pointsAtY",
|
|
1876
|
-
"pointsAtZ",
|
|
1877
|
-
"preserveAlpha",
|
|
1878
|
-
"preserveAspectRatio",
|
|
1879
|
-
"primitiveUnits",
|
|
1880
|
-
"refX",
|
|
1881
|
-
"refY",
|
|
1882
|
-
"repeatCount",
|
|
1883
|
-
"repeatDur",
|
|
1884
|
-
"requiredExtensions",
|
|
1885
|
-
"requiredFeatures",
|
|
1886
|
-
"specularConstant",
|
|
1887
|
-
"specularExponent",
|
|
1888
|
-
"spreadMethod",
|
|
1889
|
-
"startOffset",
|
|
1890
|
-
"stdDeviation",
|
|
1891
|
-
"stitchTiles",
|
|
1892
|
-
"surfaceScale",
|
|
1893
|
-
"systemLanguage",
|
|
1894
|
-
"tableValues",
|
|
1895
|
-
"targetX",
|
|
1896
|
-
"targetY",
|
|
1897
|
-
"textLength",
|
|
1898
|
-
"viewBox",
|
|
1899
|
-
"viewTarget",
|
|
1900
|
-
"xChannelSelector",
|
|
1901
|
-
"yChannelSelector",
|
|
1902
|
-
"zoomAndPan"
|
|
1903
|
-
]);
|
|
1904
1997
|
function spreadAttrs(obj) {
|
|
1905
1998
|
if (!obj || typeof obj !== "object")
|
|
1906
1999
|
return "";
|
|
@@ -1908,18 +2001,20 @@ function spreadAttrs(obj) {
|
|
|
1908
2001
|
for (const [key, value] of Object.entries(obj)) {
|
|
1909
2002
|
if (value == null || value === false)
|
|
1910
2003
|
continue;
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
if (key === "children")
|
|
2004
|
+
const c = classifyDOMProp(key);
|
|
2005
|
+
if (c.kind === "event" || c.kind === "skip" || c.kind === "ref")
|
|
1914
2006
|
continue;
|
|
1915
|
-
if (
|
|
2007
|
+
if (c.kind === "style") {
|
|
1916
2008
|
const css = styleToCss(value);
|
|
1917
2009
|
if (css != null)
|
|
1918
2010
|
parts.push(`style="${css}"`);
|
|
1919
2011
|
continue;
|
|
1920
2012
|
}
|
|
1921
|
-
|
|
1922
|
-
|
|
2013
|
+
if (c.kind === "boolean" && value === true) {
|
|
2014
|
+
parts.push(c.attrName);
|
|
2015
|
+
} else {
|
|
2016
|
+
parts.push(`${c.attrName}="${value}"`);
|
|
2017
|
+
}
|
|
1923
2018
|
}
|
|
1924
2019
|
return parts.join(" ");
|
|
1925
2020
|
}
|
|
@@ -2181,17 +2276,30 @@ function render(container, nameOrDef, props = {}) {
|
|
|
2181
2276
|
}
|
|
2182
2277
|
const tpl = document.createElement("template");
|
|
2183
2278
|
tpl.innerHTML = html;
|
|
2184
|
-
const
|
|
2185
|
-
if (
|
|
2279
|
+
const rootElements = Array.from(tpl.content.childNodes).filter((n) => n.nodeType === Node.ELEMENT_NODE);
|
|
2280
|
+
if (rootElements.length === 0) {
|
|
2186
2281
|
throw new Error("[BarefootJS] render(): template returned empty HTML");
|
|
2187
2282
|
}
|
|
2188
|
-
if (!element.getAttribute(BF_SCOPE)) {
|
|
2189
|
-
element.setAttribute(BF_SCOPE, scopeId);
|
|
2190
|
-
}
|
|
2191
2283
|
container.innerHTML = "";
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2284
|
+
if (rootElements.length === 1) {
|
|
2285
|
+
const element = rootElements[0];
|
|
2286
|
+
if (!element.getAttribute(BF_SCOPE)) {
|
|
2287
|
+
element.setAttribute(BF_SCOPE, scopeId);
|
|
2288
|
+
}
|
|
2289
|
+
container.appendChild(element);
|
|
2290
|
+
init(element, props);
|
|
2291
|
+
hydratedScopes.add(element);
|
|
2292
|
+
return;
|
|
2293
|
+
}
|
|
2294
|
+
const commentNode = document.createComment(`${BF_SCOPE_COMMENT_PREFIX}${scopeId}`);
|
|
2295
|
+
container.appendChild(commentNode);
|
|
2296
|
+
for (const node of Array.from(tpl.content.childNodes)) {
|
|
2297
|
+
container.appendChild(node);
|
|
2298
|
+
}
|
|
2299
|
+
const proxyEl = rootElements[0];
|
|
2300
|
+
commentScopeRegistry.set(proxyEl, { commentNode, scopeId });
|
|
2301
|
+
init(proxyEl, props);
|
|
2302
|
+
hydratedScopes.add(proxyEl);
|
|
2195
2303
|
}
|
|
2196
2304
|
// src/runtime/streaming.ts
|
|
2197
2305
|
function __bf_swap(id) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "BarefootJS client package: reactive primitives (SSR-safe) plus browser runtime under the `/runtime` subpath (compiler target)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -55,10 +55,10 @@
|
|
|
55
55
|
"directory": "packages/client"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@barefootjs/shared": "0.
|
|
58
|
+
"@barefootjs/shared": "0.3.0"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
|
-
"@barefootjs/jsx": "0.
|
|
61
|
+
"@barefootjs/jsx": ">=0.2.0"
|
|
62
62
|
},
|
|
63
63
|
"peerDependenciesMeta": {
|
|
64
64
|
"@barefootjs/jsx": {
|
|
@@ -66,7 +66,6 @@
|
|
|
66
66
|
}
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
|
-
"@barefootjs/jsx": "0.1.3",
|
|
70
69
|
"@happy-dom/global-registrator": "^20.0.11",
|
|
71
70
|
"typescript": "^5.0.0"
|
|
72
71
|
}
|
|
@@ -6,16 +6,9 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { createEffect } from '@barefootjs/client/reactive'
|
|
9
|
+
import { classifyDOMProp, type DOMPropClassification } from '@barefootjs/shared'
|
|
9
10
|
import { styleToCss } from './style'
|
|
10
11
|
|
|
11
|
-
/** Map of JSX prop names to HTML attribute names */
|
|
12
|
-
function toAttrName(key: string): string {
|
|
13
|
-
if (key === 'className') return 'class'
|
|
14
|
-
if (key === 'htmlFor') return 'for'
|
|
15
|
-
// Convert camelCase to kebab-case for data-* and aria-* style attributes
|
|
16
|
-
return key.replace(/([A-Z])/g, '-$1').toLowerCase()
|
|
17
|
-
}
|
|
18
|
-
|
|
19
12
|
/**
|
|
20
13
|
* Convert a JSX event prop name to a DOM event name for addEventListener.
|
|
21
14
|
* Handles: camelCase → lowercase, plus special mappings (doubleclick → dblclick).
|
|
@@ -23,7 +16,6 @@ function toAttrName(key: string): string {
|
|
|
23
16
|
*/
|
|
24
17
|
const jsxToDomEventMap: Record<string, string> = { doubleclick: 'dblclick' }
|
|
25
18
|
function toEventName(jsxPropName: string): string {
|
|
26
|
-
// onKeyDown → 'k' + 'eyDown' → 'keydown'
|
|
27
19
|
const raw = (jsxPropName[2].toLowerCase() + jsxPropName.slice(3)).toLowerCase()
|
|
28
20
|
return jsxToDomEventMap[raw] ?? raw
|
|
29
21
|
}
|
|
@@ -43,15 +35,19 @@ export function applyRestAttrs(
|
|
|
43
35
|
): void {
|
|
44
36
|
const exclude = new Set(excludeKeys)
|
|
45
37
|
|
|
46
|
-
//
|
|
38
|
+
// Precompute classifications once — keys are stable for rest props.
|
|
39
|
+
const classified: Array<{ key: string; c: DOMPropClassification }> = []
|
|
47
40
|
for (const key of Object.keys(source)) {
|
|
48
41
|
if (exclude.has(key)) continue
|
|
49
|
-
|
|
42
|
+
classified.push({ key, c: classifyDOMProp(key) })
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Wire up event handlers and ref callbacks once (not reactively)
|
|
46
|
+
for (const { key, c } of classified) {
|
|
47
|
+
if (c.kind === 'ref') {
|
|
50
48
|
const ref = source[key]
|
|
51
49
|
if (typeof ref === 'function') (ref as (el: Element) => void)(el)
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
if (key.startsWith('on') && key.length > 2 && key[2] === key[2].toUpperCase()) {
|
|
50
|
+
} else if (c.kind === 'event') {
|
|
55
51
|
const handler = source[key]
|
|
56
52
|
if (typeof handler === 'function') {
|
|
57
53
|
el.addEventListener(toEventName(key), handler as EventListener)
|
|
@@ -59,49 +55,39 @@ export function applyRestAttrs(
|
|
|
59
55
|
}
|
|
60
56
|
}
|
|
61
57
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// Event handlers and ref are wired up above, not as attributes
|
|
67
|
-
if (key === 'ref') continue
|
|
68
|
-
// `children` is a JSX construct rendered inside the element, never
|
|
69
|
-
// a DOM attribute. Without this exclusion, parent components that
|
|
70
|
-
// pass `children` through `{...props}` end up with
|
|
71
|
-
// `children="<p ...>...</p>"` written as a literal attribute on
|
|
72
|
-
// the wrapper div. The matching `spreadAttrs` (SSR-string) path
|
|
73
|
-
// already skips `children` for the same reason.
|
|
74
|
-
if (key === 'children') continue
|
|
75
|
-
if (key.startsWith('on') && key.length > 2 && key[2] === key[2].toUpperCase()) continue
|
|
58
|
+
// Filter to only attr-like entries for the reactive loop
|
|
59
|
+
const attrEntries = classified.filter(
|
|
60
|
+
({ c }) => c.kind !== 'ref' && c.kind !== 'event' && c.kind !== 'skip',
|
|
61
|
+
)
|
|
76
62
|
|
|
63
|
+
createEffect(() => {
|
|
64
|
+
for (const { key, c } of attrEntries) {
|
|
77
65
|
const value = source[key]
|
|
78
|
-
const attr = toAttrName(key)
|
|
79
66
|
|
|
80
67
|
if (value != null && value !== false) {
|
|
81
|
-
|
|
82
|
-
if (attr === 'value' && 'value' in el) {
|
|
68
|
+
if (c.kind === 'property' && c.attrName === 'value' && 'value' in el) {
|
|
83
69
|
const strVal = String(value)
|
|
84
70
|
if ((el as HTMLInputElement).value !== strVal) (el as HTMLInputElement).value = strVal
|
|
85
|
-
} else if (
|
|
71
|
+
} else if (c.kind === 'property' && c.attrName === 'checked' && 'checked' in el) {
|
|
86
72
|
(el as HTMLInputElement).checked = !!value
|
|
87
|
-
} else if (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
// both reach the DOM as a real CSS string instead of
|
|
91
|
-
// `[object Object]`. Mirrors the compiler's
|
|
92
|
-
// `setAttribute('style', styleToCss(...))` path used when the
|
|
93
|
-
// attribute is bound directly on a JSX element.
|
|
73
|
+
} else if (c.kind === 'boolean') {
|
|
74
|
+
el.setAttribute(c.attrName, '')
|
|
75
|
+
} else if (c.kind === 'style') {
|
|
94
76
|
const css = styleToCss(value)
|
|
95
77
|
if (css == null) el.removeAttribute('style')
|
|
96
78
|
else el.setAttribute('style', css)
|
|
97
79
|
} else {
|
|
98
|
-
el.setAttribute(
|
|
80
|
+
el.setAttribute(c.attrName, String(value))
|
|
99
81
|
}
|
|
100
82
|
} else {
|
|
101
|
-
if (
|
|
83
|
+
if (c.kind === 'property' && c.attrName === 'value' && 'value' in el) {
|
|
84
|
+
(el as HTMLInputElement).value = ''
|
|
85
|
+
} else if (c.kind === 'property' && c.attrName === 'checked' && 'checked' in el) {
|
|
102
86
|
(el as HTMLInputElement).checked = false
|
|
87
|
+
} else if (c.kind === 'boolean') {
|
|
88
|
+
el.removeAttribute(c.attrName)
|
|
103
89
|
} else {
|
|
104
|
-
el.removeAttribute(
|
|
90
|
+
el.removeAttribute(c.attrName)
|
|
105
91
|
}
|
|
106
92
|
}
|
|
107
93
|
}
|
package/src/runtime/component.ts
CHANGED
|
@@ -124,13 +124,31 @@ export function createComponent(
|
|
|
124
124
|
return result
|
|
125
125
|
})
|
|
126
126
|
|
|
127
|
-
// 4.
|
|
127
|
+
// 4. Pre-generate the component's scope ID.
|
|
128
128
|
//
|
|
129
|
-
//
|
|
130
|
-
//
|
|
129
|
+
// `comment: true` components (synthesized inline-JSX-callback wrappers
|
|
130
|
+
// from #1211) render as transparent shells — the parsed `firstChild` is
|
|
131
|
+
// already the inner component's root with its own bf-s. Don't overwrite
|
|
132
|
+
// it (scopeId stays null), or `$c(__scope, 's0')` from the wrapper's
|
|
133
|
+
// init resolves to null.
|
|
134
|
+
const def = getRegisteredDef(name)
|
|
135
|
+
const isCommentWrapper = def?.comment === true
|
|
136
|
+
const scopeId = isCommentWrapper ? null : `${name}_${generateId()}`
|
|
137
|
+
|
|
138
|
+
// 5. Generate HTML from props.
|
|
139
|
+
//
|
|
140
|
+
// Thread the component's own scope ID into `_parentScopeId` for the
|
|
141
|
+
// template eval so renderChild() stamps parent-prefixed bf-s / bf-h /
|
|
142
|
+
// bf-m on child components — matching the SSR convention so a later
|
|
143
|
+
// `$c(scope, 'sN')` lookup resolves them. Without this, CSR-created
|
|
144
|
+
// children carry a random prefix and their event handlers never wire
|
|
145
|
+
// up (#1627). `slot.parent` takes precedence so hoisted-children
|
|
146
|
+
// placeholders (#1320) still resolve to the calling site's scope.
|
|
131
147
|
const prevParentScopeId = _parentScopeId
|
|
132
148
|
if (slot?.parent) {
|
|
133
149
|
_parentScopeId = slot.parent
|
|
150
|
+
} else if (scopeId) {
|
|
151
|
+
_parentScopeId = scopeId
|
|
134
152
|
}
|
|
135
153
|
let html: string
|
|
136
154
|
try {
|
|
@@ -139,7 +157,7 @@ export function createComponent(
|
|
|
139
157
|
_parentScopeId = prevParentScopeId
|
|
140
158
|
}
|
|
141
159
|
|
|
142
|
-
//
|
|
160
|
+
// 6. Create DOM element
|
|
143
161
|
const element = parseHTML(html.trim()).firstChild as HTMLElement
|
|
144
162
|
|
|
145
163
|
if (!element) {
|
|
@@ -147,16 +165,9 @@ export function createComponent(
|
|
|
147
165
|
return createPlaceholder(name, key)
|
|
148
166
|
}
|
|
149
167
|
|
|
150
|
-
//
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
// from #1211) render as transparent shells — the parsed `firstChild` is
|
|
154
|
-
// already the inner component's root with its own bf-s. Don't overwrite
|
|
155
|
-
// it, or `$c(__scope, 's0')` from the wrapper's init resolves to null.
|
|
156
|
-
const def = getRegisteredDef(name)
|
|
157
|
-
const isCommentWrapper = def?.comment === true
|
|
158
|
-
if (!isCommentWrapper) {
|
|
159
|
-
element.setAttribute(BF_SCOPE, `${name}_${generateId()}`)
|
|
168
|
+
// 7. Set scope ID and key attributes.
|
|
169
|
+
if (scopeId) {
|
|
170
|
+
element.setAttribute(BF_SCOPE, scopeId)
|
|
160
171
|
}
|
|
161
172
|
if (slot) {
|
|
162
173
|
if (slot.parent) element.setAttribute(BF_HOST, slot.parent)
|
|
@@ -166,18 +177,18 @@ export function createComponent(
|
|
|
166
177
|
element.setAttribute(BF_KEY, String(key))
|
|
167
178
|
}
|
|
168
179
|
|
|
169
|
-
//
|
|
180
|
+
// 8. Set currentScope so provideContext/useContext are element-scoped.
|
|
170
181
|
// This allows context providers in initFn to store context on this element.
|
|
171
182
|
const prevScope = setCurrentScope(element)
|
|
172
183
|
|
|
173
|
-
//
|
|
184
|
+
// 9. Initialize the component (context providers set up here).
|
|
174
185
|
const initFn = getComponentInit(name)
|
|
175
186
|
if (initFn) {
|
|
176
187
|
// Pass original props (with getters) for reactivity
|
|
177
188
|
initFn(element, props)
|
|
178
189
|
}
|
|
179
190
|
|
|
180
|
-
//
|
|
191
|
+
// 10. Evaluate getter children and insert them.
|
|
181
192
|
// Children are evaluated NOW (after initFn) so that context provided by
|
|
182
193
|
// the parent is in the global store when children call useContext().
|
|
183
194
|
if (childrenIsGetter) {
|
|
@@ -187,13 +198,13 @@ export function createComponent(
|
|
|
187
198
|
}
|
|
188
199
|
}
|
|
189
200
|
|
|
190
|
-
//
|
|
201
|
+
// 11. Restore previous scope
|
|
191
202
|
setCurrentScope(prevScope)
|
|
192
203
|
|
|
193
|
-
//
|
|
204
|
+
// 12. Mark element as initialized
|
|
194
205
|
hydratedScopes.add(element)
|
|
195
206
|
|
|
196
|
-
//
|
|
207
|
+
// 13. Store props and register update function for element reuse in reconcileList
|
|
197
208
|
propsMap.set(element, props)
|
|
198
209
|
registerPropsUpdate(element, name, props)
|
|
199
210
|
|