@barefootjs/client 0.21.4 → 0.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,37 @@
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
+ * <time>{formatDate(createdAt, 'MMMM D, YYYY', 'UTC', names)}</time>
13
+ * ```
14
+ *
15
+ * Numeric tokens (longest-match; any other character passes through
16
+ * literally): `YYYY` (4-digit zero-padded year, `-`-prefixed for negative
17
+ * years), `MM` / `M` (zero-padded / bare month `01`–`12` / `1`–`12`), `DD` /
18
+ * `D` (zero-padded / bare day of month).
19
+ *
20
+ * Name tokens (#2334) read the explicit `names` table — the caller owns the
21
+ * values (per-locale name selection is the i18n layer's or the compiler's
22
+ * job, never this function's): `MMMM` / `MMM` (wide / abbreviated month
23
+ * name), `dddd` / `ddd` (wide / abbreviated weekday name, Sunday-first).
24
+ * `names` is a flat array in fixed layout: `[0..11]` wide months, `[12..23]`
25
+ * abbreviated months, `[24..30]` wide weekdays, `[31..37]` abbreviated
26
+ * weekdays. A name token indexing a missing/short table renders `''` — the
27
+ * same normative zero-value discipline as an unparseable date, byte-identical
28
+ * on every backend.
29
+ *
30
+ * `timeZone` is `'UTC'` or a fixed offset `±HH:MM` (`'+09:00'`, `'-05:30'`).
31
+ * The function is total: any other value — including IANA zone names, which
32
+ * would drag host tzdata versions into the output — normalizes to `'UTC'`, so
33
+ * every backend degrades identically instead of diverging. An unset or
34
+ * unparseable `date` renders `''`.
35
+ */
36
+ export declare function formatDate(date: Date | string, pattern: string, timeZone?: string, names?: readonly string[]): string;
37
+ //# 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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAeH,wBAAgB,UAAU,CACxB,IAAI,EAAE,IAAI,GAAG,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,QAAQ,SAAQ,EAChB,KAAK,GAAE,SAAS,MAAM,EAAO,GAC5B,MAAM,CA4CR"}
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,57 @@ 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|MMMM|MMM|MM|DD|dddd|ddd|M|D/g;
168
+ var MONTHS_WIDE = 0;
169
+ var MONTHS_ABBR = 12;
170
+ var WEEKDAYS_WIDE = 24;
171
+ var WEEKDAYS_ABBR = 31;
172
+ function pad2(n) {
173
+ return String(n).padStart(2, "0");
174
+ }
175
+ function formatDate(date, pattern, timeZone = "UTC", names = []) {
176
+ if (date === null || date === undefined)
177
+ return "";
178
+ const d = date instanceof Date ? date : new Date(date);
179
+ const t = d.getTime();
180
+ if (Number.isNaN(t))
181
+ return "";
182
+ const m = OFFSET_RE.exec(timeZone);
183
+ const offsetMinutes = m ? (m[1] === "-" ? -1 : 1) * (Number(m[2]) * 60 + Number(m[3])) : 0;
184
+ const s = new Date(t + offsetMinutes * 60000);
185
+ const year = s.getUTCFullYear();
186
+ const yyyy = (year < 0 ? "-" : "") + String(Math.abs(year)).padStart(4, "0");
187
+ const month = s.getUTCMonth() + 1;
188
+ const day = s.getUTCDate();
189
+ const weekday = s.getUTCDay();
190
+ const nameAt = (index) => names[index] ?? "";
191
+ return pattern.replace(TOKEN_RE, (token) => {
192
+ switch (token) {
193
+ case "YYYY":
194
+ return yyyy;
195
+ case "MMMM":
196
+ return nameAt(MONTHS_WIDE + month - 1);
197
+ case "MMM":
198
+ return nameAt(MONTHS_ABBR + month - 1);
199
+ case "MM":
200
+ return pad2(month);
201
+ case "M":
202
+ return String(month);
203
+ case "DD":
204
+ return pad2(day);
205
+ case "D":
206
+ return String(day);
207
+ case "dddd":
208
+ return nameAt(WEEKDAYS_WIDE + weekday);
209
+ case "ddd":
210
+ return nameAt(WEEKDAYS_ABBR + weekday);
211
+ default:
212
+ return token;
213
+ }
214
+ });
215
+ }
165
216
  // src/context.ts
166
217
  function createContext(defaultValue) {
167
218
  return {
@@ -216,6 +267,7 @@ export {
216
267
  onCleanup,
217
268
  isSSRPortal,
218
269
  forwardProps,
270
+ formatDate,
219
271
  findSiblingSlot,
220
272
  endTurn,
221
273
  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,57 @@ 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|MMMM|MMM|MM|DD|dddd|ddd|M|D/g;
168
+ var MONTHS_WIDE = 0;
169
+ var MONTHS_ABBR = 12;
170
+ var WEEKDAYS_WIDE = 24;
171
+ var WEEKDAYS_ABBR = 31;
172
+ function pad2(n) {
173
+ return String(n).padStart(2, "0");
174
+ }
175
+ function formatDate(date, pattern, timeZone = "UTC", names = []) {
176
+ if (date === null || date === undefined)
177
+ return "";
178
+ const d = date instanceof Date ? date : new Date(date);
179
+ const t = d.getTime();
180
+ if (Number.isNaN(t))
181
+ return "";
182
+ const m = OFFSET_RE.exec(timeZone);
183
+ const offsetMinutes = m ? (m[1] === "-" ? -1 : 1) * (Number(m[2]) * 60 + Number(m[3])) : 0;
184
+ const s = new Date(t + offsetMinutes * 60000);
185
+ const year = s.getUTCFullYear();
186
+ const yyyy = (year < 0 ? "-" : "") + String(Math.abs(year)).padStart(4, "0");
187
+ const month = s.getUTCMonth() + 1;
188
+ const day = s.getUTCDate();
189
+ const weekday = s.getUTCDay();
190
+ const nameAt = (index) => names[index] ?? "";
191
+ return pattern.replace(TOKEN_RE, (token) => {
192
+ switch (token) {
193
+ case "YYYY":
194
+ return yyyy;
195
+ case "MMMM":
196
+ return nameAt(MONTHS_WIDE + month - 1);
197
+ case "MMM":
198
+ return nameAt(MONTHS_ABBR + month - 1);
199
+ case "MM":
200
+ return pad2(month);
201
+ case "M":
202
+ return String(month);
203
+ case "DD":
204
+ return pad2(day);
205
+ case "D":
206
+ return String(day);
207
+ case "dddd":
208
+ return nameAt(WEEKDAYS_WIDE + weekday);
209
+ case "ddd":
210
+ return nameAt(WEEKDAYS_ABBR + weekday);
211
+ default:
212
+ return token;
213
+ }
214
+ });
215
+ }
165
216
  // ../shared/src/markers.ts
166
217
  var BF_SCOPE = "bf-s";
167
218
  var BF_SLOT = "bf";
@@ -2885,6 +2936,7 @@ export {
2885
2936
  getComponentProps,
2886
2937
  getComponentInit,
2887
2938
  forwardProps,
2939
+ formatDate,
2888
2940
  flushHydration,
2889
2941
  findSiblingSlot,
2890
2942
  findScope,
@@ -815,6 +815,57 @@ 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|MMMM|MMM|MM|DD|dddd|ddd|M|D/g;
821
+ var MONTHS_WIDE = 0;
822
+ var MONTHS_ABBR = 12;
823
+ var WEEKDAYS_WIDE = 24;
824
+ var WEEKDAYS_ABBR = 31;
825
+ function pad2(n) {
826
+ return String(n).padStart(2, "0");
827
+ }
828
+ function formatDate(date, pattern, timeZone = "UTC", names = []) {
829
+ if (date === null || date === undefined)
830
+ return "";
831
+ const d = date instanceof Date ? date : new Date(date);
832
+ const t = d.getTime();
833
+ if (Number.isNaN(t))
834
+ return "";
835
+ const m = OFFSET_RE.exec(timeZone);
836
+ const offsetMinutes = m ? (m[1] === "-" ? -1 : 1) * (Number(m[2]) * 60 + Number(m[3])) : 0;
837
+ const s = new Date(t + offsetMinutes * 60000);
838
+ const year = s.getUTCFullYear();
839
+ const yyyy = (year < 0 ? "-" : "") + String(Math.abs(year)).padStart(4, "0");
840
+ const month = s.getUTCMonth() + 1;
841
+ const day = s.getUTCDate();
842
+ const weekday = s.getUTCDay();
843
+ const nameAt = (index) => names[index] ?? "";
844
+ return pattern.replace(TOKEN_RE, (token) => {
845
+ switch (token) {
846
+ case "YYYY":
847
+ return yyyy;
848
+ case "MMMM":
849
+ return nameAt(MONTHS_WIDE + month - 1);
850
+ case "MMM":
851
+ return nameAt(MONTHS_ABBR + month - 1);
852
+ case "MM":
853
+ return pad2(month);
854
+ case "M":
855
+ return String(month);
856
+ case "DD":
857
+ return pad2(day);
858
+ case "D":
859
+ return String(day);
860
+ case "dddd":
861
+ return nameAt(WEEKDAYS_WIDE + weekday);
862
+ case "ddd":
863
+ return nameAt(WEEKDAYS_ABBR + weekday);
864
+ default:
865
+ return token;
866
+ }
867
+ });
868
+ }
818
869
  // src/context.ts
819
870
  function createContext(defaultValue) {
820
871
  return {
@@ -3285,6 +3336,7 @@ export {
3285
3336
  getComponentProps,
3286
3337
  getComponentInit,
3287
3338
  forwardProps,
3339
+ formatDate,
3288
3340
  flushHydration,
3289
3341
  findSiblingSlot,
3290
3342
  findScope,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/client",
3
- "version": "0.21.4",
3
+ "version": "0.24.1",
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.24.1"
59
59
  },
60
60
  "peerDependencies": {
61
61
  "@barefootjs/jsx": ">=0.2.0"
@@ -0,0 +1,99 @@
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
+ * <time>{formatDate(createdAt, 'MMMM D, YYYY', 'UTC', names)}</time>
13
+ * ```
14
+ *
15
+ * Numeric tokens (longest-match; any other character passes through
16
+ * literally): `YYYY` (4-digit zero-padded year, `-`-prefixed for negative
17
+ * years), `MM` / `M` (zero-padded / bare month `01`–`12` / `1`–`12`), `DD` /
18
+ * `D` (zero-padded / bare day of month).
19
+ *
20
+ * Name tokens (#2334) read the explicit `names` table — the caller owns the
21
+ * values (per-locale name selection is the i18n layer's or the compiler's
22
+ * job, never this function's): `MMMM` / `MMM` (wide / abbreviated month
23
+ * name), `dddd` / `ddd` (wide / abbreviated weekday name, Sunday-first).
24
+ * `names` is a flat array in fixed layout: `[0..11]` wide months, `[12..23]`
25
+ * abbreviated months, `[24..30]` wide weekdays, `[31..37]` abbreviated
26
+ * weekdays. A name token indexing a missing/short table renders `''` — the
27
+ * same normative zero-value discipline as an unparseable date, byte-identical
28
+ * on every backend.
29
+ *
30
+ * `timeZone` is `'UTC'` or a fixed offset `±HH:MM` (`'+09:00'`, `'-05:30'`).
31
+ * The function is total: any other value — including IANA zone names, which
32
+ * would drag host tzdata versions into the output — normalizes to `'UTC'`, so
33
+ * every backend degrades identically instead of diverging. An unset or
34
+ * unparseable `date` renders `''`.
35
+ */
36
+
37
+ const OFFSET_RE = /^([+-])(\d{2}):(\d{2})$/
38
+ const TOKEN_RE = /YYYY|MMMM|MMM|MM|DD|dddd|ddd|M|D/g
39
+
40
+ /** `names`-table section offsets (spec/template-helpers.md "format_date"). */
41
+ const MONTHS_WIDE = 0
42
+ const MONTHS_ABBR = 12
43
+ const WEEKDAYS_WIDE = 24
44
+ const WEEKDAYS_ABBR = 31
45
+
46
+ function pad2(n: number): string {
47
+ return String(n).padStart(2, '0')
48
+ }
49
+
50
+ export function formatDate(
51
+ date: Date | string,
52
+ pattern: string,
53
+ timeZone = 'UTC',
54
+ names: readonly string[] = [],
55
+ ): string {
56
+ // Explicit nullish guard: `new Date(null)` is epoch 0, not Invalid Date,
57
+ // so without this a nil receiver would format 1970-01-01 instead of the
58
+ // contract's '' (spec/template-helpers.md "format_date", golden vector
59
+ // "nil receiver renders the empty string").
60
+ if (date === null || date === undefined) return ''
61
+ const d = date instanceof Date ? date : new Date(date)
62
+ const t = d.getTime()
63
+ if (Number.isNaN(t)) return ''
64
+ const m = OFFSET_RE.exec(timeZone)
65
+ const offsetMinutes = m ? (m[1] === '-' ? -1 : 1) * (Number(m[2]) * 60 + Number(m[3])) : 0
66
+ // Shift the instant by the offset, then read UTC fields: the shifted UTC
67
+ // clock face IS the local clock face at that offset.
68
+ const s = new Date(t + offsetMinutes * 60_000)
69
+ const year = s.getUTCFullYear()
70
+ const yyyy = (year < 0 ? '-' : '') + String(Math.abs(year)).padStart(4, '0')
71
+ const month = s.getUTCMonth() + 1
72
+ const day = s.getUTCDate()
73
+ const weekday = s.getUTCDay() // 0 = Sunday, matching the table's Sunday-first layout
74
+ const nameAt = (index: number): string => names[index] ?? ''
75
+ return pattern.replace(TOKEN_RE, (token) => {
76
+ switch (token) {
77
+ case 'YYYY':
78
+ return yyyy
79
+ case 'MMMM':
80
+ return nameAt(MONTHS_WIDE + month - 1)
81
+ case 'MMM':
82
+ return nameAt(MONTHS_ABBR + month - 1)
83
+ case 'MM':
84
+ return pad2(month)
85
+ case 'M':
86
+ return String(month)
87
+ case 'DD':
88
+ return pad2(day)
89
+ case 'D':
90
+ return String(day)
91
+ case 'dddd':
92
+ return nameAt(WEEKDAYS_WIDE + weekday)
93
+ case 'ddd':
94
+ return nameAt(WEEKDAYS_ABBR + weekday)
95
+ default:
96
+ return token
97
+ }
98
+ })
99
+ }
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`).