@barefootjs/client 0.21.3 → 0.23.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.
@@ -0,0 +1,30 @@
1
+ /**
2
+ * `formatDate` — format a `Date` (or ISO-8601 string) with an explicit pattern
3
+ * and an explicit UTC offset. The pure-function date formatter of #2324: every
4
+ * input is explicit, so the output is a deterministic function of its
5
+ * arguments — no host locale, no host timezone, no ICU/CLDR data. That is what
6
+ * lets the SSR adapters lower a `formatDate(...)` call to their native
7
+ * `format_date` runtime helper (spec/template-helpers.md) with byte-identical
8
+ * output on every backend.
9
+ *
10
+ * ```tsx
11
+ * <time>{formatDate(createdAt, 'YYYY/M/D', '+09:00')}</time>
12
+ * ```
13
+ *
14
+ * Pattern tokens (longest-match; any other character passes through
15
+ * literally): `YYYY` (4-digit zero-padded year, `-`-prefixed for negative
16
+ * years), `MM` / `M` (zero-padded / bare month `01`–`12` / `1`–`12`), `DD` /
17
+ * `D` (zero-padded / bare day of month). The v1 token set is date-only and
18
+ * numeric — locale-dependent fields (month names, weekday names) are a
19
+ * deliberate later stage of #2324, and per-locale *pattern selection* belongs
20
+ * to the app's i18n layer, not to this function.
21
+ *
22
+ * `timeZone` is `'UTC'` or a fixed offset `±HH:MM` (`'+09:00'`, `'-05:30'`).
23
+ * The function is total: any other value — including IANA zone names, which
24
+ * would drag host tzdata versions into the output — normalizes to `'UTC'`, so
25
+ * every backend degrades identically instead of diverging. An unset or
26
+ * unparseable `date` renders `''` (the same normative zero-value discipline as
27
+ * the `date` helper, spec/template-helpers.md).
28
+ */
29
+ export declare function formatDate(date: Date | string, pattern: string, timeZone?: string): string;
30
+ //# sourceMappingURL=format-date.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-date.d.ts","sourceRoot":"","sources":["../src/format-date.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AASH,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,SAAQ,GAAG,MAAM,CAkCzF"}
package/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export { __slot, type SlotMarker } from './slot.ts';
5
5
  export { forwardProps } from './forward-props.ts';
6
6
  export { unwrap } from './unwrap.ts';
7
7
  export { queryHref, type QueryParams, type QueryParamValue } from './query-href.ts';
8
+ export { formatDate } from './format-date.ts';
8
9
  export { createContext, type Context } from './context.ts';
9
10
  export { useContext, provideContext, createPortal, isSSRPortal, findSiblingSlot, cleanupPortalPlaceholder, type Portal, type PortalChildren, type PortalOptions, type Renderable, } from './shims.ts';
