akanjs 2.2.7-rc.3 → 2.2.7

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/CHANGELOG.md CHANGED
@@ -1,13 +1,11 @@
1
1
  # akanjs
2
2
 
3
- ## 2.2.5
3
+ ## 2.2.7
4
4
 
5
5
  ### Patch Changes
6
6
 
7
- - d636456: add rich Map methods on memory() helper service
8
- - a1ee4e8: fill nested constant defaults for arrays on document save and load, normalize date fields to a consistent epoch representation on store (accepting legacy ISO-string values on read), and correct falsy defaults in getDefault
9
- - 5cdb05e: reverse dependency of file upload api
10
- - a7da50e: remove dependency from radix dialog
7
+ - fix: base dictionary translation failed in some cases
8
+ - fix: file upload contract workaround on shared Field.Img component
11
9
 
12
10
  ## 2.2.3
13
11
 
@@ -25,11 +25,8 @@ const getPageInfo = (): { locale: string; path: string } => {
25
25
  const localeSet = new Set(locales);
26
26
  if (getEnv().side !== "server") {
27
27
  const [, firstSegment = "", ...rest] = window.location.pathname.split("/");
28
- const hasLocalePrefix = localeSet.has(firstSegment);
29
-
30
- const activeLocale = Translator.getActiveLocale();
31
- const locale = activeLocale ?? (hasLocalePrefix ? firstSegment : defaultLocale);
32
- return { locale, path: hasLocalePrefix ? `/${rest.join("/")}` : window.location.pathname };
28
+ if (localeSet.has(firstSegment)) return { locale: firstSegment, path: `/${rest.join("/")}` };
29
+ return { locale: defaultLocale, path: window.location.pathname };
33
30
  }
34
31
  const h = headers();
35
32
 
@@ -2,7 +2,7 @@ interface FileUploadSerializedEndpoint {
2
2
  fileUpload?: boolean;
3
3
  }
4
4
 
5
- interface FileUploadSerializedSignal {
5
+ export interface FileUploadSerializedSignal {
6
6
  prefix?: string;
7
7
  endpoint: Record<string, FileUploadSerializedEndpoint>;
8
8
  }
package/common/index.ts CHANGED
@@ -1,11 +1,7 @@
1
1
  export { applyMixins } from "./applyMixins";
2
2
  export { capitalize } from "./capitalize";
3
3
  export { deepObjectify } from "./deepObjectify";
4
- export {
5
- type FileUploadCapability,
6
- fileUploadContract,
7
- resolveFileUploadCapability,
8
- } from "./fileUpload";
4
+ export * from "./fileUpload";
9
5
  export { formatNumber } from "./formatNumber";
10
6
  export { formatPhone } from "./formatPhone";
11
7
  export { getAllPropertyDescriptors } from "./getAllPropertyDescriptors";
@@ -6,7 +6,7 @@ export const getDefault = <T>(fieldObj: FieldObject): DefaultOf<T> => {
6
6
  const result: Record<string, unknown> = {};
7
7
  for (const [key, field] of Object.entries(fieldObj)) {
8
8
  if (field.fieldType === "hidden") result[key] = null;
9
- else if (field.default !== undefined && field.default !== null) {
9
+ else if (field.default) {
10
10
  if (typeof field.default === "function") result[key] = (field.default as () => object)();
11
11
  else result[key] = field.default as object;
12
12
  } else if (field.isArray) result[key] = [];
@@ -1,5 +1,12 @@
1
1
  import { DataList, getEnv, PrimitiveRegistry, type PromiseOrObject } from "akanjs/base";
2
- import { capitalize, type FetchPolicy, fileUploadContract, Logger, resolveFileUploadCapability } from "akanjs/common";
2
+ import {
3
+ capitalize,
4
+ type FetchPolicy,
5
+ type FileUploadSerializedSignal,
6
+ fileUploadContract,
7
+ Logger,
8
+ resolveFileUploadCapability,
9
+ } from "akanjs/common";
3
10
  import { type BaseInsight, type BaseObject, ConstantRegistry, deserialize, serialize } from "akanjs/constant";
4
11
  import type {
5
12
  DatabaseSignal,
@@ -472,7 +479,7 @@ export class FetchClient {
472
479
  names.addModelFiles,
473
480
  () =>
474
481
  (async (fileList: FileList, parentId?: string, option?: FetchPolicy) => {
475
- const cap = resolveFileUploadCapability(this.serializedSignal);
482
+ const cap = resolveFileUploadCapability(this.serializedSignal as Record<string, FileUploadSerializedSignal>);
476
483
  const endpoint = cap ? this.serializedSignal[cap.refName]?.endpoint[cap.endpointKey] : undefined;
477
484
  if (!cap || !endpoint)
478
485
  throw new Error(
@@ -691,7 +698,8 @@ export class FetchClient {
691
698
  const sig = {} as any;
692
699
  Object.entries(serializedSignal).forEach(([refName, serializedSignal]) => {
693
700
  if (!serializedSignal.slice) return;
694
- const cnst = ConstantRegistry.getDatabase(refName);
701
+ const cnst = ConstantRegistry.getDatabase(refName, { allowEmpty: true });
702
+ if (!cnst) return;
695
703
  const slices = Object.entries(serializedSignal.slice).map(([suffix, serializedSlice]) => {
696
704
  const sliceName = `${refName}${capitalize(suffix)}`;
697
705
  proxy.slice[sliceName] = { refName, sliceName, argLength: serializedSlice.args.length };
@@ -67,7 +67,6 @@ export class FetchSerializer {
67
67
  ...(endpointInfo.signalOption.globalPrefix !== undefined
68
68
  ? { globalPrefix: endpointInfo.signalOption.globalPrefix }
69
69
  : {}),
70
- ...(endpointInfo.signalOption.fileUpload ? { fileUpload: true } : {}),
71
70
  ...(guards?.length ? { guards } : {}),
72
71
  };
73
72
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akanjs",
3
- "version": "2.2.7-rc.3",
3
+ "version": "2.2.7",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -175,6 +175,7 @@
175
175
  "@capgo/capacitor-updater": "^8.46.1",
176
176
  "@libsql/client": "^0.17.3",
177
177
  "@playwright/test": "^1.60.0",
178
+ "@radix-ui/react-dialog": "^1.1.15",
178
179
  "@react-spring/web": "^10.1.0",
179
180
  "@use-gesture/react": "^10.3.1",
180
181
  "bullmq": "^5.76.10",
@@ -249,6 +250,9 @@
249
250
  "@playwright/test": {
250
251
  "optional": true
251
252
  },
253
+ "@radix-ui/react-dialog": {
254
+ "optional": true
255
+ },
252
256
  "@react-spring/web": {
253
257
  "optional": true
254
258
  },
@@ -254,47 +254,20 @@ export class InjectInfo<
254
254
  enumerable: true,
255
255
  });
256
256
  } else if (injectInfo.isMap) {
257
- const topic = `akan:memory:${injectInfo.parentRefName}`;
258
- const getter = injectInfo.get as unknown as (value: unknown) => unknown;
259
- const setter = injectInfo.set as unknown as (value: unknown) => string | number | Buffer;
260
- const get = async (key: string) => {
261
- const value = await cacheAdaptor.hget(topic, propKey, key);
262
- return value === undefined || value === null ? undefined : getter(value);
263
- };
264
- const set = async (key: string, value: unknown) => {
265
- const setValue = setter(value);
266
- await cacheAdaptor.hset(topic, propKey, key, setValue);
267
- };
268
257
  Object.defineProperty(instance, propKey, {
269
258
  value: {
270
- get,
271
- set,
272
- delete: async (key: string) => {
273
- await cacheAdaptor.hdelete(topic, propKey, key);
274
- },
275
- getOrInsert: async (key: string, value: unknown) => {
276
- const existingValue = await get(key);
277
- if (existingValue !== undefined) return existingValue;
278
- await set(key, value);
279
- return value;
280
- },
281
- getOrInsertComputed: async (key: string, compute: (key: string) => unknown | Promise<unknown>) => {
282
- const existingValue = await get(key);
283
- if (existingValue !== undefined) return existingValue;
284
- const value = await compute(key);
285
- await set(key, value);
286
- return value;
287
- },
288
- keys: async () => await cacheAdaptor.hkeys(topic, propKey),
289
- entries: async () => {
290
- const entries = await cacheAdaptor.hentries(topic, propKey);
291
- return entries.map(([key, value]) => [key, getter(value)]);
259
+ get: async (key: string) => {
260
+ const getter = injectInfo.get as unknown as (value: unknown) => unknown;
261
+ const value = await cacheAdaptor.hget(`akan:memory:${injectInfo.parentRefName}`, propKey, key);
262
+ return value === null ? value : getter(value);
292
263
  },
293
- forEach: async (callback: (value: unknown, key: string) => void | Promise<void>) => {
294
- for (const [key, value] of await cacheAdaptor.hentries(topic, propKey)) await callback(getter(value), key);
264
+ set: async (key: string, value: unknown) => {
265
+ const setter = injectInfo.set as unknown as (value: unknown) => string | number | Buffer;
266
+ const setValue = setter(value);
267
+ await cacheAdaptor.hset(`akan:memory:${injectInfo.parentRefName}`, propKey, key, setValue);
295
268
  },
296
- clear: async () => {
297
- await cacheAdaptor.hclear(topic, propKey);
269
+ delete: async (key: string) => {
270
+ await cacheAdaptor.hdelete(`akan:memory:${injectInfo.parentRefName}`, propKey, key);
298
271
  },
299
272
  },
300
273
  });
@@ -373,7 +346,6 @@ export const injectionBuilder = (parentRefName: string) => ({
373
346
  const isMap = modelRef === Map;
374
347
  if (isMap && !opts.of) throw new Error("of should be provided when modelRef is Map");
375
348
  type FieldValue = never extends GetFn ? GetFieldValue<ValueRef, ExplicitType, MapValue> : ReturnType<GetFn>;
376
- type MapFieldValue = never extends GetFn ? FieldToValue<MapValue> : ReturnType<GetFn>;
377
349
  type IsNullable = DefaultValue extends never ? true : false;
378
350
  type UseValue = IsNullable extends true ? FieldValue | null : FieldValue;
379
351
  return new InjectInfo<
@@ -384,18 +356,9 @@ export const injectionBuilder = (parentRefName: string) => ({
384
356
  : UseValue
385
357
  : MapConstructor extends ValueRef
386
358
  ? {
387
- get: (key: string) => Promise<MapFieldValue | undefined>;
388
- set: (key: string, value: MapFieldValue) => Promise<void>;
359
+ get: (key: string) => Promise<FieldToValue<MapValue>>;
360
+ set: (key: string, value: FieldToValue<MapValue>) => Promise<void>;
389
361
  delete: (key: string) => Promise<void>;
390
- getOrInsert: (key: string, value: MapFieldValue) => Promise<MapFieldValue>;
391
- getOrInsertComputed: (
392
- key: string,
393
- compute: (key: string) => MapFieldValue | Promise<MapFieldValue>,
394
- ) => Promise<MapFieldValue>;
395
- keys: () => Promise<string[]>;
396
- entries: () => Promise<[string, MapFieldValue][]>;
397
- forEach: (callback: (value: MapFieldValue, key: string) => void | Promise<void>) => Promise<void>;
398
- clear: () => Promise<void>;
399
362
  }
400
363
  : { get: () => Promise<UseValue>; set: (value: UseValue) => Promise<void>; delete: () => Promise<void> },
401
364
  never,
@@ -16,9 +16,6 @@ export interface CacheAdaptor {
16
16
  ): Promise<void>;
17
17
  hget<T extends string | number | Buffer>(topic: string, key: string, subKey: string): Promise<T | undefined>;
18
18
  hdelete(topic: string, key: string, subKey: string): Promise<void>;
19
- hkeys(topic: string, key: string): Promise<string[]>;
20
- hentries<T extends string | number | Buffer>(topic: string, key: string): Promise<[string, T][]>;
21
- hclear(topic: string, key: string): Promise<void>;
22
19
  }
23
20
 
24
21
  interface RedisEnv extends BaseEnv {
@@ -99,16 +96,6 @@ export class RedisCache
99
96
  async hdelete(topic: string, key: string, subKey: string): Promise<void> {
100
97
  await this.redis.hdel(`${topic}:${key}`, subKey);
101
98
  }
102
- async hkeys(topic: string, key: string): Promise<string[]> {
103
- return await this.redis.hkeys(`${topic}:${key}`);
104
- }
105
- async hentries<T extends string | number | Buffer>(topic: string, key: string): Promise<[string, T][]> {
106
- const values = await this.redis.hgetall(`${topic}:${key}`);
107
- return Object.entries(values) as [string, T][];
108
- }
109
- async hclear(topic: string, key: string): Promise<void> {
110
- await this.redis.del(`${topic}:${key}`);
111
- }
112
99
  getClient(): Redis {
113
100
  return this.redis;
114
101
  }
@@ -4,7 +4,7 @@ import { mkdir } from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import type { InArgs, InValue, Client as LibsqlClient } from "@libsql/client";
6
6
  import { type BaseEnv, dayjs, FIELD_META, type PromiseOrObject } from "akanjs/base";
7
- import { type ConstantModel, getDefault } from "akanjs/constant";
7
+ import type { ConstantModel } from "akanjs/constant";
8
8
  import {
9
9
  createDocumentId,
10
10
  type DatabaseModel,
@@ -300,13 +300,6 @@ const jsonPath = (path: string) =>
300
300
  .map((part) => part.replaceAll('"', '\\"'))
301
301
  .join(".")}`;
302
302
  const encodeSqlValue = (value: unknown) => encodeDocumentValue(value);
303
-
304
- const decodeDateValue = (value: unknown) => {
305
- if (value === null || value === undefined) return value;
306
- if (typeof value === "number") return dayjs(value);
307
- const epoch = Number(value);
308
- return Number.isNaN(epoch) ? dayjs(value as never) : dayjs(epoch);
309
- };
310
303
  const QUERY_OPERATOR_KEYS = new Set([
311
304
  "eq",
312
305
  "ne",
@@ -780,9 +773,6 @@ export class SqliteDocumentStore {
780
773
  } else {
781
774
  doc[key] = value;
782
775
  }
783
- if (doc[key] !== undefined && doc[key] !== null) {
784
- doc[key] = this.normalizeWriteValue(doc[key], props);
785
- }
786
776
  if (props.enum && doc[key] !== undefined && doc[key] !== null) {
787
777
  const values = Array.isArray(doc[key]) ? doc[key] : [doc[key]];
788
778
  const fieldEnum = props.enum as { has: (value: unknown) => boolean } | undefined;
@@ -968,28 +958,12 @@ export class SqliteDocumentStore {
968
958
 
969
959
  private decodeDocumentPayload(payload: Record<string, unknown>) {
970
960
  const fields = this.database.doc[FIELD_META] as unknown as FieldMap;
971
- const result: Record<string, unknown> = {};
972
- for (const [key, fieldMeta] of Object.entries(fields)) {
973
- if (BASE_COLUMNS.has(key)) continue;
974
- const props = fieldMeta.getProps();
975
- const value = payload[key];
976
- if (value === undefined) {
977
- const def = props.default;
978
- if (def !== undefined && def !== null) {
979
- result[key] = typeof def === "function" ? (def as (data: unknown) => unknown)(payload) : def;
980
- } else if (props.nullable) {
981
- result[key] = null;
982
- }
983
- } else {
984
- result[key] = this.decodeFieldValue(value, props);
985
- }
986
- }
987
- for (const [key, value] of Object.entries(payload)) {
988
- if (key in result || BASE_COLUMNS.has(key)) continue;
989
- const props = fields[key]?.getProps?.();
990
- result[key] = props ? this.decodeFieldValue(value, props) : value;
991
- }
992
- return result;
961
+ return Object.fromEntries(
962
+ Object.entries(payload).map(([key, value]) => {
963
+ const props = fields[key]?.getProps?.();
964
+ return [key, props ? this.decodeFieldValue(value, props) : value];
965
+ }),
966
+ );
993
967
  }
994
968
 
995
969
  private decodeFieldValue(value: unknown, props: Record<string, unknown>): unknown {
@@ -999,8 +973,8 @@ export class SqliteDocumentStore {
999
973
  return new Map(entries.map(([key, item]) => [key, this.decodeMapValue(item, props)]));
1000
974
  }
1001
975
  if (props.modelRef === Date) {
1002
- if (Array.isArray(value)) return value.map((item) => (item === null ? item : decodeDateValue(item)));
1003
- return decodeDateValue(value);
976
+ if (Array.isArray(value)) return value.map((item) => (item === null ? item : dayjs(Number(item))));
977
+ return dayjs(Number(value));
1004
978
  }
1005
979
  if (Array.isArray(value)) return value.map((item) => this.decodeNestedValue(item, props));
1006
980
  return this.decodeNestedValue(value, props);
@@ -1008,7 +982,7 @@ export class SqliteDocumentStore {
1008
982
 
1009
983
  private decodeMapValue(value: unknown, props: Record<string, unknown>) {
1010
984
  if (value === undefined || value === null) return value;
1011
- if (props.of === Date) return decodeDateValue(value);
985
+ if (props.of === Date) return dayjs(Number(value));
1012
986
  return value;
1013
987
  }
1014
988
 
@@ -1017,44 +991,12 @@ export class SqliteDocumentStore {
1017
991
  if (!props.isClass || !props.isScalar) return value;
1018
992
  const scalarFields = (props.modelRef as { [FIELD_META]?: FieldMap } | undefined)?.[FIELD_META];
1019
993
  if (!scalarFields) return value;
1020
- const source = value as Record<string, unknown>;
1021
- const defaults = getDefault(scalarFields as never) as Record<string, unknown>;
1022
- const result: Record<string, unknown> = {};
1023
- for (const [key, fieldMeta] of Object.entries(scalarFields)) {
1024
- const nestedProps = fieldMeta.getProps();
1025
- const nested = source[key];
1026
- result[key] = nested === undefined ? defaults[key] : this.decodeFieldValue(nested, nestedProps);
1027
- }
1028
- for (const [key, nested] of Object.entries(source)) {
1029
- if (!(key in result)) result[key] = nested;
1030
- }
1031
- return result;
1032
- }
1033
-
1034
- private normalizeWriteValue(value: unknown, props: Record<string, unknown>): unknown {
1035
- if (value === undefined || value === null) return value;
1036
- if (props.modelRef === Date) {
1037
- if (Array.isArray(value))
1038
- return value.map((item) => (item === null || item === undefined ? item : dayjs(item as never)));
1039
- return dayjs(value as never);
1040
- }
1041
- if (!props.isClass || !props.isScalar) return value;
1042
- if (Array.isArray(value)) return value.map((item) => this.fillScalarDefaults(item, props));
1043
- return this.fillScalarDefaults(value, props);
1044
- }
1045
-
1046
- private fillScalarDefaults(value: unknown, props: Record<string, unknown>): unknown {
1047
- if (!value || typeof value !== "object") return value;
1048
- const scalarFields = (props.modelRef as { [FIELD_META]?: FieldMap } | undefined)?.[FIELD_META];
1049
- if (!scalarFields) return value;
1050
- const defaults = getDefault(scalarFields as never) as Record<string, unknown>;
1051
- const result = { ...(value as Record<string, unknown>) };
1052
- for (const [key, fieldMeta] of Object.entries(scalarFields)) {
1053
- const nestedProps = fieldMeta.getProps();
1054
- if (result[key] === undefined) result[key] = defaults[key];
1055
- else result[key] = this.normalizeWriteValue(result[key], nestedProps);
1056
- }
1057
- return result;
994
+ return Object.fromEntries(
995
+ Object.entries(value as Record<string, unknown>).map(([key, nested]) => {
996
+ const nestedProps = scalarFields[key]?.getProps?.();
997
+ return [key, nestedProps ? this.decodeFieldValue(nested, nestedProps) : nested];
998
+ }),
999
+ );
1058
1000
  }
1059
1001
 
1060
1002
  hydrate(data: DocumentRecord, originalData: DocumentRecord = data) {
@@ -14,7 +14,6 @@ import {
14
14
  } from "./solidSqlite";
15
15
 
16
16
  type CacheRow = { value: string | Buffer | null; valueType: SolidValueType; expiresAt: number | null };
17
- type CacheEntryRow = CacheRow & { subKey: string };
18
17
 
19
18
  export class SolidCache
20
19
  extends adapt("solidCache", ({ env }) => ({
@@ -136,28 +135,6 @@ export class SolidCache
136
135
  .run(topic, key, subKey);
137
136
  }
138
137
 
139
- async hkeys(topic: string, key: string): Promise<string[]> {
140
- this.#cleanup();
141
- const rows = this.#db
142
- .query(`SELECT "subKey" FROM "_akan_solid_cache_hash" WHERE "topic" = ? AND "key" = ? ORDER BY "subKey" ASC`)
143
- .all(topic, key) as { subKey: string }[];
144
- return rows.map((row) => row.subKey);
145
- }
146
-
147
- async hentries<T extends string | number | Buffer>(topic: string, key: string): Promise<[string, T][]> {
148
- this.#cleanup();
149
- const rows = this.#db
150
- .query(
151
- `SELECT "subKey", "value", "valueType", "expiresAt" FROM "_akan_solid_cache_hash" WHERE "topic" = ? AND "key" = ? ORDER BY "subKey" ASC`,
152
- )
153
- .all(topic, key) as CacheEntryRow[];
154
- return rows.map((row) => [row.subKey, decodeSolidValue<T>(row.valueType, row.value) as T]);
155
- }
156
-
157
- async hclear(topic: string, key: string): Promise<void> {
158
- this.#db.query(`DELETE FROM "_akan_solid_cache_hash" WHERE "topic" = ? AND "key" = ?`).run(topic, key);
159
- }
160
-
161
138
  #cleanup() {
162
139
  const now = Date.now();
163
140
  this.#db.query(`DELETE FROM "_akan_solid_cache" WHERE "expiresAt" IS NOT NULL AND "expiresAt" <= ?`).run(now);
@@ -64,7 +64,6 @@ export class FetchSerializer {
64
64
  args: endpointInfo.args.map(FetchSerializer.#serializeArg),
65
65
  returns: FetchSerializer.#serializeReturns(endpointInfo),
66
66
  ...(endpointInfo.signalOption.path ? { path: endpointInfo.signalOption.path } : {}),
67
- ...(endpointInfo.signalOption.fileUpload ? { fileUpload: true } : {}),
68
67
  ...(guards?.length ? { guards } : {}),
69
68
  };
70
69
  }
package/signal/types.ts CHANGED
@@ -70,8 +70,6 @@ export interface SignalOption<Response = any, Nullable extends boolean = false,
70
70
  middlewares?: MiddlewareCls[];
71
71
  prefix?: false | string;
72
72
  globalPrefix?: false;
73
- /** Marks this mutation as the framework file-upload endpoint (see resolveFileUploadCapability). */
74
- fileUpload?: boolean;
75
73
 
76
74
  scheduleType?: "init" | "destroy" | "cron" | "interval" | "timeout";
77
75
  scheduleCron?: string;
@@ -86,7 +84,6 @@ interface SerializedSignalOption {
86
84
  prefix?: false | string;
87
85
  globalPrefix?: false;
88
86
  guards?: string[];
89
- fileUpload?: boolean;
90
87
  }
91
88
  export interface SerializedSlice extends SerializedSignalOption {}
92
89
 
package/store/action.ts CHANGED
@@ -1,14 +1,5 @@
1
1
  import { DataList, type Dayjs, FIELD_META, type GetStateObject, type SLICE_META } from "akanjs/base";
2
- import {
3
- capitalize,
4
- deepObjectify,
5
- type FetchPolicy,
6
- isQueryEqual,
7
- Logger,
8
- lowerlize,
9
- pathSet,
10
- resolveFileUploadCapability,
11
- } from "akanjs/common";
2
+ import { capitalize, deepObjectify, type FetchPolicy, isQueryEqual, Logger, lowerlize, pathSet } from "akanjs/common";
12
3
  import {
13
4
  type BaseInsight,
14
5
  type BaseObject,
@@ -286,7 +277,6 @@ export const makeFormSetter = (refName: string, fetch: FetchProxy<any>) => {
286
277
  type Light = BaseObject;
287
278
  const [fieldName, className] = [refName, capitalize(refName)];
288
279
  const modelRef = ConstantRegistry.getDatabase(refName).full;
289
- const fileUploadRefName = resolveFileUploadCapability(fetch.serializedSignal)?.refName;
290
280
 
291
281
  const names = {
292
282
  model: fieldName,
@@ -366,7 +356,7 @@ export const makeFormSetter = (refName: string, fetch: FetchProxy<any>) => {
366
356
  },
367
357
  }
368
358
  : {}),
369
- ...(field.isClass && !!fileUploadRefName && ConstantRegistry.getRefName(field.modelRef) === fileUploadRefName
359
+ ...(field.isClass && ConstantRegistry.getRefName(field.modelRef) === "file"
370
360
  ? {
371
361
  [namesOfField.uploadFieldOnModel]: async function (this: SetGet, fileList: FileList, index?: number) {
372
362
  const form = (this.get() as { [key: string]: any })[names.modelForm] as { [key: string]: any };
@@ -393,9 +383,7 @@ export const makeFormSetter = (refName: string, fetch: FetchProxy<any>) => {
393
383
  const intervalKey = setInterval(() => {
394
384
  void (async () => {
395
385
  const currentFile = await (
396
- (fetch as { [key: string]: any })[fileUploadRefName as string] as (
397
- id: string,
398
- ) => Promise<ProtoFile>
386
+ (fetch as { [key: string]: any }).file as (id: string) => Promise<ProtoFile>
399
387
  )(file.id);
400
388
  if (field.isArray)
401
389
  this.set((state: { [key: string]: { [key: string]: ProtoFile[] } }) => {
@@ -1,7 +1,7 @@
1
1
  interface FileUploadSerializedEndpoint {
2
2
  fileUpload?: boolean;
3
3
  }
4
- interface FileUploadSerializedSignal {
4
+ export interface FileUploadSerializedSignal {
5
5
  prefix?: string;
6
6
  endpoint: Record<string, FileUploadSerializedEndpoint>;
7
7
  }
@@ -1,7 +1,7 @@
1
1
  export { applyMixins } from "./applyMixins.d.ts";
2
2
  export { capitalize } from "./capitalize.d.ts";
3
3
  export { deepObjectify } from "./deepObjectify.d.ts";
4
- export { type FileUploadCapability, fileUploadContract, resolveFileUploadCapability, } from "./fileUpload.d.ts";
4
+ export * from "./fileUpload.d.ts";
5
5
  export { formatNumber } from "./formatNumber.d.ts";
6
6
  export { formatPhone } from "./formatPhone.d.ts";
7
7
  export { getAllPropertyDescriptors } from "./getAllPropertyDescriptors.d.ts";
@@ -76,15 +76,9 @@ export declare const injectionBuilder: (parentRefName: string) => {
76
76
  get?: GetFn;
77
77
  set?: (value: ReturnType<GetFn>) => GetFieldValue<ValueRef, ExplicitType>;
78
78
  }) => InjectInfo<"memory", Local extends true ? MapConstructor extends ValueRef ? Map<string, FieldToValue<MapValue>> : (DefaultValue extends never ? true : false) extends true ? (never extends GetFn ? GetFieldValue<ValueRef, ExplicitType, MapValue> : ReturnType<GetFn>) | null : never extends GetFn ? GetFieldValue<ValueRef, ExplicitType, MapValue> : ReturnType<GetFn> : MapConstructor extends ValueRef ? {
79
- get: (key: string) => Promise<(never extends GetFn ? FieldToValue<MapValue> : ReturnType<GetFn>) | undefined>;
80
- set: (key: string, value: never extends GetFn ? FieldToValue<MapValue> : ReturnType<GetFn>) => Promise<void>;
79
+ get: (key: string) => Promise<FieldToValue<MapValue>>;
80
+ set: (key: string, value: FieldToValue<MapValue>) => Promise<void>;
81
81
  delete: (key: string) => Promise<void>;
82
- getOrInsert: (key: string, value: never extends GetFn ? FieldToValue<MapValue> : ReturnType<GetFn>) => Promise<never extends GetFn ? FieldToValue<MapValue> : ReturnType<GetFn>>;
83
- getOrInsertComputed: (key: string, compute: (key: string) => (never extends GetFn ? FieldToValue<MapValue> : ReturnType<GetFn>) | Promise<never extends GetFn ? FieldToValue<MapValue> : ReturnType<GetFn>>) => Promise<never extends GetFn ? FieldToValue<MapValue> : ReturnType<GetFn>>;
84
- keys: () => Promise<string[]>;
85
- entries: () => Promise<[string, never extends GetFn ? FieldToValue<MapValue> : ReturnType<GetFn>][]>;
86
- forEach: (callback: (value: never extends GetFn ? FieldToValue<MapValue> : ReturnType<GetFn>, key: string) => void | Promise<void>) => Promise<void>;
87
- clear: () => Promise<void>;
88
82
  } : {
89
83
  get: () => Promise<(DefaultValue extends never ? true : false) extends true ? (never extends GetFn ? GetFieldValue<ValueRef, ExplicitType, MapValue> : ReturnType<GetFn>) | null : never extends GetFn ? GetFieldValue<ValueRef, ExplicitType, MapValue> : ReturnType<GetFn>>;
90
84
  set: (value: (DefaultValue extends never ? true : false) extends true ? (never extends GetFn ? GetFieldValue<ValueRef, ExplicitType, MapValue> : ReturnType<GetFn>) | null : never extends GetFn ? GetFieldValue<ValueRef, ExplicitType, MapValue> : ReturnType<GetFn>) => Promise<void>;
@@ -12,9 +12,6 @@ export interface CacheAdaptor {
12
12
  }): Promise<void>;
13
13
  hget<T extends string | number | Buffer>(topic: string, key: string, subKey: string): Promise<T | undefined>;
14
14
  hdelete(topic: string, key: string, subKey: string): Promise<void>;
15
- hkeys(topic: string, key: string): Promise<string[]>;
16
- hentries<T extends string | number | Buffer>(topic: string, key: string): Promise<[string, T][]>;
17
- hclear(topic: string, key: string): Promise<void>;
18
15
  }
19
16
  interface RedisEnv extends BaseEnv {
20
17
  redis?: {
@@ -37,9 +34,6 @@ export declare class RedisCache extends RedisCache_base implements CacheAdaptor
37
34
  }): Promise<void>;
38
35
  hget<T extends string | number | Buffer>(topic: string, key: string, subKey: string): Promise<T | undefined>;
39
36
  hdelete(topic: string, key: string, subKey: string): Promise<void>;
40
- hkeys(topic: string, key: string): Promise<string[]>;
41
- hentries<T extends string | number | Buffer>(topic: string, key: string): Promise<[string, T][]>;
42
- hclear(topic: string, key: string): Promise<void>;
43
37
  getClient(): Redis;
44
38
  onDestroy(): Promise<void>;
45
39
  }
@@ -1,7 +1,7 @@
1
1
  import { Database } from "bun:sqlite";
2
2
  import type { Client as LibsqlClient } from "@libsql/client";
3
3
  import { type BaseEnv, type PromiseOrObject } from "akanjs/base";
4
- import { type ConstantModel } from "akanjs/constant";
4
+ import type { ConstantModel } from "akanjs/constant";
5
5
  import { type DatabaseModel, type DocumentQuery, type DocumentSchema, type DocumentUpdate, type DocumentUpdateOptions, type SchemaOf } from "akanjs/document";
6
6
  import type { Sql } from "postgres";
7
7
  export interface SqliteDatabaseConfig {
@@ -256,8 +256,6 @@ export declare class SqliteDocumentStore {
256
256
  private decodeFieldValue;
257
257
  private decodeMapValue;
258
258
  private decodeNestedValue;
259
- private normalizeWriteValue;
260
- private fillScalarDefaults;
261
259
  hydrate(data: DocumentRecord, originalData?: DocumentRecord): any;
262
260
  private runHooks;
263
261
  private insertStmt;
@@ -18,8 +18,5 @@ export declare class SolidCache extends SolidCache_base implements CacheAdaptor
18
18
  }): Promise<void>;
19
19
  hget<T extends string | number | Buffer>(topic: string, key: string, subKey: string): Promise<T | undefined>;
20
20
  hdelete(topic: string, key: string, subKey: string): Promise<void>;
21
- hkeys(topic: string, key: string): Promise<string[]>;
22
- hentries<T extends string | number | Buffer>(topic: string, key: string): Promise<[string, T][]>;
23
- hclear(topic: string, key: string): Promise<void>;
24
21
  }
25
22
  export {};
@@ -60,8 +60,6 @@ export interface SignalOption<Response = any, Nullable extends boolean = false,
60
60
  middlewares?: MiddlewareCls[];
61
61
  prefix?: false | string;
62
62
  globalPrefix?: false;
63
- /** Marks this mutation as the framework file-upload endpoint (see resolveFileUploadCapability). */
64
- fileUpload?: boolean;
65
63
  scheduleType?: "init" | "destroy" | "cron" | "interval" | "timeout";
66
64
  scheduleCron?: string;
67
65
  scheduleTime?: number;
@@ -74,7 +72,6 @@ interface SerializedSignalOption {
74
72
  prefix?: false | string;
75
73
  globalPrefix?: false;
76
74
  guards?: string[];
77
- fileUpload?: boolean;
78
75
  }
79
76
  export interface SerializedSlice extends SerializedSignalOption {
80
77
  }
@@ -6,4 +6,4 @@ export interface ModalProps {
6
6
  children?: ReactNode;
7
7
  onCancel?: () => void;
8
8
  }
9
- export declare const Modal: ({ className, bodyClassName, confirmClose, children, onCancel }: ModalProps) => import("react").ReactPortal | null;
9
+ export declare const Modal: ({ className, bodyClassName, confirmClose, children, onCancel }: ModalProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,7 +1,7 @@
1
1
  import { type ProviderProps } from "./Provider.d.ts";
2
2
  export declare const Dialog: {
3
3
  ({ children, ...props }: ProviderProps): import("react/jsx-runtime").JSX.Element;
4
- Modal: ({ className, bodyClassName, confirmClose, children, onCancel }: import("./Modal.d.ts").ModalProps) => import("react").ReactPortal | null;
4
+ Modal: ({ className, bodyClassName, confirmClose, children, onCancel }: import("./Modal.d.ts").ModalProps) => import("react/jsx-runtime").JSX.Element;
5
5
  Title: ({ children }: import("./Title.d.ts").TitleProps) => null;
6
6
  Action: ({ children }: import("./Action.d.ts").ActionProps) => null;
7
7
  Trigger: ({ className, children }: import("./Trigger.d.ts").TriggerProps) => import("react/jsx-runtime").JSX.Element;
@@ -16,4 +16,15 @@ export interface ModalProps {
16
16
  /** Ask for close confirmation before dismissing. */
17
17
  confirmClose?: boolean;
18
18
  }
19
- export declare const Modal: ({ className, title, action, open, onCancel, bodyClassName, children, confirmClose, }: ModalProps) => import("react/jsx-runtime").JSX.Element;
19
+ export declare const Modal: {
20
+ ({ className, title, action, open, onCancel, bodyClassName, children, confirmClose, }: ModalProps): import("react/jsx-runtime").JSX.Element;
21
+ Window: ({ open, onCancel, title, children }: WindowProps) => import("react/jsx-runtime").JSX.Element | null;
22
+ };
23
+ interface WindowProps {
24
+ open: boolean;
25
+ onCancel: () => void;
26
+ title: ReactNode;
27
+ children: ReactNode;
28
+ }
29
+ export declare const Window: ({ open, onCancel, title, children }: WindowProps) => import("react/jsx-runtime").JSX.Element | null;
30
+ export {};
@@ -6,9 +6,9 @@ import { type HTMLAttributes, type ReactNode, type RefObject } from "react";
6
6
  export declare const Client: {
7
7
  (): import("react/jsx-runtime").JSX.Element;
8
8
  Wrapper: ({ children, theme, lang, dictionary, signals, reconnect, }: ClientWrapperProps) => import("react/jsx-runtime").JSX.Element;
9
- Bridge: ({ env, lang, theme, prefix, gaTrackingId }: ClientBridgeProps) => "" | import("react/jsx-runtime").JSX.Element | undefined;
9
+ Bridge: ({ env, lang, theme, prefix, gaTrackingId, }: ClientBridgeProps) => "" | import("react/jsx-runtime").JSX.Element | undefined;
10
10
  Inner: () => import("react/jsx-runtime").JSX.Element;
11
- SsrBridge: ({ lang, prefix }: ClientSsrBridgeProps) => null;
11
+ SsrBridge: ({ lang, prefix, }: ClientSsrBridgeProps) => null;
12
12
  };
13
13
  interface ClientWrapperProps {
14
14
  children: ReactNode;
@@ -37,11 +37,11 @@ interface ClientBridgeProps {
37
37
  prefix?: string;
38
38
  gaTrackingId?: string;
39
39
  }
40
- export declare const ClientBridge: ({ env, lang, theme, prefix, gaTrackingId }: ClientBridgeProps) => "" | import("react/jsx-runtime").JSX.Element | undefined;
40
+ export declare const ClientBridge: ({ env, lang, theme, prefix, gaTrackingId, }: ClientBridgeProps) => "" | import("react/jsx-runtime").JSX.Element | undefined;
41
41
  export declare const ClientInner: () => import("react/jsx-runtime").JSX.Element;
42
42
  interface ClientSsrBridgeProps {
43
43
  lang: string;
44
44
  prefix?: string;
45
45
  }
46
- export declare const ClientSsrBridge: ({ lang, prefix }: ClientSsrBridgeProps) => null;
46
+ export declare const ClientSsrBridge: ({ lang, prefix, }: ClientSsrBridgeProps) => null;
47
47
  export {};
@@ -1,9 +1,9 @@
1
1
  "use client";
2
+ import * as Dialog from "@radix-ui/react-dialog";
2
3
  import { useDrag } from "@use-gesture/react";
3
4
  import { clsx, usePage } from "akanjs/client";
4
5
  import { animated } from "akanjs/ui";
5
- import { type ReactNode, useCallback, useContext, useEffect, useId, useRef, useState } from "react";
6
- import { createPortal } from "react-dom";
6
+ import { type ReactNode, useContext, useEffect, useRef, useState } from "react";
7
7
  import { BiX } from "react-icons/bi";
8
8
  import { config, useSpring } from "react-spring";
9
9
 
@@ -11,8 +11,6 @@ import { DialogContext } from "./context";
11
11
 
12
12
  const MODAL_MARGIN = 0;
13
13
  const OPACITY = { START: 0, END: 1 };
14
- let bodyScrollLockCount = 0;
15
- let previousBodyOverflow = "";
16
14
 
17
15
  const interpolate = (o: number, i: number, t: number) => {
18
16
  return o + (i - o) * t;
@@ -27,80 +25,41 @@ export interface ModalProps {
27
25
  }
28
26
  export const Modal = ({ className, bodyClassName, confirmClose, children, onCancel }: ModalProps) => {
29
27
  const { open, setOpen, title, action } = useContext(DialogContext);
28
+ const openRef = useRef<boolean>(open);
30
29
  const { l } = usePage();
31
30
  const ref = useRef<HTMLDivElement>(null);
32
- const closeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
33
- const closingRef = useRef(false);
34
- const focusedElementRef = useRef<HTMLElement | null>(null);
35
- const titleId = useId();
36
- const contentId = useId();
37
31
  const [{ translate }, api] = useSpring(() => ({ translate: 1 }));
38
- const [portalElement, setPortalElement] = useState<HTMLElement | null>(null);
39
- const [isMounted, setIsMounted] = useState(open);
40
32
  const [showBackground, setShowBackground] = useState(false);
41
-
42
- const openModal = useCallback(
43
- async ({ canceled }: { canceled?: boolean } = {}) => {
44
- closingRef.current = false;
45
- setIsMounted(true);
46
- if (closeTimerRef.current) clearTimeout(closeTimerRef.current);
47
- closeTimerRef.current = setTimeout(() => {
48
- setShowBackground(true);
49
- }, 100);
50
- await Promise.all(api.start({ translate: 0, immediate: false, config: canceled ? config.wobbly : config.stiff }));
51
- },
52
- [api],
53
- );
54
-
55
- const closeModal = useCallback(
56
- async ({
57
- velocity = 0,
58
- confirmClose,
59
- notifyCancel = true,
60
- }: {
61
- velocity?: number;
62
- confirmClose?: boolean;
63
- notifyCancel?: boolean;
64
- }) => {
65
- if (closingRef.current) return;
66
- if (confirmClose && !window.confirm(l("base.confirmClose"))) {
67
- return;
68
- }
69
-
70
- closingRef.current = true;
71
- if (closeTimerRef.current) clearTimeout(closeTimerRef.current);
72
- closeTimerRef.current = setTimeout(() => {
73
- setShowBackground(false);
74
- }, 100);
75
- await Promise.all(api.start({ translate: 1, immediate: false, config: { ...config.stiff, velocity } }));
76
- setIsMounted(false);
77
- setOpen(false);
78
- if (notifyCancel) onCancel?.();
79
- closingRef.current = false;
80
- },
81
- [api, l, onCancel, setOpen],
82
- );
83
-
84
- const requestClose = useCallback(
85
- (options?: { velocity?: number }) => {
86
- void closeModal({ velocity: options?.velocity, confirmClose });
87
- },
88
- [closeModal, confirmClose],
89
- );
90
-
33
+ const openModal = async ({ canceled }: { canceled?: boolean } = {}) => {
34
+ setTimeout(() => {
35
+ setShowBackground(true);
36
+ }, 100);
37
+ await Promise.all(api.start({ translate: 0, immediate: false, config: canceled ? config.wobbly : config.stiff }));
38
+ };
39
+ const closeModal = async ({ velocity = 0, confirmClose }: { velocity?: number; confirmClose?: boolean }) => {
40
+ if (confirmClose && !window.confirm(l("base.confirmClose"))) {
41
+ return;
42
+ }
43
+ setTimeout(() => {
44
+ setShowBackground(false);
45
+ }, 100);
46
+ await Promise.all(api.start({ translate: 1, immediate: false, config: { ...config.stiff, velocity } }));
47
+ setOpen(false);
48
+ onCancel?.();
49
+ };
91
50
  const bind = useDrag(
92
51
  ({ last, velocity: [, vy], direction: [, dy], offset: [, oy], movement: [, my], cancel, canceled }) => {
93
52
  if (!ref.current) return;
94
- const height = Math.max((ref.current.clientHeight || MODAL_MARGIN) - MODAL_MARGIN, 1);
53
+ const height = (ref.current.clientHeight || MODAL_MARGIN) - MODAL_MARGIN;
95
54
  if (my > 70) cancel();
96
55
  if (last) {
97
- if (my > height * 0.5 || (vy > 0.5 && dy > 0)) requestClose({ velocity: vy / height });
56
+ if (my > height * 0.5 || (vy > 0.5 && dy > 0))
57
+ void closeModal({ velocity: vy / height, confirmClose: confirmClose });
98
58
  else void openModal({ canceled });
99
59
  } else void api.start({ translate: oy / height, immediate: true });
100
60
  },
101
61
  { from: () => [0, translate.get()], filterTaps: true, bounds: { top: 0 }, rubberband: true },
102
62
  );
103
-
104
63
  const opacity = translate.to((t) => {
105
64
  return interpolate(OPACITY.END, OPACITY.START, t);
106
65
  });
@@ -109,104 +68,33 @@ export const Modal = ({ className, bodyClassName, confirmClose, children, onCanc
109
68
  });
110
69
 
111
70
  useEffect(() => {
112
- if (typeof document === "undefined") return;
113
- setPortalElement(document.body);
114
- }, []);
115
-
116
- useEffect(() => {
117
- if (open) {
118
- void openModal();
119
- }
120
- }, [open, openModal]);
121
-
122
- useEffect(() => {
123
- if (!open && isMounted) {
124
- void closeModal({ notifyCancel: false });
125
- }
126
- }, [closeModal, isMounted, open]);
127
-
128
- useEffect(() => {
129
- if (!isMounted || typeof document === "undefined") return;
130
-
131
- bodyScrollLockCount += 1;
132
- if (bodyScrollLockCount === 1) {
133
- previousBodyOverflow = document.body.style.overflow;
134
- document.body.style.overflow = "hidden";
135
- }
136
-
137
- return () => {
138
- bodyScrollLockCount -= 1;
139
- if (bodyScrollLockCount === 0) {
140
- document.body.style.overflow = previousBodyOverflow;
141
- previousBodyOverflow = "";
142
- }
143
- };
144
- }, [isMounted]);
145
-
146
- useEffect(() => {
147
- if (!isMounted || !portalElement || typeof document === "undefined") return;
148
-
149
- focusedElementRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
150
- queueMicrotask(() => {
151
- ref.current?.focus();
152
- });
153
-
154
- return () => {
155
- if (focusedElementRef.current && document.contains(focusedElementRef.current)) {
156
- focusedElementRef.current.focus();
157
- }
158
- focusedElementRef.current = null;
159
- };
160
- }, [isMounted, portalElement]);
161
-
162
- useEffect(() => {
163
- if (!isMounted) return;
164
-
165
- const onKeyDown = (event: KeyboardEvent) => {
166
- if (event.key !== "Escape") return;
167
- event.preventDefault();
168
- requestClose();
169
- };
170
-
171
- window.addEventListener("keydown", onKeyDown);
172
- return () => {
173
- window.removeEventListener("keydown", onKeyDown);
174
- };
175
- }, [isMounted, requestClose]);
176
-
177
- useEffect(() => {
178
- return () => {
179
- if (closeTimerRef.current) clearTimeout(closeTimerRef.current);
180
- };
181
- }, []);
182
-
183
- if (!isMounted || !portalElement) return null;
184
-
185
- return createPortal(
186
- <>
187
- <div
188
- className={clsx("fixed inset-0 z-10", showBackground && "animate-fadeIn bg-black/50 backdrop-blur-md")}
189
- onClick={(event) => {
190
- if (event.target !== event.currentTarget) return;
191
- requestClose();
71
+ if (openRef.current === open) return;
72
+ openRef.current = open;
73
+ if (open) void openModal();
74
+ else void closeModal({});
75
+ }, [open]);
76
+
77
+ return (
78
+ <Dialog.Portal>
79
+ <Dialog.Overlay
80
+ onClick={() => {
81
+ void closeModal({ confirmClose });
192
82
  }}
193
- />
194
- <div className="fixed top-1/2 left-1/2 z-10 flex -translate-x-1/2 -translate-y-1/2 items-center justify-center">
83
+ >
84
+ {showBackground ? (
85
+ <div className={"fixed inset-0 z-10 bg-base-content/50 backdrop-blur-md data-[state=open]:animate-fadeIn"} />
86
+ ) : null}
87
+ </Dialog.Overlay>
88
+ <Dialog.Content
89
+ className="fixed top-1/2 left-1/2 flex -translate-x-1/2 -translate-y-1/2 items-center justify-center"
90
+ asChild
91
+ forceMount
92
+ >
195
93
  <div className="z-10">
196
- <animated.div
197
- ref={ref}
198
- style={{ translateY, opacity }}
199
- role="dialog"
200
- aria-modal="true"
201
- aria-labelledby={title ? titleId : undefined}
202
- aria-describedby={contentId}
203
- tabIndex={-1}
204
- >
94
+ <animated.div ref={ref} style={{ translateY, opacity }}>
205
95
  <button
206
- type="button"
207
- aria-label="Close"
208
96
  className="btn btn-circle btn-sm absolute top-[-16px] right-0 z-20 md:top-[-40px]"
209
- onClick={() => requestClose()}
97
+ onClick={() => void closeModal({ confirmClose })}
210
98
  >
211
99
  <BiX className="text-3xl" />
212
100
  </button>
@@ -216,33 +104,34 @@ export const Modal = ({ className, bodyClassName, confirmClose, children, onCanc
216
104
  className,
217
105
  )}
218
106
  >
219
- <animated.div
220
- {...bind()}
221
- id={titleId}
222
- className="relative z-10 flex w-full animate-fadeIn cursor-pointer touch-pan-y flex-col items-center justify-center px-4 pt-1"
223
- >
224
- <div className="flex w-full cursor-pointer items-center justify-center pt-1 opacity-50">
225
- <div className="h-1 w-24 rounded-full bg-gray-500" />
226
- </div>
227
- <div className="flex w-full items-center justify-start">
228
- <div className="w-full text-start font-bold text-lg">{title}</div>
107
+ <Dialog.Title asChild>
108
+ <animated.div
109
+ {...bind()}
110
+ className="relative z-10 flex w-full animate-fadeIn cursor-pointer touch-pan-y flex-col items-center justify-center px-4 pt-1"
111
+ >
112
+ <div className="flex w-full cursor-pointer items-center justify-center pt-1 opacity-50">
113
+ <div className="h-1 w-24 rounded-full bg-gray-500" />
114
+ </div>
115
+ <div className="flex w-full items-center justify-start">
116
+ <div className="w-full text-start font-bold text-lg">{title}</div>
117
+ </div>
118
+ </animated.div>
119
+ </Dialog.Title>
120
+ <Dialog.Description asChild>
121
+ <div
122
+ className={clsx(
123
+ "scrollbar-none relative m-2 flex size-full min-w-[90vw] overflow-x-hidden overflow-y-scroll border-base-content/30 border-t-[0.1px] p-4 sm:p-4 md:min-w-[384px] md:px-8 lg:min-w-[576px] xl:min-w-[768px]",
124
+ bodyClassName,
125
+ )}
126
+ >
127
+ {children}
229
128
  </div>
230
- </animated.div>
231
- <div
232
- id={contentId}
233
- className={clsx(
234
- "scrollbar-none relative m-2 flex size-full min-w-[90vw] overflow-x-hidden overflow-y-scroll border-base-content/30 border-t-[0.1px] p-4 sm:p-4 md:min-w-[384px] md:px-8 lg:min-w-[576px] xl:min-w-[768px]",
235
- bodyClassName,
236
- )}
237
- >
238
- {children}
239
- </div>
129
+ </Dialog.Description>
240
130
  {action ? <div className="w-full">{action}</div> : null}
241
131
  </div>
242
132
  </animated.div>
243
133
  </div>
244
- </div>
245
- </>,
246
- portalElement,
134
+ </Dialog.Content>
135
+ </Dialog.Portal>
247
136
  );
248
137
  };
@@ -1,4 +1,5 @@
1
1
  "use client";
2
+ import * as Dialog from "@radix-ui/react-dialog";
2
3
  import { clsx } from "akanjs/client";
3
4
  import { type ReactNode, useEffect, useState } from "react";
4
5
 
@@ -22,9 +23,11 @@ export const Provider = ({ className, defaultOpen = false, open = defaultOpen, c
22
23
  }, [open]);
23
24
  return (
24
25
  <DialogContext.Provider value={{ open: openState, setOpen: setOpenState, title, setTitle, action, setAction }}>
25
- <div data-open={openState} className={clsx("group/dialog", className)}>
26
- {children}
27
- </div>
26
+ <Dialog.Root open={openState}>
27
+ <div data-open={openState} className={clsx("group/dialog", className)}>
28
+ {children}
29
+ </div>
30
+ </Dialog.Root>
28
31
  </DialogContext.Provider>
29
32
  );
30
33
  };
package/ui/Modal.tsx CHANGED
@@ -1,5 +1,7 @@
1
1
  "use client";
2
+ import * as RadixDialog from "@radix-ui/react-dialog";
2
3
  import type { ReactNode } from "react";
4
+ import { BiX } from "react-icons/bi";
3
5
 
4
6
  import { Dialog } from "./Dialog";
5
7
 
@@ -41,3 +43,45 @@ export const Modal = ({
41
43
  </Dialog>
42
44
  );
43
45
  };
46
+
47
+ interface WindowProps {
48
+ open: boolean;
49
+ onCancel: () => void;
50
+ title: ReactNode;
51
+ children: ReactNode;
52
+ }
53
+
54
+ export const Window = ({ open, onCancel, title, children }: WindowProps) => {
55
+ if (!open) return null;
56
+
57
+ return (
58
+ <RadixDialog.Root open={open}>
59
+ <RadixDialog.Portal>
60
+ <RadixDialog.Overlay className="fixed inset-0 bg-black/40" />
61
+ <RadixDialog.Content
62
+ className="fixed top-1/2 left-1/2 z-[2] w-[90%] min-w-auto -translate-x-1/2 -translate-y-1/2 animate-fadeIn rounded-[10px] border-[3px] border-black text-black backdrop-blur-lg md:w-fit"
63
+ style={{
64
+ background: `rgba(255, 255, 255, 0.3)`,
65
+ width: "406px",
66
+ }}
67
+ >
68
+ <RadixDialog.Title className="height-[36px] relative overflow-hidden rounded-t-[6px] border-black border-b-2 bg-white/60 text-center">
69
+ <div className="m-0 text-[22px]">{title}</div>
70
+ <RadixDialog.Close
71
+ onClick={() => {
72
+ onCancel();
73
+ }}
74
+ className="absolute top-0 right-0 flex h-[34px] w-[40px] cursor-pointer items-center justify-center border-black border-l-2"
75
+ >
76
+ <BiX className="text-[32px]" />
77
+ </RadixDialog.Close>
78
+ </RadixDialog.Title>
79
+ <RadixDialog.Description className="overflow-y-hidden rounded-b-[10px] p-2">
80
+ {children}
81
+ </RadixDialog.Description>
82
+ </RadixDialog.Content>
83
+ </RadixDialog.Portal>
84
+ </RadixDialog.Root>
85
+ );
86
+ };
87
+ Modal.Window = Window;
@@ -55,10 +55,8 @@ export const ClientWrapper = ({
55
55
  reconnect = true,
56
56
  }: ClientWrapperProps) => {
57
57
 
58
- if (dictionary) {
59
- Translator.seed(lang, dictionary);
60
-
61
- if (typeof window !== "undefined") Translator.setActiveLocale(lang);
58
+ if (dictionary) Translator.seed(lang, dictionary);
59
+ if (getEnv().renderMode === "ssr") {
62
60
  }
63
61
  useLayoutEffect(() => {
64
62
  Logger.rawLog(logo);
@@ -73,7 +71,8 @@ export const ClientWrapper = ({
73
71
  };
74
72
  Client.Wrapper = ClientWrapper;
75
73
 
76
- interface ClientPathWrapperProps extends Omit<HTMLAttributes<HTMLDivElement>, "style"> {
74
+ interface ClientPathWrapperProps
75
+ extends Omit<HTMLAttributes<HTMLDivElement>, "style"> {
77
76
  bind?: () => HTMLAttributes<HTMLDivElement>;
78
77
  wrapperRef?: RefObject<HTMLDivElement | null> | null;
79
78
  pageType?: "current" | "prev" | "cached";
@@ -94,12 +93,18 @@ export const ClientPathWrapper = ({
94
93
  layoutStyle = "web",
95
94
  ...props
96
95
  }: ClientPathWrapperProps) => {
97
- const href = location?.href ?? (typeof window !== "undefined" ? window.location.href : "");
98
- const hash = location?.hash ?? (typeof window !== "undefined" ? window.location.hash : "");
96
+ const href =
97
+ location?.href ??
98
+ (typeof window !== "undefined" ? window.location.href : "");
99
+ const hash =
100
+ location?.hash ??
101
+ (typeof window !== "undefined" ? window.location.hash : "");
99
102
  const pathname = location?.pathname ?? "/";
100
103
  const params = location?.params ?? {};
101
104
  const searchParams = location?.searchParams ?? {};
102
- const search = location?.search ?? (typeof window !== "undefined" ? window.location.search : "");
105
+ const search =
106
+ location?.search ??
107
+ (typeof window !== "undefined" ? window.location.search : "");
103
108
  const lang = params.lang;
104
109
  const firstPath = pathname.split("/")[2];
105
110
  const pathRoute: PathRoute = location?.pathRoute ?? {
@@ -132,7 +137,9 @@ export const ClientPathWrapper = ({
132
137
  }}
133
138
  >
134
139
  <animated.div
135
- {...(bind && pathRoute.pageState.gesture && gestureEnabled ? bind() : {})}
140
+ {...(bind && pathRoute.pageState.gesture && gestureEnabled
141
+ ? bind()
142
+ : {})}
136
143
  className={clsx("group/path", className)}
137
144
  ref={wrapperRef}
138
145
  {...props}
@@ -154,7 +161,13 @@ interface ClientBridgeProps {
154
161
  gaTrackingId?: string;
155
162
  }
156
163
 
157
- export const ClientBridge = ({ env, lang, theme, prefix, gaTrackingId }: ClientBridgeProps) => {
164
+ export const ClientBridge = ({
165
+ env,
166
+ lang,
167
+ theme,
168
+ prefix,
169
+ gaTrackingId,
170
+ }: ClientBridgeProps) => {
158
171
  const uiOperation = st.use.uiOperation();
159
172
  const pathname = st.use.pathname();
160
173
  const params = st.use.params();
@@ -221,18 +234,26 @@ function applyThemePolicy(theme: AkanTheme): void {
221
234
  }
222
235
  if (theme === "system") {
223
236
  const dark = window.matchMedia("(prefers-color-scheme: dark)").matches;
224
- document.documentElement.setAttribute("data-theme", dark ? "dark" : "light");
237
+ document.documentElement.setAttribute(
238
+ "data-theme",
239
+ dark ? "dark" : "light",
240
+ );
225
241
  return;
226
242
  }
227
243
  document.documentElement.setAttribute("data-theme", theme);
228
244
  }
229
245
 
230
- function buildSearchParams(entries: Iterable<[string, string]>): Record<string, string | string[]> {
246
+ function buildSearchParams(
247
+ entries: Iterable<[string, string]>,
248
+ ): Record<string, string | string[]> {
231
249
  const params: Record<string, string | string[]> = {};
232
250
  for (const [key, value] of entries) {
233
251
  const current = params[key];
234
252
  if (current === undefined) params[key] = value;
235
- else params[key] = Array.isArray(current) ? [...current, value] : [current, value];
253
+ else
254
+ params[key] = Array.isArray(current)
255
+ ? [...current, value]
256
+ : [current, value];
236
257
  }
237
258
  return params;
238
259
  }
@@ -252,7 +273,10 @@ interface ClientSsrBridgeProps {
252
273
  lang: string;
253
274
  prefix?: string;
254
275
  }
255
- export const ClientSsrBridge = ({ lang, prefix = "" }: ClientSsrBridgeProps) => {
276
+ export const ClientSsrBridge = ({
277
+ lang,
278
+ prefix = "",
279
+ }: ClientSsrBridgeProps) => {
256
280
  useEffect(() => {
257
281
  const visiblePrefix = getEnv().operationMode === "local" ? prefix : "";
258
282
  const navigateRscWithFallback = (
@@ -266,13 +290,19 @@ export const ClientSsrBridge = ({ lang, prefix = "" }: ClientSsrBridgeProps) =>
266
290
  return;
267
291
  }
268
292
  void navigation.catch((error) => {
269
- Logger.warn(`RSC navigation failed, falling back to document navigation: ${String(error)}`);
293
+ Logger.warn(
294
+ `RSC navigation failed, falling back to document navigation: ${String(error)}`,
295
+ );
270
296
  fallback();
271
297
  });
272
298
  };
273
299
  const syncHref = (href: string) => {
274
300
  const url = new URL(href, window.location.origin);
275
- const { path } = getPathInfo(`${url.pathname}${url.search}${url.hash}`, lang, visiblePrefix);
301
+ const { path } = getPathInfo(
302
+ `${url.pathname}${url.search}${url.hash}`,
303
+ lang,
304
+ visiblePrefix,
305
+ );
276
306
  const searchParams = buildSearchParams(url.searchParams.entries());
277
307
  st.set({ pathname: url.pathname, path, searchParams });
278
308
  };
@@ -284,11 +314,17 @@ export const ClientSsrBridge = ({ lang, prefix = "" }: ClientSsrBridgeProps) =>
284
314
  router: {
285
315
  push: (href, routeOptions) => {
286
316
  syncHref(href);
287
- navigateRscWithFallback(href, routeOptions, () => window.location.assign(href));
317
+ navigateRscWithFallback(href, routeOptions, () =>
318
+ window.location.assign(href),
319
+ );
288
320
  },
289
321
  replace: (href, routeOptions) => {
290
322
  syncHref(href);
291
- navigateRscWithFallback(href, { ...routeOptions, replace: true }, () => window.location.replace(href));
323
+ navigateRscWithFallback(
324
+ href,
325
+ { ...routeOptions, replace: true },
326
+ () => window.location.replace(href),
327
+ );
292
328
  },
293
329
  back: () => {
294
330
  window.history.back();
@@ -310,8 +346,14 @@ export const ClientSsrBridge = ({ lang, prefix = "" }: ClientSsrBridgeProps) =>
310
346
  const visiblePrefix = getEnv().operationMode === "local" ? prefix : "";
311
347
  const sync = () => {
312
348
  const { pathname, search, hash } = window.location;
313
- const { path } = getPathInfo(`${pathname}${search}${hash}`, lang, visiblePrefix);
314
- const searchParams = buildSearchParams(new URLSearchParams(search).entries());
349
+ const { path } = getPathInfo(
350
+ `${pathname}${search}${hash}`,
351
+ lang,
352
+ visiblePrefix,
353
+ );
354
+ const searchParams = buildSearchParams(
355
+ new URLSearchParams(search).entries(),
356
+ );
315
357
  st.set({ pathname: window.location.pathname, path, searchParams });
316
358
  };
317
359
  sync();