@barefootjs/client 0.5.2 → 0.5.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"apply-rest-attrs.d.ts","sourceRoot":"","sources":["../../src/runtime/apply-rest-attrs.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAiBH;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,EAAE,EAAE,OAAO,EACX,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,WAAW,EAAE,MAAM,EAAE,GACpB,IAAI,CA4DN"}
1
+ {"version":3,"file":"apply-rest-attrs.d.ts","sourceRoot":"","sources":["../../src/runtime/apply-rest-attrs.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAiBH;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,EAAE,EAAE,OAAO,EACX,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,WAAW,EAAE,MAAM,EAAE,GACpB,IAAI,CAkEN"}
@@ -1,22 +1,2 @@
1
- /**
2
- * Branch-template slot helper (#1213).
3
- *
4
- * Conditional `template()` arrows interpolate Child-position expressions
5
- * via `${expr}`. When `expr` evaluates to a live `Node` (e.g. the result
6
- * of `_p.renderNode(node())` returning an `HTMLElement` from
7
- * `createComponent`), the surrounding template literal coerces it via
8
- * `Object.prototype.toString`, producing `"[object HTMLDivElement]"` and
9
- * destroying the live node identity on hydration.
10
- *
11
- * `__bfSlot` intercepts the value before stringification: if it's a
12
- * `Node`, it stashes the node into the closure-scoped `slots` array and
13
- * returns a unique marker comment. The `insert()` runtime then walks the
14
- * parsed fragment for those markers and splices the original node back
15
- * in by identity (no `cloneNode`), preserving event listeners and signal
16
- * bindings.
17
- *
18
- * Non-node values fall through to `String(value)` for the existing
19
- * inline-string path.
20
- */
21
1
  export declare function __bfSlot(value: unknown, slots: Node[]): string;
22
2
  //# sourceMappingURL=branch-slot.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"branch-slot.d.ts","sourceRoot":"","sources":["../../src/runtime/branch-slot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,CAW9D"}
1
+ {"version":3,"file":"branch-slot.d.ts","sourceRoot":"","sources":["../../src/runtime/branch-slot.ts"],"names":[],"mappings":"AA2BA,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,CAW9D"}
@@ -81,6 +81,28 @@ export declare function renderChild(name: string, props: Record<string, unknown>
81
81
  * back to ">" in the DOM attribute value, preserving CSS matching.
82
82
  */
83
83
  export declare function escapeAttrGt(html: string): string;
84
+ /**
85
+ * HTML-escape a single attribute *value* before it is concatenated into a
86
+ * client-rendered template string. Matches the SSR adapters' attribute
87
+ * escaping (Hono's `escapeToBuffer`: `& " ' < >`) so client-rendered DOM
88
+ * is byte-identical to the server-rendered form and metacharacter-bearing
89
+ * values (UnoCSS `[class*="size-"]`, `has-[>svg]`, …) don't corrupt
90
+ * attribute parsing when the template is inserted via `innerHTML`. `&` is
91
+ * replaced first so the emitted entities aren't themselves re-escaped.
92
+ */
93
+ export declare function escapeAttr(value: unknown): string;
94
+ /**
95
+ * HTML-escape interpolated **text content** before it is concatenated into
96
+ * a client-rendered template string (the `<!--bf:sN-->…<!--/-->` text
97
+ * slots). The HTML spec only requires `& < >` in text, but the SSR
98
+ * adapters (Hono) escape text with the same set as attribute values
99
+ * (`& " ' < >`), and the fixture-hydrate / CSR-conformance layer requires
100
+ * byte-parity with the server-rendered output — so this delegates to
101
+ * `escapeAttr`. Kept as a distinct export so generated code reads
102
+ * `escapeText(...)` at text sites (self-documenting) and so the two
103
+ * contexts can diverge later without touching call sites.
104
+ */
105
+ export declare function escapeText(value: unknown): string;
84
106
  /**
85
107
  * Parse an HTML string into a DocumentFragment, safely escaping ">" in
86
108
  * attribute values. All code that sets innerHTML on dynamic HTML should
@@ -1 +1 @@
1
- {"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../../src/runtime/component.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAS3C,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAExD;AAWD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH;;;GAGG;AACH;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAA;IACd,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAA;CACd;AAED,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,GAAG,YAAY,EAChC,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACnC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EACrB,IAAI,CAAC,EAAE,uBAAuB,GAC7B,WAAW,CAyIb;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAE3F;AAuBD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,SAAS,CAE7G;AAGD;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EACrB,UAAU,CAAC,EAAE,MAAM,GAClB,MAAM,CAiDR;AA+DD;;;;;GAKG;AACH;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD;AAID;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,GAAG,gBAAgB,CAcjF"}
1
+ {"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../../src/runtime/component.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAS3C,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAExD;AAWD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH;;;GAGG;AACH;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAA;IACd,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAA;CACd;AAED,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,GAAG,YAAY,EAChC,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACnC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EACrB,IAAI,CAAC,EAAE,uBAAuB,GAC7B,WAAW,CAyIb;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAE3F;AAuBD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,SAAS,CAE7G;AAGD;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EACrB,UAAU,CAAC,EAAE,MAAM,GAClB,MAAM,CAiDR;AA+DD;;;;;GAKG;AACH;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAOjD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEjD;AAID;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,GAAG,gBAAgB,CAcjF"}
@@ -10,7 +10,7 @@ export { reconcileElements, getLoopChildren, getLoopNodes } from './reconcile-el
10
10
  export { qsaItem, upsertChildItem } from './qsa-item';
11
11
  export { mapArray, mapArrayAnchored } from './map-array';
12
12
  export { registerTemplate, getTemplate, hasTemplate, type TemplateFn } from './template';
13
- export { createComponent, renderChild, getPropsUpdateFn, getComponentProps, parseHTML, } from './component';
13
+ export { createComponent, renderChild, getPropsUpdateFn, getComponentProps, parseHTML, escapeAttr, escapeText, } from './component';
14
14
  export { applyRestAttrs } from './apply-rest-attrs';
15
15
  export { spreadAttrs } from './spread-attrs';
16
16
  export { styleToCss } from './style';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAUA,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,sBAAsB,EACtB,UAAU,EACV,UAAU,EACV,SAAS,EACT,OAAO,EACP,OAAO,EACP,KAAK,EACL,KAAK,QAAQ,EACb,KAAK,MAAM,EACX,KAAK,IAAI,EACT,KAAK,SAAS,EACd,KAAK,QAAQ,GACd,MAAM,6BAA6B,CAAA;AAEpC,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAE,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,SAAS,CAAA;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAA;AAIlC,OAAO,EACL,aAAa,EACb,UAAU,EACV,cAAc,EACd,eAAe,EACf,KAAK,OAAO,GACb,MAAM,WAAW,CAAA;AAGlB,OAAO,EACL,YAAY,EACZ,WAAW,EACX,eAAe,EACf,wBAAwB,EACxB,KAAK,MAAM,EACX,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,cAAc,GACpB,MAAM,UAAU,CAAA;AAGjB,OAAO,EAAE,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,QAAQ,CAAA;AACzD,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACvF,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACrD,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAGxD,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAA;AAGxF,OAAO,EACL,eAAe,EACf,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,SAAS,GACV,MAAM,aAAa,CAAA;AAGpB,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAGpC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,aAAa,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AACnG,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AACnF,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACxF,OAAO,EAAE,MAAM,EAAE,KAAK,YAAY,EAAE,KAAK,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAC/E,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAGpD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAGlD,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAGjC,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAGvD,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAUA,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,sBAAsB,EACtB,UAAU,EACV,UAAU,EACV,SAAS,EACT,OAAO,EACP,OAAO,EACP,KAAK,EACL,KAAK,QAAQ,EACb,KAAK,MAAM,EACX,KAAK,IAAI,EACT,KAAK,SAAS,EACd,KAAK,QAAQ,GACd,MAAM,6BAA6B,CAAA;AAEpC,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAE,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,SAAS,CAAA;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAA;AAIlC,OAAO,EACL,aAAa,EACb,UAAU,EACV,cAAc,EACd,eAAe,EACf,KAAK,OAAO,GACb,MAAM,WAAW,CAAA;AAGlB,OAAO,EACL,YAAY,EACZ,WAAW,EACX,eAAe,EACf,wBAAwB,EACxB,KAAK,MAAM,EACX,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,cAAc,GACpB,MAAM,UAAU,CAAA;AAGjB,OAAO,EAAE,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,QAAQ,CAAA;AACzD,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACvF,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACrD,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAGxD,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAA;AAGxF,OAAO,EACL,eAAe,EACf,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,SAAS,EACT,UAAU,EACV,UAAU,GACX,MAAM,aAAa,CAAA;AAGpB,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAGpC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,aAAa,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AACnG,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AACnF,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACxF,OAAO,EAAE,MAAM,EAAE,KAAK,YAAY,EAAE,KAAK,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAC/E,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAGpD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAGlD,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAGjC,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAGvD,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA"}
@@ -248,6 +248,8 @@ function classifyDOMProp(key) {
248
248
  return { kind: "skip", attrName: key };
249
249
  if (key === "ref")
250
250
  return { kind: "ref", attrName: key };
251
+ if (key === "dangerouslySetInnerHTML")
252
+ return { kind: "innerHTML", attrName: key };
251
253
  if (isEventProp(key))
252
254
  return { kind: "event", attrName: key };
253
255
  const attrName = toHTMLAttrNameRuntime(key);
@@ -1104,6 +1106,12 @@ function unwrapPropsForTemplate(props) {
1104
1106
  function escapeAttrGt(html) {
1105
1107
  return html.replace(/"[^"]*"/g, (match) => match.replace(/>/g, "&gt;"));
1106
1108
  }
1109
+ function escapeAttr(value) {
1110
+ return String(value).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1111
+ }
1112
+ function escapeText(value) {
1113
+ return escapeAttr(value);
1114
+ }
1107
1115
  var SVG_NS = "http://www.w3.org/2000/svg";
1108
1116
  function parseHTML(html, parent) {
1109
1117
  const tpl = document.createElement("template");
@@ -1932,7 +1940,10 @@ function applyRestAttrs(el, source, excludeKeys) {
1932
1940
  createEffect2(() => {
1933
1941
  for (const { key, c } of attrEntries) {
1934
1942
  const value = source[key];
1935
- if (value != null && value !== false) {
1943
+ if (c.kind === "innerHTML") {
1944
+ const html = value;
1945
+ el.innerHTML = html != null && html.__html != null ? String(html.__html) : "";
1946
+ } else if (value != null && value !== false) {
1936
1947
  if (c.kind === "property" && c.attrName === "value" && "value" in el) {
1937
1948
  const strVal = String(value);
1938
1949
  if (el.value !== strVal)
@@ -1975,6 +1986,8 @@ function spreadAttrs(obj) {
1975
1986
  const c = classifyDOMProp(key);
1976
1987
  if (c.kind === "event" || c.kind === "skip" || c.kind === "ref")
1977
1988
  continue;
1989
+ if (c.kind === "innerHTML")
1990
+ continue;
1978
1991
  if (c.kind === "style") {
1979
1992
  const css = styleToCss(value);
1980
1993
  if (css != null)
@@ -2251,7 +2264,7 @@ function __bfSlot(value, slots) {
2251
2264
  if (Array.isArray(value)) {
2252
2265
  return value.map((v) => __bfSlot(v, slots)).join("");
2253
2266
  }
2254
- return String(value);
2267
+ return escapeText(value);
2255
2268
  }
2256
2269
  // src/runtime/dynamic-text.ts
2257
2270
  var END_MARKER = "/";
@@ -2445,6 +2458,8 @@ export {
2445
2458
  findSiblingSlot,
2446
2459
  findScope,
2447
2460
  find,
2461
+ escapeText,
2462
+ escapeAttr,
2448
2463
  cssEscape,
2449
2464
  createSignal2 as createSignal,
2450
2465
  createRoot2 as createRoot,
@@ -1 +1 @@
1
- {"version":3,"file":"spread-attrs.d.ts","sourceRoot":"","sources":["../../src/runtime/spread-attrs.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAmBhE"}
1
+ {"version":3,"file":"spread-attrs.d.ts","sourceRoot":"","sources":["../../src/runtime/spread-attrs.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAyBhE"}
@@ -439,6 +439,8 @@ function classifyDOMProp(key) {
439
439
  return { kind: "skip", attrName: key };
440
440
  if (key === "ref")
441
441
  return { kind: "ref", attrName: key };
442
+ if (key === "dangerouslySetInnerHTML")
443
+ return { kind: "innerHTML", attrName: key };
442
444
  if (isEventProp(key))
443
445
  return { kind: "event", attrName: key };
444
446
  const attrName = toHTMLAttrNameRuntime(key);
@@ -1294,6 +1296,12 @@ function unwrapPropsForTemplate(props) {
1294
1296
  function escapeAttrGt(html) {
1295
1297
  return html.replace(/"[^"]*"/g, (match) => match.replace(/>/g, "&gt;"));
1296
1298
  }
1299
+ function escapeAttr(value) {
1300
+ return String(value).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1301
+ }
1302
+ function escapeText(value) {
1303
+ return escapeAttr(value);
1304
+ }
1297
1305
  var SVG_NS = "http://www.w3.org/2000/svg";
1298
1306
  function parseHTML(html, parent) {
1299
1307
  const tpl = document.createElement("template");
@@ -2118,7 +2126,10 @@ function applyRestAttrs(el, source, excludeKeys) {
2118
2126
  createEffect(() => {
2119
2127
  for (const { key, c } of attrEntries) {
2120
2128
  const value = source[key];
2121
- if (value != null && value !== false) {
2129
+ if (c.kind === "innerHTML") {
2130
+ const html = value;
2131
+ el.innerHTML = html != null && html.__html != null ? String(html.__html) : "";
2132
+ } else if (value != null && value !== false) {
2122
2133
  if (c.kind === "property" && c.attrName === "value" && "value" in el) {
2123
2134
  const strVal = String(value);
2124
2135
  if (el.value !== strVal)
@@ -2161,6 +2172,8 @@ function spreadAttrs(obj) {
2161
2172
  const c = classifyDOMProp(key);
2162
2173
  if (c.kind === "event" || c.kind === "skip" || c.kind === "ref")
2163
2174
  continue;
2175
+ if (c.kind === "innerHTML")
2176
+ continue;
2164
2177
  if (c.kind === "style") {
2165
2178
  const css = styleToCss(value);
2166
2179
  if (css != null)
@@ -2436,7 +2449,7 @@ function __bfSlot(value, slots) {
2436
2449
  if (Array.isArray(value)) {
2437
2450
  return value.map((v) => __bfSlot(v, slots)).join("");
2438
2451
  }
2439
- return String(value);
2452
+ return escapeText(value);
2440
2453
  }
2441
2454
  // src/runtime/dynamic-text.ts
2442
2455
  var END_MARKER = "/";
@@ -2630,6 +2643,8 @@ export {
2630
2643
  findSiblingSlot,
2631
2644
  findScope,
2632
2645
  find,
2646
+ escapeText,
2647
+ escapeAttr,
2633
2648
  cssEscape,
2634
2649
  createSignal,
2635
2650
  createRoot,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/client",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
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,7 +55,7 @@
55
55
  "directory": "packages/client"
56
56
  },
57
57
  "dependencies": {
58
- "@barefootjs/shared": "0.5.2"
58
+ "@barefootjs/shared": "0.5.3"
59
59
  },
60
60
  "peerDependencies": {
61
61
  "@barefootjs/jsx": ">=0.2.0"
@@ -64,7 +64,13 @@ export function applyRestAttrs(
64
64
  for (const { key, c } of attrEntries) {
65
65
  const value = source[key]
66
66
 
67
- if (value != null && value !== false) {
67
+ if (c.kind === 'innerHTML') {
68
+ // `dangerouslySetInnerHTML={{ __html }}` arriving via a rest object —
69
+ // set the element's raw innerHTML (the escape hatch), never an
70
+ // attribute. Reactive: re-runs when `source` changes.
71
+ const html = value as { __html?: unknown } | null | undefined
72
+ el.innerHTML = html != null && html.__html != null ? String(html.__html) : ''
73
+ } else if (value != null && value !== false) {
68
74
  if (c.kind === 'property' && c.attrName === 'value' && 'value' in el) {
69
75
  const strVal = String(value)
70
76
  if ((el as HTMLInputElement).value !== strVal) (el as HTMLInputElement).value = strVal
@@ -15,9 +15,16 @@
15
15
  * in by identity (no `cloneNode`), preserving event listeners and signal
16
16
  * bindings.
17
17
  *
18
- * Non-node values fall through to `String(value)` for the existing
19
- * inline-string path.
18
+ * Non-node values fall through to the inline-string path, HTML-escaped
19
+ * (#1694 follow-up) so a branch-template text value containing `< / &`
20
+ * surfaces as text — not markup — and matches the SSR-rendered bytes.
21
+ * Escaping happens here, on the string path only, so the `<!--bf-slot:N-->`
22
+ * markers returned for live `Node` values are left intact for `insert()`
23
+ * to splice. (Doing it here rather than wrapping the whole `__bfSlot(...)`
24
+ * call in `escapeText` is what lets the marker path stay raw.)
20
25
  */
26
+ import { escapeText } from './component'
27
+
21
28
  export function __bfSlot(value: unknown, slots: Node[]): string {
22
29
  if (value == null || value === false || value === true) return ''
23
30
  if (typeof Node !== 'undefined' && value instanceof Node) {
@@ -28,5 +35,5 @@ export function __bfSlot(value: unknown, slots: Node[]): string {
28
35
  if (Array.isArray(value)) {
29
36
  return value.map((v) => __bfSlot(v, slots)).join('')
30
37
  }
31
- return String(value)
38
+ return escapeText(value)
32
39
  }
@@ -400,6 +400,39 @@ export function escapeAttrGt(html: string): string {
400
400
  return html.replace(/"[^"]*"/g, match => match.replace(/>/g, '&gt;'))
401
401
  }
402
402
 
403
+ /**
404
+ * HTML-escape a single attribute *value* before it is concatenated into a
405
+ * client-rendered template string. Matches the SSR adapters' attribute
406
+ * escaping (Hono's `escapeToBuffer`: `& " ' < >`) so client-rendered DOM
407
+ * is byte-identical to the server-rendered form and metacharacter-bearing
408
+ * values (UnoCSS `[class*="size-"]`, `has-[>svg]`, …) don't corrupt
409
+ * attribute parsing when the template is inserted via `innerHTML`. `&` is
410
+ * replaced first so the emitted entities aren't themselves re-escaped.
411
+ */
412
+ export function escapeAttr(value: unknown): string {
413
+ return String(value)
414
+ .replace(/&/g, '&amp;')
415
+ .replace(/"/g, '&quot;')
416
+ .replace(/'/g, '&#39;')
417
+ .replace(/</g, '&lt;')
418
+ .replace(/>/g, '&gt;')
419
+ }
420
+
421
+ /**
422
+ * HTML-escape interpolated **text content** before it is concatenated into
423
+ * a client-rendered template string (the `<!--bf:sN-->…<!--/-->` text
424
+ * slots). The HTML spec only requires `& < >` in text, but the SSR
425
+ * adapters (Hono) escape text with the same set as attribute values
426
+ * (`& " ' < >`), and the fixture-hydrate / CSR-conformance layer requires
427
+ * byte-parity with the server-rendered output — so this delegates to
428
+ * `escapeAttr`. Kept as a distinct export so generated code reads
429
+ * `escapeText(...)` at text sites (self-documenting) and so the two
430
+ * contexts can diverge later without touching call sites.
431
+ */
432
+ export function escapeText(value: unknown): string {
433
+ return escapeAttr(value)
434
+ }
435
+
403
436
  const SVG_NS = 'http://www.w3.org/2000/svg'
404
437
 
405
438
  /**
@@ -68,6 +68,8 @@ export {
68
68
  getPropsUpdateFn,
69
69
  getComponentProps,
70
70
  parseHTML,
71
+ escapeAttr,
72
+ escapeText,
71
73
  } from './component'
72
74
 
73
75
  // Spread props helpers
@@ -23,6 +23,12 @@ export function spreadAttrs(obj: Record<string, unknown>): string {
23
23
  if (value == null || value === false) continue
24
24
  const c = classifyDOMProp(key)
25
25
  if (c.kind === 'event' || c.kind === 'skip' || c.kind === 'ref') continue
26
+ // `dangerouslySetInnerHTML` is not an attribute — its `{ __html }` is the
27
+ // element's content, which this attribute-string builder can't emit. Skip
28
+ // it so a spread carrying it never serialises a bogus
29
+ // `dangerouslySetInnerHTML="[object Object]"`. (Content is set by the
30
+ // template for an explicit attr, or by `applyRestAttrs` for a rest object.)
31
+ if (c.kind === 'innerHTML') continue
26
32
  if (c.kind === 'style') {
27
33
  const css = styleToCss(value)
28
34
  if (css != null) parts.push(`style="${css}"`)