10
11
  export { Async, Region, type AsyncProps, type RegionProps, } from './builtins.ts';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,sBAAsB,EACtB,UAAU,EACV,cAAc,EACd,UAAU,EACV,SAAS,EACT,OAAO,EACP,OAAO,EACP,KAAK,EACL,eAAe,EACf,SAAS,EACT,OAAO,EACP,gBAAgB,EAMhB,kBAAkB,EAClB,KAAK,gBAAgB,EACrB,sBAAsB,EACtB,KAAK,QAAQ,EACb,KAAK,MAAM,EACX,KAAK,IAAI,EACT,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,iBAAiB,EACtB,KAAK,cAAc,GACpB,MAAM,6BAA6B,CAAA;AAEpC,OAAO,EACL,mBAAmB,EACnB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,aAAa,GACnB,MAAM,sBAAsB,CAAA;AAE7B,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE7C,OAAO,EAAE,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,WAAW,CAAA;AAEnD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEjD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEpC,OAAO,EAAE,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEnF,OAAO,EAAE,aAAa,EAAE,KAAK,OAAO,EAAE,MAAM,cAAc,CAAA;AAE1D,OAAO,EACL,UAAU,EACV,cAAc,EACd,YAAY,EACZ,WAAW,EACX,eAAe,EACf,wBAAwB,EACxB,KAAK,MAAM,EACX,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,UAAU,GAChB,MAAM,YAAY,CAAA;AAKnB,OAAO,EACL,KAAK,EACL,MAAM,EACN,KAAK,UAAU,EACf,KAAK,WAAW,GACjB,MAAM,eAAe,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,sBAAsB,EACtB,UAAU,EACV,cAAc,EACd,UAAU,EACV,SAAS,EACT,OAAO,EACP,OAAO,EACP,KAAK,EACL,eAAe,EACf,SAAS,EACT,OAAO,EACP,gBAAgB,EAMhB,kBAAkB,EAClB,KAAK,gBAAgB,EACrB,sBAAsB,EACtB,KAAK,QAAQ,EACb,KAAK,MAAM,EACX,KAAK,IAAI,EACT,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,iBAAiB,EACtB,KAAK,cAAc,GACpB,MAAM,6BAA6B,CAAA;AAEpC,OAAO,EACL,mBAAmB,EACnB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,aAAa,GACnB,MAAM,sBAAsB,CAAA;AAE7B,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE7C,OAAO,EAAE,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,WAAW,CAAA;AAEnD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEjD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEpC,OAAO,EAAE,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACnF,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE7C,OAAO,EAAE,aAAa,EAAE,KAAK,OAAO,EAAE,MAAM,cAAc,CAAA;AAE1D,OAAO,EACL,UAAU,EACV,cAAc,EACd,YAAY,EACZ,WAAW,EACX,eAAe,EACf,wBAAwB,EACxB,KAAK,MAAM,EACX,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,UAAU,GAChB,MAAM,YAAY,CAAA;AAKnB,OAAO,EACL,KAAK,EACL,MAAM,EACN,KAAK,UAAU,EACf,KAAK,WAAW,GACjB,MAAM,eAAe,CAAA"}
package/dist/index.js CHANGED
@@ -162,6 +162,43 @@ function queryHref(base, params) {
162
162
  const qs = u.toString();
163
163
  return qs ? `${base}?${qs}` : base;
164
164
  }
165
+ // src/format-date.ts
166
+ var OFFSET_RE = /^([+-])(\d{2}):(\d{2})$/;
167
+ var TOKEN_RE = /YYYY|MM|DD|M|D/g;
168
+ function pad2(n) {
169
+ return String(n).padStart(2, "0");
170
+ }
171
+ function formatDate(date, pattern, timeZone = "UTC") {
172
+ if (date === null || date === undefined)
173
+ return "";
174
+ const d = date instanceof Date ? date : new Date(date);
175
+ const t = d.getTime();
176
+ if (Number.isNaN(t))
177
+ return "";
178
+ const m = OFFSET_RE.exec(timeZone);
179
+ const offsetMinutes = m ? (m[1] === "-" ? -1 : 1) * (Number(m[2]) * 60 + Number(m[3])) : 0;
180
+ const s = new Date(t + offsetMinutes * 60000);
181
+ const year = s.getUTCFullYear();
182
+ const yyyy = (year < 0 ? "-" : "") + String(Math.abs(year)).padStart(4, "0");
183
+ const month = s.getUTCMonth() + 1;
184
+ const day = s.getUTCDate();
185
+ return pattern.replace(TOKEN_RE, (token) => {
186
+ switch (token) {
187
+ case "YYYY":
188
+ return yyyy;
189
+ case "MM":
190
+ return pad2(month);
191
+ case "M":
192
+ return String(month);
193
+ case "DD":
194
+ return pad2(day);
195
+ case "D":
196
+ return String(day);
197
+ default:
198
+ return token;
199
+ }
200
+ });
201
+ }
165
202
  // src/context.ts
