@barefootjs/client 0.21.4 → 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";
@@ -2885,6 +2922,7 @@ export {
2885
2922
  getComponentProps,
2886
2923
  getComponentInit,
2887
2924
  forwardProps,
2925
+ formatDate,
2888
2926
  flushHydration,
2889
2927
  findSiblingSlot,
2890
2928
  findScope,
@@ -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 {
@@ -3285,6 +3322,7 @@ export {
3285
3322
  getComponentProps,
3286
3323
  getComponentInit,
3287
3324
  forwardProps,
3325
+ formatDate,
3288
3326
  flushHydration,
3289
3327
  findSiblingSlot,
3290
3328
  findScope,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/client",
3
- "version": "0.21.4",
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.4"
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`).