166
203
  function createContext(defaultValue) {
167
204
  return {
@@ -216,6 +253,7 @@ export {
216
253
  onCleanup,
217
254
  isSSRPortal,
218
255
  forwardProps,
256
+ formatDate,
219
257
  findSiblingSlot,
220
258
  endTurn,
221
259
  createSignal,
@@ -5,6 +5,7 @@ export { __slot, type SlotMarker } from '../slot.ts';
5
5
  export { forwardProps } from '../forward-props.ts';
6
6
  export { unwrap } from '../unwrap.ts';
7
7
  export { queryHref, type QueryParams, type QueryParamValue } from '../query-href.ts';
8
+ export { formatDate } from '../format-date.ts';
8
9
  export { createContext, useContext, provideContext, setCurrentScope, type Context, } from './context.ts';
9
10
  export { createPortal, isSSRPortal, findSiblingSlot, cleanupPortalPlaceholder, type Portal, type PortalOptions, type Renderable, type PortalChildren, } from './portal.ts';
10
11
  export { reconcileList, type RenderItemFn } from './list.ts';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"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"}
@@ -162,6 +162,43 @@ function queryHref(base, params) {
162
162
  const qs = u.toString();
163
163
  return qs ? `${base}?${qs}` : base;
164
164
  }
165
+ // src/format-date.ts
166
+ var OFFSET_RE = /^([+-])(\d{2}):(\d{2})$/;
167
+ var TOKEN_RE = /YYYY|MM|DD|M|D/g;
168
+ function pad2(n) {
169
+ return String(n).padStart(2, "0");
170
+ }
171
+ function formatDate(date, pattern, timeZone = "UTC") {
172
+ if (date === null || date === undefined)
173
+ return "";
174
+ const d = date instanceof Date ? date : new Date(date);
175
+ const t = d.getTime();
176
+ if (Number.isNaN(t))
177
+ return "";
178
+ const m = OFFSET_RE.exec(timeZone);
179
+ const offsetMinutes = m ? (m[1] === "-" ? -1 : 1) * (Number(m[2]) * 60 + Number(m[3])) : 0;
180
+ const s = new Date(t + offsetMinutes * 60000);
181
+ const year = s.getUTCFullYear();
182
+ const yyyy = (year < 0 ? "-" : "") + String(Math.abs(year)).padStart(4, "0");
183
+ const month = s.getUTCMonth() + 1;
184
+ const day = s.getUTCDate();
185
+ return pattern.replace(TOKEN_RE, (token) => {
186
+ switch (token) {
187
+ case "YYYY":
188
+ return yyyy;
189
+ case "MM":
190
+ return pad2(month);
191
+ case "M":
192
+ return String(month);
193
+ case "DD":
194
+ return pad2(day);
195
+ case "D":
196
+ return String(day);
197
+ default:
198
+ return token;
199
+ }
200
+ });
201
+ }
165
202
  // ../shared/src/markers.ts
166
203
  var BF_SCOPE = "bf-s";
167
204
  var BF_SLOT = "bf";
@@ -656,6 +693,21 @@ function isInsideNestedCommentScope(element, scope) {
656
693
  }
657
694
  return false;
658
695
  }
696
+ function isInsideNestedChildScope(element, root) {
697
+ let current = element;
698
+ while (current !== root) {
699
+ const parent = current.parentNode;
700
+ if (!parent)
701
+ return false;
702
+ if (parent !== root && parent.nodeType === Node.ELEMENT_NODE && parent.hasAttribute(BF_SCOPE)) {
703
+ return true;
704
+ }
705
+ if (isTopLevelCommentScopeNode(current))
706
+ return true;
707
+ current = parent;
708
+ }
709
+ return false;
710
+ }
659
711
  function isTopLevelCommentScopeNode(node) {
660
712
  const parent = node.parentNode;
661
713
  if (!parent)
@@ -760,7 +812,14 @@ function qsa(el, selector) {
760
812
  }
761
813
  if (el.matches(selector))
762
814
  return el;
763
- return el.querySelector(selector);
815
+ const first = el.querySelector(selector);
816
+ if (!first || !isInsideNestedChildScope(first, el))
817
+ return first;
818
+ for (const candidate of el.querySelectorAll(selector)) {
819
+ if (candidate !== first && !isInsideNestedChildScope(candidate, el))
820
+ return candidate;
821
+ }
822
+ return null;
764
823
  }
765
824
  function splitTopLevelCommas(selector) {
766
825
  const out = [];
@@ -2863,6 +2922,7 @@ export {
2863
2922
  getComponentProps,
2864
2923
  getComponentInit,
2865
2924
  forwardProps,
2925
+ formatDate,
2866
2926
  flushHydration,
2867
2927
  findSiblingSlot,
2868
2928
  findScope,
@@ -61,6 +61,12 @@ export declare function findCondTarget(scope: Element, selector: string): Elemen
61
61
  * then its descendants. Unlike querySelector() which only searches descendants,
62
62
  * this also matches the root element.
63
63
  *
64
+ * Descendant candidates that fall inside a nested child component's own
65
+ * scope are skipped (`isInsideNestedChildScope`) — compiler slot IDs
66
+ * (`bf="sN"`) are assigned independently per component file, so a slot
67
+ * number can coincidentally collide between `el`'s own template and a
68
+ * child component mounted somewhere inside it (#2316).
69
+ *
64
70
  * Used by compiler-generated code for event binding and attribute updates
65
71
  * on loop items where the target may be the loop item's root element itself.
66
72
  */
@@ -1 +1 @@
1
- {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/runtime/query.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,sFAAsF;AACtF,eAAO,MAAM,SAAS,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAGJ,CAAA;AAqCnC;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI,EACjC,OAAO,CAAC,EAAE,OAAO,GAChB,OAAO,GAAG,IAAI,CAoDhB;AAiLD;;;;;;;;;;GAUG;AACH,wBAAgB,IAAI,CAClB,KAAK,EAAE,OAAO,GAAG,IAAI,EACrB,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,OAAO,GACpB,OAAO,GAAG,IAAI,CAiChB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAW/E;AAuBD;;;;;;;GAOG;AACH,wBAAgB,GAAG,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CA0BxE;AA+CD;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAU9E;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,EAAE,CAO1E;AAED;;;;;;;;GAQG;AACH,wBAAgB,CAAC,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,EAAE,GAAG,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC,EAAE,CAO7E;AAED;;;;;GAKG;AACH,wBAAgB,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,EAAE,GAAG,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC,EAAE,CAE9E;AAkDD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,OAAO,EACd,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,MAAM,EAAE,MAAM,GACb,OAAO,GAAG,IAAI,CAehB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAiB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC,CAkBnE;AAyDD;;;;;;;GAOG;AACH,wBAAgB,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,EAAE,GAAG,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,EAAE,CAkC3E;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAS3D;AAED,OAAO,EAAE,oBAAoB,IAAI,MAAM,EAAE,CAAA"}
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/runtime/query.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,sFAAsF;AACtF,eAAO,MAAM,SAAS,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAGJ,CAAA;AAqCnC;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI,EACjC,OAAO,CAAC,EAAE,OAAO,GAChB,OAAO,GAAG,IAAI,CAoDhB;AA2ND;;;;;;;;;;GAUG;AACH,wBAAgB,IAAI,CAClB,KAAK,EAAE,OAAO,GAAG,IAAI,EACrB,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,OAAO,GACpB,OAAO,GAAG,IAAI,CAiChB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAW/E;AAuBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,GAAG,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAsCxE;AA+CD;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAU9E;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,EAAE,CAO1E;AAED;;;;;;;;GAQG;AACH,wBAAgB,CAAC,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,EAAE,GAAG,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC,EAAE,CAO7E;AAED;;;;;GAKG;AACH,wBAAgB,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,EAAE,GAAG,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC,EAAE,CAE9E;AAkDD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,OAAO,EACd,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,MAAM,EAAE,MAAM,GACb,OAAO,GAAG,IAAI,CAehB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAiB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC,CAkBnE;AAyDD;;;;;;;GAOG;AACH,wBAAgB,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,EAAE,GAAG,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,EAAE,CAkC3E;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAS3D;AAED,OAAO,EAAE,oBAAoB,IAAI,MAAM,EAAE,CAAA"}
@@ -815,6 +815,43 @@ function queryHref(base, params) {
815
815
  const qs = u.toString();
816
816
  return qs ? `${base}?${qs}` : base;
817
817
  }
818
+ // src/format-date.ts
819
+ var OFFSET_RE = /^([+-])(\d{2}):(\d{2})$/;
820
+ var TOKEN_RE = /YYYY|MM|DD|M|D/g;
821
+ function pad2(n) {
822
+ return String(n).padStart(2, "0");
823
+ }
824
+ function formatDate(date, pattern, timeZone = "UTC") {
825
+ if (date === null || date === undefined)
826
+ return "";
827
+ const d = date instanceof Date ? date : new Date(date);
828
+ const t = d.getTime();
829
+ if (Number.isNaN(t))
830
+ return "";
831
+ const m = OFFSET_RE.exec(timeZone);
832
+ const offsetMinutes = m ? (m[1] === "-" ? -1 : 1) * (Number(m[2]) * 60 + Number(m[3])) : 0;
833
+ const s = new Date(t + offsetMinutes * 60000);
834
+ const year = s.getUTCFullYear();
835
+ const yyyy = (year < 0 ? "-" : "") + String(Math.abs(year)).padStart(4, "0");
836
+ const month = s.getUTCMonth() + 1;
837
+ const day = s.getUTCDate();
838
+ return pattern.replace(TOKEN_RE, (token) => {
839
+ switch (token) {
840
+ case "YYYY":
841
+ return yyyy;
842
+ case "MM":
843
+ return pad2(month);
844
+ case "M":
845
+ return String(month);
846
+ case "DD":
847
+ return pad2(day);
848
+ case "D":
849
+ return String(day);
850
+ default:
851
+ return token;
852
+ }
853
+ });
854
+ }
818
855
  // src/context.ts
819
856
  function createContext(defaultValue) {
820
857
  return {
@@ -1064,6 +1101,21 @@ function isInsideNestedCommentScope(element, scope) {
1064
1101
  }
1065
1102
  return false;
1066
1103
  }
1104
+ function isInsideNestedChildScope(element, root) {
1105
+ let current = element;
1106
+ while (current !== root) {
1107
+ const parent = current.parentNode;
1108
+ if (!parent)
1109
+ return false;
1110
+ if (parent !== root && parent.nodeType === Node.ELEMENT_NODE && parent.hasAttribute(BF_SCOPE)) {
1111
+ return true;
1112
+ }
1113
+ if (isTopLevelCommentScopeNode(current))
1114
+ return true;
1115
+ current = parent;
1116
+ }
1117
+ return false;
1118
+ }
1067
1119
  function isTopLevelCommentScopeNode(node) {
1068
1120
  const parent = node.parentNode;
1069
1121
  if (!parent)
@@ -1168,7 +1220,14 @@ function qsa(el, selector) {
1168
1220
  }
1169
1221
  if (el.matches(selector))
1170
1222
  return el;
1171
- return el.querySelector(selector);
1223
+ const first = el.querySelector(selector);
1224
+ if (!first || !isInsideNestedChildScope(first, el))
1225
+ return first;
1226
+ for (const candidate of el.querySelectorAll(selector)) {
1227
+ if (candidate !== first && !isInsideNestedChildScope(candidate, el))
1228
+ return candidate;
1229
+ }
1230
+ return null;
1172
1231
  }
1173
1232
  function splitTopLevelCommas(selector) {
1174
1233
  const out = [];
@@ -3263,6 +3322,7 @@ export {
3263
3322
  getComponentProps,
3264
3323
  getComponentInit,
3265
3324
  forwardProps,
3325
+ formatDate,
3266
3326
  flushHydration,
3267
3327
  findSiblingSlot,
3268
3328
  findScope,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/client",
3
- "version": "0.21.3",
3
+ "version": "0.23.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,7 +55,7 @@
55
55
  "directory": "packages/client"
56
56
  },
57
57
  "dependencies": {
58
- "@barefootjs/shared": "0.21.3"
58
+ "@barefootjs/shared": "0.23.0"
59
59
  },
60
60
  "peerDependencies": {
61
61
  "@barefootjs/jsx": ">=0.2.0"
@@ -0,0 +1,71 @@
1
+ /**
2
+ * `formatDate` — format a `Date` (or ISO-8601 string) with an explicit pattern
3
+ * and an explicit UTC offset. The pure-function date formatter of #2324: every
4
+ * input is explicit, so the output is a deterministic function of its
5
+ * arguments — no host locale, no host timezone, no ICU/CLDR data. That is what
6
+ * lets the SSR adapters lower a `formatDate(...)` call to their native
7
+ * `format_date` runtime helper (spec/template-helpers.md) with byte-identical
8
+ * output on every backend.
9
+ *
10
+ * ```tsx
11
+ * <time>{formatDate(createdAt, 'YYYY/M/D', '+09:00')}</time>
12
+ * ```
13
+ *
14
+ * Pattern tokens (longest-match; any other character passes through
15
+ * literally): `YYYY` (4-digit zero-padded year, `-`-prefixed for negative
16
+ * years), `MM` / `M` (zero-padded / bare month `01`–`12` / `1`–`12`), `DD` /
17
+ * `D` (zero-padded / bare day of month). The v1 token set is date-only and
18
+ * numeric — locale-dependent fields (month names, weekday names) are a
19
+ * deliberate later stage of #2324, and per-locale *pattern selection* belongs
20
+ * to the app's i18n layer, not to this function.
21
+ *
22
+ * `timeZone` is `'UTC'` or a fixed offset `±HH:MM` (`'+09:00'`, `'-05:30'`).
23
+ * The function is total: any other value — including IANA zone names, which
24
+ * would drag host tzdata versions into the output — normalizes to `'UTC'`, so
25
+ * every backend degrades identically instead of diverging. An unset or
26
+ * unparseable `date` renders `''` (the same normative zero-value discipline as
27
+ * the `date` helper, spec/template-helpers.md).
28
+ */
29
+
30
+ const OFFSET_RE = /^([+-])(\d{2}):(\d{2})$/
31
+ const TOKEN_RE = /YYYY|MM|DD|M|D/g
32
+
33
+ function pad2(n: number): string {
34
+ return String(n).padStart(2, '0')
35
+ }
36
+
37
+ export function formatDate(date: Date | string, pattern: string, timeZone = 'UTC'): string {
38
+ // Explicit nullish guard: `new Date(null)` is epoch 0, not Invalid Date,
39
+ // so without this a nil receiver would format 1970-01-01 instead of the
40
+ // contract's '' (spec/template-helpers.md "format_date", golden vector
41
+ // "nil receiver renders the empty string").
42
+ if (date === null || date === undefined) return ''
43
+ const d = date instanceof Date ? date : new Date(date)
44
+ const t = d.getTime()
45
+ if (Number.isNaN(t)) return ''
46
+ const m = OFFSET_RE.exec(timeZone)
47
+ const offsetMinutes = m ? (m[1] === '-' ? -1 : 1) * (Number(m[2]) * 60 + Number(m[3])) : 0
48
+ // Shift the instant by the offset, then read UTC fields: the shifted UTC
49
+ // clock face IS the local clock face at that offset.
50
+ const s = new Date(t + offsetMinutes * 60_000)
51
+ const year = s.getUTCFullYear()
52
+ const yyyy = (year < 0 ? '-' : '') + String(Math.abs(year)).padStart(4, '0')
53
+ const month = s.getUTCMonth() + 1
54
+ const day = s.getUTCDate()
55
+ return pattern.replace(TOKEN_RE, (token) => {
56
+ switch (token) {
57
+ case 'YYYY':
58
+ return yyyy
59
+ case 'MM':
60
+ return pad2(month)
61
+ case 'M':
62
+ return String(month)
63
+ case 'DD':
64
+ return pad2(day)
65
+ case 'D':
66
+ return String(day)
67
+ default:
68
+ return token
69
+ }
70
+ })
71
+ }
package/src/index.ts CHANGED
@@ -49,6 +49,7 @@ export { forwardProps } from './forward-props.ts'
49
49
  export { unwrap } from './unwrap.ts'
50
50
 
51
51
  export { queryHref, type QueryParams, type QueryParamValue } from './query-href.ts'
52
+ export { formatDate } from './format-date.ts'
52
53
 
53
54
  export { createContext, type Context } from './context.ts'
54
55
 
@@ -53,6 +53,7 @@ export { __slot, type SlotMarker } from '../slot.ts'
53
53
  export { forwardProps } from '../forward-props.ts'
54
54
  export { unwrap } from '../unwrap.ts'
55
55
  export { queryHref, type QueryParams, type QueryParamValue } from '../query-href.ts'
56
+ export { formatDate } from '../format-date.ts'
56
57
 
57
58
  // Context API (real DOM-bound implementations; `createContext` is the
58
59
  // same pure function re-exported from `../context`).
@@ -251,6 +251,48 @@ function isInsideNestedCommentScope(element: Element, scope: Element): boolean {
251
251
  return false
252
252
  }
253
253
 
254
+ /**
255
+ * True if `element` sits inside a nested child component's own scope
256
+ * boundary somewhere on the path up to `root` — a plain `[bf-s]` element
257
+ * (a regular child component's root) or a fragment-rooted child's
258
+ * comment-scope range mounted as a sibling along that path.
259
+ *
260
+ * Generalizes `isInsideNestedCommentScope` (which only catches the
261
+ * comment-scope case) to also catch plain `bf-s` boundaries, for `qsa()`
262
+ * — the loose, unfiltered sibling of `find()`'s `belongsToScope`-gated
263
+ * search. Unlike `belongsToScope`, this walks up to `root` by object
264
+ * identity rather than requiring `root` itself to carry `bf-s`: `qsa()`'s
265
+ * roots include `insert()`'s whole-component branch scope (which does
266
+ * carry `bf-s`) but also loop-item template clones (which normally don't),
267
+ * so a `.closest('[bf-s]') === root` check can't be reused here.
268
+ *
269
+ * Without this, a slot number that happens to collide between a component
270
+ * and a nested child mounted earlier in its DOM (compiler slot IDs are
271
+ * assigned independently per component file, so collisions are expected)
272
+ * makes `qsa()` return the child's element instead of the component's own.
273
+ *
274
+ * Deliberately does NOT reject `element` itself for carrying `bf-s`
275
+ * (unlike `belongsToScope`'s own-scope check) — `qsa()` doubles as the
276
+ * lookup for a nested child component's *own* scope root (e.g. compiled
277
+ * `qsa(parentScope, '[bf-h="X"][bf-m="sN"], [bf-s$="_sN"]')` calls feeding
278
+ * `initChild()`), and that target legitimately carries `bf-s`. Only an
279
+ * *intermediate* `bf-s` between `element` and `root` — i.e. `element`
280
+ * nested inside some other, unrelated child scope — disqualifies it.
281
+ */
282
+ function isInsideNestedChildScope(element: Element, root: Element): boolean {
283
+ let current: Node = element
284
+ while (current !== root) {
285
+ const parent: Node | null = current.parentNode
286
+ if (!parent) return false
287
+ if (parent !== root && parent.nodeType === Node.ELEMENT_NODE && (parent as Element).hasAttribute(BF_SCOPE)) {
288
+ return true
289
+ }
290
+ if (isTopLevelCommentScopeNode(current)) return true
291
+ current = parent
292
+ }
293
+ return false
294
+ }
295
+
254
296
  /**
255
297
  * True if `node` falls inside a `<!--bf-scope:...-->` … `<!--bf-/scope:...-->`
256
298
  * range among its own siblings — i.e. `node` is (or is inside) a top-level
@@ -409,6 +451,12 @@ function findInPortals(scopeId: string, selector: string): Element | null {
409
451
  * then its descendants. Unlike querySelector() which only searches descendants,
410
452
  * this also matches the root element.
411
453
  *
454
+ * Descendant candidates that fall inside a nested child component's own
455
+ * scope are skipped (`isInsideNestedChildScope`) — compiler slot IDs
456
+ * (`bf="sN"`) are assigned independently per component file, so a slot
457
+ * number can coincidentally collide between `el`'s own template and a
458
+ * child component mounted somewhere inside it (#2316).
459
+ *
412
460
  * Used by compiler-generated code for event binding and attribute updates
413
461
  * on loop items where the target may be the loop item's root element itself.
414
462
  */
@@ -437,7 +485,19 @@ export function qsa(el: Element | null, selector: string): Element | null {
437
485
  return qsaChildScope(el, selector)
438
486
  }
439
487
  if (el.matches(selector)) return el
440
- return el.querySelector(selector)
488
+
489
+ // Fast path: the overwhelmingly common case has no nested-child-scope
490
+ // collision, so a single querySelector() (matching the old behavior)
491
+ // resolves it without the cost of enumerating every match. Only fall
492
+ // back to the full querySelectorAll() scan — needed to skip past a
493
+ // rejected candidate to the next DOM-order match — when the first hit
494
+ // actually turns out to belong to a nested child scope.
495
+ const first = el.querySelector(selector)
496
+ if (!first || !isInsideNestedChildScope(first, el)) return first
497
+ for (const candidate of el.querySelectorAll(selector)) {
498
+ if (candidate !== first && !isInsideNestedChildScope(candidate, el)) return candidate
499
+ }
500
+ return null
441
501
  }
442
502
 
443
503
  /** Split a CSS selector list on top-level commas, ignoring commas inside