akanjs 3.0.0-alpha.110 → 3.0.0-alpha.112

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.
@@ -146,6 +146,7 @@ export const baseDictionary = serviceDictionary(["en", "ko"])
146
146
  password: ["Password", "비밀번호"],
147
147
  passwordConfirm: ["Password Confirm", "비밀번호 확인"],
148
148
  noOptions: ["Can't find any datas", "데이터를 찾을 수 없습니다."],
149
+ noSelection: ["No selection", "선택 안 함"],
149
150
  addModel: ["Add {model}", "{model} 추가"],
150
151
  createModel: ["Create {model}", "{model} 생성하기"],
151
152
  createSuccess: ["{model} create success", "{model} 생성 완료"],
@@ -53,6 +53,26 @@ export class DocumentSchema<Doc = unknown> {
53
53
  return this;
54
54
  }
55
55
 
56
+ removePre<HookDoc = Doc>(type: SaveEventType, hook: DocumentSaveHook<HookDoc>) {
57
+ const hooks = this.preHooks.get(type);
58
+ if (hooks)
59
+ this.preHooks.set(
60
+ type,
61
+ hooks.filter((registered) => registered !== (hook as unknown)),
62
+ );
63
+ return this;
64
+ }
65
+
66
+ removePost<HookDoc = Doc>(type: SaveEventType, hook: DocumentSaveHook<HookDoc>) {
67
+ const hooks = this.postHooks.get(type);
68
+ if (hooks)
69
+ this.postHooks.set(
70
+ type,
71
+ hooks.filter((registered) => registered !== (hook as unknown)),
72
+ );
73
+ return this;
74
+ }
75
+
56
76
  hook(type: DocumentHookName, hook: DocumentSaveHook<Doc>) {
57
77
  const [, phase, event] = /^(before|after)(.+)$/.exec(type) ?? [];
58
78
  if (!phase || !event) throw new Error(`Invalid document hook: ${type}`);
@@ -100,6 +100,27 @@ export const fillMissingFilterArgs = (filterInfo: FilterInfo, args: unknown[]) =
100
100
  return [...args, ...Array(filterInfo.args.length - args.length).fill(undefined)];
101
101
  };
102
102
 
103
+ const queryOptionKeys = new Set(["select", "skip", "limit", "sort", "sample"]);
104
+
105
+ const isQueryOptionArg = (value: unknown) => {
106
+ if (!value || typeof value !== "object") return false;
107
+ const proto = Object.getPrototypeOf(value);
108
+ if (proto !== Object.prototype && proto !== null) return false;
109
+ return Object.keys(value).every((key) => queryOptionKeys.has(key));
110
+ };
111
+
112
+ /**
113
+ * Splits a generated filter method's arguments into the filter's own args and the trailing query option.
114
+ * Shared by the database and service resolvers so the two cannot drift into disagreeing about what an option is.
115
+ */
116
+ export const splitFilterArgs = (filterInfo: FilterInfo, args: unknown[]) => {
117
+ const hasQueryOption = args.length > filterInfo.args.length || isQueryOptionArg(args.at(-1));
118
+ return {
119
+ queryArgs: fillMissingFilterArgs(filterInfo, hasQueryOption ? args.slice(0, -1) : args),
120
+ queryOption: (hasQueryOption ? args.at(-1) : {}) as Record<string, unknown>,
121
+ };
122
+ };
123
+
103
124
  export interface FilterArgInfo {
104
125
  name: string;
105
126
  argRef: ConstantFieldType;
package/document/into.ts CHANGED
@@ -24,14 +24,6 @@ interface DefaultMdlStats<
24
24
  pickById: (docId: string | undefined, projection?: _Projection) => Promise<TDocument>;
25
25
  sample: (query: _FilterQuery, size?: number) => Promise<TDocument[]>;
26
26
  sampleOne: (query: _FilterQuery) => Promise<TDocument | null>;
27
- preSaveListenerSet: Set<(doc: TDocument, type: CRUDEventType, previous?: TDocument) => PromiseOrObject<void>>;
28
- postSaveListenerSet: Set<(doc: TDocument, type: CRUDEventType, previous?: TDocument) => PromiseOrObject<void>>;
29
- preCreateListenerSet: Set<(doc: TDocument, type: CRUDEventType, previous?: TDocument) => PromiseOrObject<void>>;
30
- postCreateListenerSet: Set<(doc: TDocument, type: CRUDEventType, previous?: TDocument) => PromiseOrObject<void>>;
31
- preUpdateListenerSet: Set<(doc: TDocument, type: CRUDEventType, previous?: TDocument) => PromiseOrObject<void>>;
32
- postUpdateListenerSet: Set<(doc: TDocument, type: CRUDEventType, previous?: TDocument) => PromiseOrObject<void>>;
33
- preRemoveListenerSet: Set<(doc: TDocument, type: CRUDEventType, previous?: TDocument) => PromiseOrObject<void>>;
34
- postRemoveListenerSet: Set<(doc: TDocument, type: CRUDEventType, previous?: TDocument) => PromiseOrObject<void>>;
35
27
  listenPre: (
36
28
  eventType: SaveEventType,
37
29
  listener: (doc: TDocument, type: CRUDEventType, previous?: TDocument) => PromiseOrObject<void>,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akanjs",
3
- "version": "3.0.0-alpha.110",
3
+ "version": "3.0.0-alpha.112",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -13,7 +13,6 @@ import {
13
13
  type DocumentUpdateInput,
14
14
  documentQueryHelper,
15
15
  type FindQueryOption,
16
- fillMissingFilterArgs,
17
16
  getFilterInfoByKey,
18
17
  getFilterMeta,
19
18
  getFilterSortByKey,
@@ -22,6 +21,7 @@ import {
22
21
  type Mdl,
23
22
  NoDocumentError,
24
23
  type SaveEventType,
24
+ splitFilterArgs,
25
25
  type UpdateChain,
26
26
  } from "akanjs/document";
27
27
  import {
@@ -107,6 +107,25 @@ export class DatabaseResolver {
107
107
  schema.index(fields as { [key: string]: 1 | -1 });
108
108
  }
109
109
 
110
+ const listen = (phase: "pre" | "post") => {
111
+ return (
112
+ type: SaveEventType,
113
+ listener: (doc: any, type: CRUDEventType, previous?: any) => PromiseOrObject<void>,
114
+ ) => {
115
+ const hook = function (this: any, _next?: () => void, crudType?: CRUDEventType, previous?: any) {
116
+ return listener(this, crudType ?? "update", previous);
117
+ };
118
+ if (phase === "pre") schema.pre(type, hook);
119
+ else schema.post(type, hook);
120
+ return () => {
121
+ if (phase === "pre") schema.removePre(type, hook);
122
+ else schema.removePost(type, hook);
123
+ };
124
+ };
125
+ };
126
+ const listenPreHook = listen("pre");
127
+ const listenPostHook = listen("post");
128
+
110
129
  class DatabaseModelInstance extends adapt(`${modelName}Model`, ({ plug }) => ({
111
130
  __database: plug(DatabaseAdaptorRole, (database) => database),
112
131
  __cache: plug(CacheAdaptorRole, (cache) => new CacheDatabase(modelName, cache)),
@@ -224,50 +243,45 @@ export class DatabaseResolver {
224
243
  };
225
244
  return chain;
226
245
  };
246
+ const pickById = async (id: string | undefined, projection?: any) => {
247
+ if (!id) throw new NoDocumentError("No Document ID");
248
+ const doc = await timedQuery(() => store.findOne({ id }, { select: projection }));
249
+ if (!doc) throw new NoDocumentError(`No Document (${modelName}): ${id}`);
250
+ return doc;
251
+ };
227
252
  return Object.assign(Model, {
228
253
  refName: modelName,
229
- pickOne: (query: QueryOf<any>, projection?: any) => store.pickOne(query, { select: projection }),
230
- pickById: (id: string | undefined, projection?: any) => {
231
- if (!id) throw new NoDocumentError("No Document ID");
232
- return store.findOne({ id }, { select: projection }).then((doc) => {
233
- if (!doc) throw new NoDocumentError(`No Document (${modelName}): ${id}`);
234
- return doc;
235
- });
236
- },
237
- exists: async (query: QueryOf<any>) => await store.exists(query),
238
- sample: (query: QueryOf<any>, size = 1) => store.find(query, { sample: size, limit: size }),
239
- sampleOne: (query: QueryOf<any>) => store.findOne(query, { sample: true }),
240
- find: (query: QueryOf<any>) => createFindManyChain(query),
241
- findOne: (query: QueryOf<any>) => createFindOneChain(query),
242
- findById: (id: string | undefined) => (id ? store.findOne({ id }) : Promise.resolve(null)),
243
- count: (query: QueryOf<any>) => store.count(query),
254
+ pickOne: (query: QueryOf<any>, projection?: any) =>
255
+ timedQuery(() => store.pickOne(query, { select: projection })),
256
+ pickById,
257
+
258
+ pickAndWrite: async (id: string, rawData: any) => await (await pickById(id)).set(rawData).save(),
259
+ pickOneAndWrite: async (query: QueryOf<any>, rawData: any) =>
260
+ await (await timedQuery(() => store.pickOne(query))).set(rawData).save(),
261
+ exists: async (query: QueryOf<any>) => await timedQuery(() => store.exists(query)),
262
+ sample: (query: QueryOf<any>, size = 1) => timedQuery(() => store.find(query, { sample: size, limit: size })),
263
+ sampleOne: (query: QueryOf<any>) => timedQuery(() => store.findOne(query, { sample: true })),
264
+ find: (query: QueryOf<any>, projection?: any) => createFindManyChain(query, { select: projection }),
265
+ findOne: (query: QueryOf<any>, projection?: any) => createFindOneChain(query, { select: projection }),
266
+ findById: (id: string | undefined, projection?: any) =>
267
+ id ? timedQuery(() => store.findOne({ id }, { select: projection })) : Promise.resolve(null),
268
+ count: (query: QueryOf<any>) => timedQuery(() => store.count(query)),
244
269
  updateOne: (query: QueryOf<any>, update: DocumentUpdateInput, options?: { upsert?: boolean }) =>
245
- store.updateOneByQuery(query, update, options),
246
- updateMany: (query: QueryOf<any>, update: DocumentUpdateInput) => store.updateManyByQuery(query, update),
247
- removeOne: (query: QueryOf<any>) => store.removeOneByQuery(query),
248
- removeMany: (query: QueryOf<any>) => store.removeManyByQuery(query),
270
+ timedQuery(() => store.updateOneByQuery(query, update, options)),
271
+ updateMany: (query: QueryOf<any>, update: DocumentUpdateInput) =>
272
+ timedQuery(() => store.updateManyByQuery(query, update)),
273
+ removeOne: (query: QueryOf<any>) => timedQuery(() => store.removeOneByQuery(query)),
274
+ removeMany: (query: QueryOf<any>) => timedQuery(() => store.removeManyByQuery(query)),
249
275
  updateById: (id: string, update: DocumentUpdateInput, options?: { upsert?: boolean }) =>
250
- store.updateOneByQuery({ id }, update, options),
251
- removeById: (id: string) => store.removeOneByQuery({ id }),
276
+ timedQuery(() => store.updateOneByQuery({ id }, update, options)),
277
+ removeById: (id: string) => timedQuery(() => store.removeOneByQuery({ id })),
252
278
 
253
- countDocuments: (query: QueryOf<any>) => store.count(query),
279
+ countDocuments: (query: QueryOf<any>) => timedQuery(() => store.count(query)),
254
280
  bulkWrite: (
255
281
  operations: { updateOne: { filter: QueryOf<any>; update: DocumentUpdateInput; upsert?: boolean } }[],
256
- ) => store.bulkWrite(operations),
257
- listenPre: (
258
- type: SaveEventType,
259
- listener: (doc: any, type: CRUDEventType, previous?: any) => PromiseOrObject<void>,
260
- ) =>
261
- schema.pre(type, function (this: any, _next, crudType, previous) {
262
- return listener(this, crudType ?? "update", previous);
263
- }),
264
- listenPost: (
265
- type: SaveEventType,
266
- listener: (doc: any, type: CRUDEventType, previous?: any) => PromiseOrObject<void>,
267
- ) =>
268
- schema.post(type, function (this: any, _next, crudType, previous) {
269
- return listener(this, crudType ?? "update", previous);
270
- }),
282
+ ) => timedQuery(() => store.bulkWrite(operations)),
283
+ listenPre: listenPreHook,
284
+ listenPost: listenPostHook,
271
285
  });
272
286
  }
273
287
 
@@ -289,40 +303,34 @@ export class DatabaseResolver {
289
303
  }
290
304
  async __pick(query?: QueryOf<any>, queryOption?: FindQueryOption): Promise<any> {
291
305
  const { find, sort, skip, sample, select } = getFindQuery(query, queryOption);
292
- return await this.__store.pickOne(find, { sort, skip, sample, select });
306
+ return await timedQuery(() => this.__store.pickOne(find, { sort, skip, sample, select }));
293
307
  }
294
308
  async __pickId(query?: QueryOf<any>, queryOption?: FindQueryOption): Promise<string> {
295
309
  const { find, sort, skip, sample } = getFindQuery(query, queryOption);
296
- const id = await this.__store.findId(find, { sort, skip, sample });
310
+ const id = await timedQuery(() => this.__store.findId(find, { sort, skip, sample }));
297
311
  if (!id) throw new NoDocumentError(`No Document (${database.refName}): ${JSON.stringify(query)}`);
298
312
  return id;
299
313
  }
300
314
  async __exists(query?: QueryOf<any>): Promise<string | null> {
301
- return await this.__store.exists(query);
315
+ return await timedQuery(() => this.__store.exists(query));
302
316
  }
303
317
  async __count(query?: QueryOf<any>): Promise<number> {
304
318
  return await timedQuery(() => this.__store.count(query));
305
319
  }
306
320
  async __insight(query?: QueryOf<any>): Promise<any> {
307
- return await this.__store.insight(query);
321
+ return await timedQuery(() => this.__store.insight(query));
308
322
  }
309
323
  listenPre(
310
324
  type: SaveEventType,
311
325
  listener: (doc: any, type: CRUDEventType, previous?: any) => PromiseOrObject<void>,
312
326
  ) {
313
- schema.pre(type, function (this: any, _next, crudType, previous) {
314
- return listener(this, crudType ?? "update", previous);
315
- });
316
- return () => undefined;
327
+ return listenPreHook(type, listener);
317
328
  }
318
329
  listenPost(
319
330
  type: SaveEventType,
320
331
  listener: (doc: any, type: CRUDEventType, previous?: any) => PromiseOrObject<void>,
321
332
  ) {
322
- schema.post(type, function (this: any, _next, crudType, previous) {
323
- return listener(this, crudType ?? "update", previous);
324
- });
325
- return () => undefined;
333
+ return listenPostHook(type, listener);
326
334
  }
327
335
  async __get(id: string) {
328
336
  const doc = await this.__loader.load(id);
@@ -345,7 +353,7 @@ export class DatabaseResolver {
345
353
  return this.__loadMany(ids);
346
354
  }
347
355
  async clone(data: DataInputOf<any, any> & { id: string }) {
348
- return await this.__store.clone(data);
356
+ return await timedQuery(() => this.__store.clone(data));
349
357
  }
350
358
  async __create(data: DataInputOf<any, any>) {
351
359
  return await timedQuery(() => this.__store.create(data));
@@ -360,7 +368,7 @@ export class DatabaseResolver {
360
368
  return this.__update(id, data);
361
369
  }
362
370
  async __remove(id: string) {
363
- return await this.__store.remove(id);
371
+ return await timedQuery(() => this.__store.remove(id));
364
372
  }
365
373
  async __removeMany(query: QueryOf<any>) {
366
374
  return await timedQuery(() => this.__store.removeManyByQuery(query));
@@ -380,21 +388,11 @@ export class DatabaseResolver {
380
388
  }
381
389
 
382
390
  const getQueryDataFromKey = (queryKey: string, args: any): { query: any; queryOption: any } => {
383
- const lastArg = args.at(-1);
384
- const hasQueryOption =
385
- lastArg &&
386
- typeof lastArg === "object" &&
387
- (typeof lastArg.select === "object" ||
388
- typeof lastArg.skip === "number" ||
389
- typeof lastArg.limit === "number" ||
390
- typeof lastArg.sort === "string");
391
391
  const filterInfo = getFilterInfoByKey(database.filter, queryKey);
392
392
  const queryFn = filterInfo.queryFn;
393
393
  if (!queryFn) throw new Error(`No query function for key: ${queryKey}`);
394
- const queryArgs = fillMissingFilterArgs(filterInfo, hasQueryOption ? args.slice(0, -1) : args);
395
- const query = queryFn(...queryArgs, documentQueryHelper);
396
- const queryOption = hasQueryOption ? lastArg : {};
397
- return { query, queryOption };
394
+ const { queryArgs, queryOption } = splitFilterArgs(filterInfo, args);
395
+ return { query: queryFn(...queryArgs, documentQueryHelper), queryOption };
398
396
  };
399
397
  Object.entries(filterMeta.query).forEach(([queryKey, filterInfo]) => {
400
398
  const queryFn = filterInfo.queryFn;
@@ -426,35 +424,35 @@ export class DatabaseResolver {
426
424
  return (this as unknown as DatabaseInstance).__pickId(query, queryOption);
427
425
  },
428
426
  [`exists${capitalize(queryKey)}`]: async function (...args: any) {
429
- const query = queryFn(...fillMissingFilterArgs(filterInfo, args), documentQueryHelper);
427
+ const query = queryFn(...splitFilterArgs(filterInfo, args).queryArgs, documentQueryHelper);
430
428
  return (this as unknown as DatabaseInstance).__exists(query);
431
429
  },
432
430
  [`count${capitalize(queryKey)}`]: async function (...args: any) {
433
- const query = queryFn(...fillMissingFilterArgs(filterInfo, args), documentQueryHelper);
431
+ const query = queryFn(...splitFilterArgs(filterInfo, args).queryArgs, documentQueryHelper);
434
432
  return (this as unknown as DatabaseInstance).__count(query);
435
433
  },
436
434
  [`insight${capitalize(queryKey)}`]: async function (...args: any) {
437
- const query = queryFn(...fillMissingFilterArgs(filterInfo, args), documentQueryHelper);
435
+ const query = queryFn(...splitFilterArgs(filterInfo, args).queryArgs, documentQueryHelper);
438
436
  return (this as unknown as DatabaseInstance).__insight(query);
439
437
  },
440
438
  [`query${capitalize(queryKey)}`]: (...args: any) =>
441
- queryFn(...fillMissingFilterArgs(filterInfo, args), documentQueryHelper),
439
+ queryFn(...splitFilterArgs(filterInfo, args).queryArgs, documentQueryHelper),
442
440
  [`remove${capitalize(queryKey)}`]: async function (...args: any) {
443
- const query = queryFn(...fillMissingFilterArgs(filterInfo, args), documentQueryHelper);
441
+ const query = queryFn(...splitFilterArgs(filterInfo, args).queryArgs, documentQueryHelper);
444
442
  return (this as unknown as DatabaseInstance).__removeMany(query);
445
443
  },
446
444
  [`removeOne${capitalize(queryKey)}`]: async function (...args: any) {
447
- const query = queryFn(...fillMissingFilterArgs(filterInfo, args), documentQueryHelper);
445
+ const query = queryFn(...splitFilterArgs(filterInfo, args).queryArgs, documentQueryHelper);
448
446
  return (this as unknown as DatabaseInstance).__removeOne(query);
449
447
  },
450
448
  [`update${capitalize(queryKey)}`]: function (...args: any): UpdateChain {
451
449
  const instance = this as unknown as DatabaseInstance;
452
- const query = queryFn(...fillMissingFilterArgs(filterInfo, args), documentQueryHelper);
450
+ const query = queryFn(...splitFilterArgs(filterInfo, args).queryArgs, documentQueryHelper);
453
451
  return { set: (update) => instance.__updateMany(query, update) };
454
452
  },
455
453
  [`updateOne${capitalize(queryKey)}`]: function (...args: any): UpdateChain {
456
454
  const instance = this as unknown as DatabaseInstance;
457
- const query = queryFn(...fillMissingFilterArgs(filterInfo, args), documentQueryHelper);
455
+ const query = queryFn(...splitFilterArgs(filterInfo, args).queryArgs, documentQueryHelper);
458
456
  return { set: (update) => instance.__updateOne(query, update) };
459
457
  },
460
458
  });
@@ -10,11 +10,11 @@ import {
10
10
  type DocumentUpdateInput,
11
11
  documentQueryHelper,
12
12
  type FindQueryOption,
13
- fillMissingFilterArgs,
14
13
  getFilterInfoByKey,
15
14
  getFilterMeta,
16
15
  type ListQueryOption,
17
16
  type SaveEventType,
17
+ splitFilterArgs,
18
18
  type UpdateChain,
19
19
  } from "akanjs/document";
20
20
  import type { DatabaseService, ServiceCls } from "akanjs/service";
@@ -127,21 +127,11 @@ export class ServiceResolver {
127
127
  const className = capitalize(database.refName);
128
128
  Object.assign(srvRef.prototype, ServiceResolver.#getDefaultDbServiceMethods(database.refName, className, cascade));
129
129
  const getQueryDataFromKey = (queryKey: string, args: any): { query: any; queryOption: any } => {
130
- const lastArg = args.at(-1);
131
- const hasQueryOption =
132
- lastArg &&
133
- typeof lastArg === "object" &&
134
- (typeof lastArg.select === "object" ||
135
- typeof lastArg.skip === "number" ||
136
- typeof lastArg.limit === "number" ||
137
- typeof lastArg.sort === "string");
138
130
  const filterInfo = getFilterInfoByKey(database.filter, queryKey);
139
131
  const queryFn = filterInfo.queryFn;
140
132
  if (!queryFn) throw new Error(`No query function for key: ${queryKey}`);
141
- const queryArgs = fillMissingFilterArgs(filterInfo, hasQueryOption ? args.slice(0, -1) : args);
142
- const query = queryFn(...queryArgs, documentQueryHelper);
143
- const queryOption = hasQueryOption ? lastArg : {};
144
- return { query, queryOption };
133
+ const { queryArgs, queryOption } = splitFilterArgs(filterInfo, args);
134
+ return { query: queryFn(...queryArgs, documentQueryHelper), queryOption };
145
135
  };
146
136
  const filterMeta = getFilterMeta(database.filter);
147
137
  const queryKeys = Object.keys(filterMeta.query);
@@ -189,7 +179,7 @@ export class ServiceResolver {
189
179
  return this.__insight(query);
190
180
  },
191
181
  [`query${capitalize(queryKey)}`]: function (this: DatabaseService, ...args: any) {
192
- return queryFn(...fillMissingFilterArgs(filterInfo, args), documentQueryHelper);
182
+ return queryFn(...splitFilterArgs(filterInfo, args).queryArgs, documentQueryHelper);
193
183
  },
194
184
  [`remove${capitalizedQueryKey}`]: async function (this: DatabaseService, ...args: any) {
195
185
  const { query } = getQueryDataFromKey(queryKey, args);
@@ -1 +1 @@
1
- export declare const baseDictionary: import("./dictInfo.d.ts").ServiceDictInfo<[string, string], "ping" | "pingBody" | "pingParam" | "pingQuery" | "wsPing" | "pubsubPing", "serverUnreachable" | "serverUnavailable" | "gatewayTimeout" | "unexpectedResponse", "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "ok" | "agent" | "connecting" | "new" | "success" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "edit" | "view">;
1
+ export declare const baseDictionary: import("./dictInfo.d.ts").ServiceDictInfo<[string, string], "ping" | "pingBody" | "pingParam" | "pingQuery" | "wsPing" | "pubsubPing", "serverUnreachable" | "serverUnavailable" | "gatewayTimeout" | "unexpectedResponse", "error" | "remove" | "save" | "refresh" | "password" | "skip" | "stop" | "send" | "ok" | "agent" | "connecting" | "new" | "success" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "edit" | "view">;
@@ -1,13 +1,13 @@
1
1
  import type { AgentEndpoint, AgentTurn, BaseEndpoint } from "akanjs/signal";
2
2
  export declare const dictionary: {
3
- base: import("./locale.d.ts").DictModule<import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "ok" | "agent" | "connecting" | "new" | "success" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "edit" | "view">, "base.error.gatewayTimeout" | "base.error.serverUnavailable" | "base.error.serverUnreachable" | "base.error.unexpectedResponse">;
3
+ base: import("./locale.d.ts").DictModule<import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "skip" | "stop" | "send" | "ok" | "agent" | "connecting" | "new" | "success" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "edit" | "view">, "base.error.gatewayTimeout" | "base.error.serverUnavailable" | "base.error.serverUnreachable" | "base.error.unexpectedResponse">;
4
4
  agentTurn: import("./locale.d.ts").DictModule<import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc`, never>;
5
5
  agent: import("./locale.d.ts").DictModule<import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, "agent.error.llmUnavailable" | "agent.error.deepseekRequestFailed">;
6
6
  };
7
- export declare const Err: import("./trans.d.ts").ErrConstructor<"base.error.gatewayTimeout" | "base.error.serverUnavailable" | "base.error.serverUnreachable" | "base.error.unexpectedResponse" | "agent.error.llmUnavailable" | "agent.error.deepseekRequestFailed">, translate: (lang: "en" | "ko" | (string & {}) | "zhChs" | "zhCht" | "ja", key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "ok" | "agent" | "connecting" | "new" | "success" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, data?: import("./trans.d.ts").TranslationData) => string, msg: {
8
- info: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "ok" | "agent" | "connecting" | "new" | "success" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
9
- success: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "ok" | "agent" | "connecting" | "new" | "success" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
10
- error: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "ok" | "agent" | "connecting" | "new" | "success" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
11
- warning: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "ok" | "agent" | "connecting" | "new" | "success" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
12
- loading: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "ok" | "agent" | "connecting" | "new" | "success" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
13
- }, getDictionary: (lang: "en" | "ko" | (string & {}) | "zhChs" | "zhCht" | "ja") => object, getAllDictionary: () => import("./trans.d.ts").RootDictionary, __Dict_Key__: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "ok" | "agent" | "connecting" | "new" | "success" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, __Error_Key__: "base.error.gatewayTimeout" | "base.error.serverUnavailable" | "base.error.serverUnreachable" | "base.error.unexpectedResponse" | "agent.error.llmUnavailable" | "agent.error.deepseekRequestFailed";
7
+ export declare const Err: import("./trans.d.ts").ErrConstructor<"base.error.gatewayTimeout" | "base.error.serverUnavailable" | "base.error.serverUnreachable" | "base.error.unexpectedResponse" | "agent.error.llmUnavailable" | "agent.error.deepseekRequestFailed">, translate: (lang: "en" | "ko" | (string & {}) | "zhChs" | "zhCht" | "ja", key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "skip" | "stop" | "send" | "ok" | "agent" | "connecting" | "new" | "success" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, data?: import("./trans.d.ts").TranslationData) => string, msg: {
8
+ info: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "skip" | "stop" | "send" | "ok" | "agent" | "connecting" | "new" | "success" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
9
+ success: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "skip" | "stop" | "send" | "ok" | "agent" | "connecting" | "new" | "success" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
10
+ error: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "skip" | "stop" | "send" | "ok" | "agent" | "connecting" | "new" | "success" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
11
+ warning: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "skip" | "stop" | "send" | "ok" | "agent" | "connecting" | "new" | "success" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
12
+ loading: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "skip" | "stop" | "send" | "ok" | "agent" | "connecting" | "new" | "success" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
13
+ }, getDictionary: (lang: "en" | "ko" | (string & {}) | "zhChs" | "zhCht" | "ja") => object, getAllDictionary: () => import("./trans.d.ts").RootDictionary, __Dict_Key__: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "skip" | "stop" | "send" | "ok" | "agent" | "connecting" | "new" | "success" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "cameraPromptHeader" | "cameraPromptPhoto" | "cameraPromptPicture" | "cameraPromptCancel" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentAttachDuplicate" | "agentAttachTooMany" | "agentAttachTooMuch" | "agentAnswerNeeded" | "agentQueue" | "agentQueuePlaceholder" | "agentQueued" | "agentQueueEdit" | "agentQueueCancel" | "agentToolDone" | "agentToolFailed" | "agentToolRunning" | "agentToolResult" | "agentTokens" | "agentSummarizing" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "noSelection" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "draftConflict" | "draftApplied" | "draftRestore" | "draftDiscard" | "draftStartOver" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, __Error_Key__: "base.error.gatewayTimeout" | "base.error.serverUnavailable" | "base.error.serverUnreachable" | "base.error.unexpectedResponse" | "agent.error.llmUnavailable" | "agent.error.deepseekRequestFailed";
@@ -28,6 +28,8 @@ export declare class DocumentSchema<Doc = unknown> {
28
28
  readonly indexes: DocumentIndexDescriptor[];
29
29
  pre<HookDoc = Doc>(type: SaveEventType, hook: DocumentSaveHook<HookDoc>): this;
30
30
  post<HookDoc = Doc>(type: SaveEventType, hook: DocumentSaveHook<HookDoc>): this;
31
+ removePre<HookDoc = Doc>(type: SaveEventType, hook: DocumentSaveHook<HookDoc>): this;
32
+ removePost<HookDoc = Doc>(type: SaveEventType, hook: DocumentSaveHook<HookDoc>): this;
31
33
  hook(type: DocumentHookName, hook: DocumentSaveHook<Doc>): this;
32
34
  index(fields: DocumentIndexDescriptor["fields"], options?: Omit<DocumentIndexDescriptor, "fields">): this;
33
35
  createIndex(name: string): DocumentIndexBuilder<this>;
@@ -19,6 +19,14 @@ export declare const getFilterInfoByKey: <ArgNames extends string[] = [], Args e
19
19
  export declare const setFilterInfoByKey: <ArgNames extends string[] = [], Args extends any[] = any[], Model = any>(modelRef: Cls<Model>, key: string, filterInfo: FilterInfo<ArgNames, Args, Model>) => void;
20
20
  export declare const getFilterSortByKey: (modelRef: FilterCls, key: string) => unknown;
21
21
  export declare const fillMissingFilterArgs: (filterInfo: FilterInfo, args: unknown[]) => any[];
22
+ /**
23
+ * Splits a generated filter method's arguments into the filter's own args and the trailing query option.
24
+ * Shared by the database and service resolvers so the two cannot drift into disagreeing about what an option is.
25
+ */
26
+ export declare const splitFilterArgs: (filterInfo: FilterInfo, args: unknown[]) => {
27
+ queryArgs: any[];
28
+ queryOption: Record<string, unknown>;
29
+ };
22
30
  export interface FilterArgInfo {
23
31
  name: string;
24
32
  argRef: ConstantFieldType;
@@ -15,14 +15,6 @@ interface DefaultMdlStats<TDocument, TSchema, _Partial extends Partial<TSchema>
15
15
  pickById: (docId: string | undefined, projection?: _Projection) => Promise<TDocument>;
16
16
  sample: (query: _FilterQuery, size?: number) => Promise<TDocument[]>;
17
17
  sampleOne: (query: _FilterQuery) => Promise<TDocument | null>;
18
- preSaveListenerSet: Set<(doc: TDocument, type: CRUDEventType, previous?: TDocument) => PromiseOrObject<void>>;
19
- postSaveListenerSet: Set<(doc: TDocument, type: CRUDEventType, previous?: TDocument) => PromiseOrObject<void>>;
20
- preCreateListenerSet: Set<(doc: TDocument, type: CRUDEventType, previous?: TDocument) => PromiseOrObject<void>>;
21
- postCreateListenerSet: Set<(doc: TDocument, type: CRUDEventType, previous?: TDocument) => PromiseOrObject<void>>;
22
- preUpdateListenerSet: Set<(doc: TDocument, type: CRUDEventType, previous?: TDocument) => PromiseOrObject<void>>;
23
- postUpdateListenerSet: Set<(doc: TDocument, type: CRUDEventType, previous?: TDocument) => PromiseOrObject<void>>;
24
- preRemoveListenerSet: Set<(doc: TDocument, type: CRUDEventType, previous?: TDocument) => PromiseOrObject<void>>;
25
- postRemoveListenerSet: Set<(doc: TDocument, type: CRUDEventType, previous?: TDocument) => PromiseOrObject<void>>;
26
18
  listenPre: (eventType: SaveEventType, listener: (doc: TDocument, type: CRUDEventType, previous?: TDocument) => PromiseOrObject<void>) => () => void;
27
19
  listenPost: (eventType: SaveEventType, listener: (doc: TDocument, type: CRUDEventType, previous?: TDocument) => PromiseOrObject<void>) => () => void;
28
20
  }
@@ -24,7 +24,7 @@ export interface SelectProps<T extends string | number | boolean | null | undefi
24
24
  selectClassName?: string;
25
25
  selectorClassName?: string;
26
26
  selectedClassName?: string;
27
- /** Allow no value to be selected. */
27
+ /** Allow no value to be selected: offers a clear row and the clear button. */
28
28
  nullable?: boolean;
29
29
  /** Disable open and selection behavior. */
30
30
  disabled?: boolean;
@@ -80,9 +80,7 @@ export const Parent = <Light extends { id: string }>({
80
80
  <div {...agentAttrs(onChange)} className={cn("flex flex-col", className)}>
81
81
  {label ? <Label className={labelClassName} nullable={nullable} label={label} desc={desc} /> : null}
82
82
  <Select<string | null, false, true>
83
- label={label}
84
- desc={desc}
85
- labelClassName={labelClassName}
83
+ nullable={nullable}
86
84
  selectClassName={selectClassName}
87
85
  value={value?.id ?? null}
88
86
  searchable
@@ -173,10 +171,8 @@ export const ParentId = <Light extends { id: string }>({
173
171
  <div {...agentAttrs(onChange)} className={cn("flex flex-col", className)}>
174
172
  {label ? <Label className={labelClassName} nullable={nullable} label={label} desc={desc} /> : null}
175
173
  <Select<string | null, false, true>
174
+ nullable={nullable}
176
175
  searchable
177
- desc={desc}
178
- label={label}
179
- labelClassName={labelClassName}
180
176
  selectClassName={selectClassName}
181
177
  value={value}
182
178
  options={modelList.map((model) => model.id)}
@@ -268,10 +264,8 @@ export const Children = <Light extends { id: string }>({
268
264
  <div {...agentAttrs(onChange)} className={cn("flex flex-col", className)}>
269
265
  {label ? <Label className={labelClassName} nullable={nullable} label={label} desc={desc} /> : null}
270
266
  <Select
267
+ nullable={nullable}
271
268
  searchable
272
- desc={desc}
273
- label={label}
274
- labelClassName={labelClassName}
275
269
  selectClassName={selectClassName}
276
270
  multiple
277
271
  value={(value ?? []).map((model) => model.id)}
@@ -357,10 +351,8 @@ export const ChildrenId = <Light extends { id: string }>({
357
351
  <div {...agentAttrs(onChange)} className={cn("flex flex-col", className)}>
358
352
  {label ? <Label className={labelClassName} nullable={nullable} label={label} desc={desc} /> : null}
359
353
  <Select
354
+ nullable={nullable}
360
355
  searchable
361
- desc={desc}
362
- label={label}
363
- labelClassName={labelClassName}
364
356
  multiple
365
357
 
366
358
  value={value ?? []}
package/ui/Select.tsx CHANGED
@@ -1,14 +1,26 @@
1
1
  "use client";
2
2
  import { type Cls, type EnumInstance, isEnum } from "akanjs/base";
3
3
  import { cn, usePage } from "akanjs/client";
4
+ import { clamp } from "akanjs/common";
4
5
  import { useFieldTool } from "akanjs/store";
5
6
  import { useDebounce } from "akanjs/webkit";
6
- import { type ComponentType, createElement, type ReactNode, useEffect, useMemo, useRef, useState } from "react";
7
+ import {
8
+ type ComponentType,
9
+ createElement,
10
+ type KeyboardEvent,
11
+ type ReactNode,
12
+ useEffect,
13
+ useId,
14
+ useMemo,
15
+ useRef,
16
+ useState,
17
+ } from "react";
7
18
  import { createPortal } from "react-dom";
8
19
  import { BiCheck, BiChevronDown, BiX } from "react-icons/bi";
9
20
  import { BsQuestionCircleFill } from "react-icons/bs";
10
21
  import { TiDelete } from "react-icons/ti";
11
22
  import { agentAttrs } from "./agentAttrs";
23
+ import { Label } from "./Field/Label";
12
24
  import { overlayZ, useOverlayLayerProps } from "./overlayLayer";
13
25
  import { useOverlayPosition } from "./overlayPosition";
14
26
  import { useUiOverride } from "./UiOverride";
@@ -43,7 +55,7 @@ export interface SelectProps<
43
55
  selectClassName?: string;
44
56
  selectorClassName?: string;
45
57
  selectedClassName?: string;
46
- /** Allow no value to be selected. */
58
+ /** Allow no value to be selected: offers a clear row and the clear button. */
47
59
  nullable?: boolean;
48
60
  /** Disable open and selection behavior. */
49
61
  disabled?: boolean;
@@ -91,69 +103,120 @@ const DefaultSelect = <
91
103
  useFieldTool(onChange, { disabled });
92
104
  const { l } = usePage();
93
105
  const [isOpen, setIsOpen] = useState(false);
94
- const labeledOptions: { label: string | boolean | number; value: T }[] = useMemo(
95
- () =>
96
- isEnum(options as Cls)
97
- ? (options as EnumInstance<string, T>).values.map((v) => ({
98
- label: typeof v === "string" ? v : typeof v === "object" ? JSON.stringify(v) : String(v),
99
- value: v,
100
- }))
101
- : (options as { label?: string | boolean | number; value?: T }[])[0]?.label &&
102
- (options as { label?: string | boolean | number; value?: T }[])[0]?.value
103
- ? (options as { label: string | boolean | number; value: T }[])
104
- : (options.map((v: unknown) => ({ label: v as string | boolean | number, value: v as T })) as {
105
- label: string | boolean | number;
106
- value: T;
107
- }[]),
108
- [options],
109
- );
106
+ const labeledOptions: LabelOption<T>[] = useMemo(() => {
107
+ if (isEnum(options as Cls))
108
+ return (options as EnumInstance<string, T>).values.map((v) => ({
109
+ label: typeof v === "string" ? v : typeof v === "object" ? JSON.stringify(v) : String(v),
110
+ value: v,
111
+ }));
112
+ const list = options as (T | LabelOption<T>)[];
113
+ const first = list[0];
114
+
115
+ const isLabeled = typeof first === "object" && first !== null && "label" in first && "value" in first;
116
+ return isLabeled
117
+ ? (list as LabelOption<T>[])
118
+ : (list as T[]).map((v) => ({ label: v as string | boolean | number, value: v }));
119
+ }, [options]);
110
120
 
111
- const [selectedValues, setSelectedValues] = useState<T[]>(multiple ? (value as T[]) : [value as T]);
112
121
  const [searchText, setSearchText] = useState("");
113
122
 
114
123
  const [portal, setPortal] = useState<HTMLElement | null>(null);
115
- const [searchOptions, setSearchOptions] = useState<{ label: ReactNode; value: T }[]>(labeledOptions);
124
+ const [searchOptions, setSearchOptions] = useState<LabelOption<T>[]>(labeledOptions);
125
+ const [activeIdx, setActiveIdx] = useState(-1);
126
+ const listId = useId();
116
127
  const dropdownRef = useRef<HTMLDivElement>(null);
128
+
129
+ const fieldRef = useRef<HTMLDivElement>(null);
117
130
  const optionsRef = useRef<HTMLDivElement>(null);
118
131
 
119
132
  const overlayLayerProps = useOverlayLayerProps();
120
133
  const position = useOverlayPosition({
121
134
  opened: isOpen,
122
- triggerRef: dropdownRef,
135
+ triggerRef: fieldRef,
123
136
  panelRef: optionsRef,
124
137
  align: "start",
125
138
  });
126
139
 
127
- const selected = (v: T) => {
128
- if (multiple) return selectedValues.includes(v);
129
- return v === selectedValues[0];
140
+ const selectedValues: T[] = multiple ? ((value as T[]) ?? []) : [value as T];
141
+ const hasSelection = multiple ? selectedValues.length > 0 : value !== null && value !== undefined;
142
+
143
+ const rows: (LabelOption<T> | null)[] = useMemo(
144
+ () => (nullable ? [null, ...searchOptions] : searchOptions),
145
+ [nullable, searchOptions],
146
+ );
147
+
148
+ const selected = (v: T) => (multiple ? selectedValues.includes(v) : v === selectedValues[0]);
149
+
150
+ const resetSearch = () => {
151
+ setSearchText("");
152
+ setSearchOptions(labeledOptions);
153
+ };
154
+ const close = () => {
155
+ setIsOpen(false);
156
+ setActiveIdx(-1);
157
+ resetSearch();
130
158
  };
131
159
 
132
160
  const handleClickOutside = (event: Event) => {
133
161
  const target = event.target as Node;
134
162
 
135
163
  if (optionsRef.current?.contains(target)) return;
136
- if (dropdownRef.current && !dropdownRef.current.contains(target)) {
137
- setIsOpen(false);
138
- }
164
+ if (dropdownRef.current && !dropdownRef.current.contains(target)) close();
139
165
  };
140
- const onSelect = (v: T, index: number) => {
166
+ const onSelect = (v: T) => {
141
167
  if (multiple) {
142
- if (selectedValues.includes(v)) {
143
- setSelectedValues(selectedValues.filter((rev) => rev !== v));
144
- (onChange as (value: T[], prev: T[]) => void)(
145
- selectedValues.filter((rev) => rev !== v),
146
- selectedValues,
147
- );
148
- } else {
149
- setSelectedValues([...selectedValues, v]);
150
- (onChange as (value: T[], prev: T[]) => void)([...selectedValues, v], selectedValues);
151
- }
168
+ const next = selectedValues.includes(v) ? selectedValues.filter((rev) => rev !== v) : [...selectedValues, v];
169
+ (onChange as (value: T[], prev: T[]) => void)(next, selectedValues);
170
+ resetSearch();
152
171
  } else {
153
- setSelectedValues([v]);
154
172
  (onChange as (value: T, prev: T) => void)(v, selectedValues[0]);
155
- setIsOpen(false);
173
+ close();
174
+ }
175
+ };
176
+ const onClear = () => {
177
+ if (multiple) (onChange as (value: T[], prev: T[]) => void)([], selectedValues);
178
+ else (onChange as (value: T | null, prev: T | null) => void)(null, selectedValues[0]);
179
+ close();
180
+ };
181
+ const commit = (idx: number) => {
182
+ const row = rows[idx];
183
+ if (row === undefined) close();
184
+ else if (row === null) onClear();
185
+ else onSelect(row.value);
186
+ };
187
+ const onKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
188
+ if (disabled) return;
189
+ if (e.key === "Tab") {
190
+ if (isOpen) close();
191
+ return;
192
+ }
193
+ if (e.key === "Escape") {
194
+ if (!isOpen) return;
195
+ e.preventDefault();
196
+ close();
197
+ return;
198
+ }
199
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
200
+ e.preventDefault();
201
+ if (!isOpen) {
202
+ setIsOpen(true);
203
+ setActiveIdx(e.key === "ArrowUp" ? rows.length - 1 : 0);
204
+ } else if (rows.length) setActiveIdx(clamp(activeIdx + (e.key === "ArrowDown" ? 1 : -1), 0, rows.length - 1));
205
+ return;
206
+ }
207
+ if (e.key === "Home" || e.key === "End") {
208
+ if (!isOpen || !rows.length) return;
209
+ e.preventDefault();
210
+ setActiveIdx(e.key === "Home" ? 0 : rows.length - 1);
211
+ return;
156
212
  }
213
+
214
+ if (e.key !== "Enter" && !(e.key === " " && !searchable)) return;
215
+ e.preventDefault();
216
+ if (!isOpen) {
217
+ setIsOpen(true);
218
+ setActiveIdx(0);
219
+ } else commit(activeIdx);
157
220
  };
158
221
  const debouncedOnSearch = useDebounce(
159
222
  (text: string) => {
@@ -185,66 +248,86 @@ const DefaultSelect = <
185
248
  }, [isOpen]);
186
249
 
187
250
  useEffect(() => {
188
- if (multiple) setSelectedValues(value as T[]);
189
- else setSelectedValues([value as T]);
190
- }, [value]);
251
+ setActiveIdx(-1);
252
+ }, [rows]);
253
+
254
+ useEffect(() => {
255
+ if (!isOpen || activeIdx < 0) return;
256
+ optionsRef.current?.querySelector<HTMLElement>(`[data-idx="${activeIdx}"]`)?.scrollIntoView({ block: "nearest" });
257
+ }, [activeIdx, isOpen]);
191
258
 
192
259
  return (
193
260
  <div {...agentAttrs(onChange)} className={cn("relative min-w-[150px]", className)} ref={dropdownRef}>
261
+ {label ? <Label className={labelClassName} label={label} desc={desc} nullable={nullable} /> : null}
194
262
  <div
263
+ ref={fieldRef}
264
+ role="combobox"
265
+ aria-haspopup="listbox"
266
+ aria-expanded={isOpen}
267
+ aria-controls={listId}
268
+ aria-disabled={disabled}
269
+ aria-activedescendant={isOpen && activeIdx >= 0 ? `${listId}-${activeIdx}` : undefined}
270
+ tabIndex={disabled || searchable ? -1 : 0}
195
271
  data-open={isOpen}
196
272
  className={cn(
197
273
  "flex h-auto min-h-[40px] w-full cursor-pointer items-center rounded-field border border-input px-0 py-1 pr-5 pl-1 focus:outline-hidden",
198
- "bg-background data-[open=true]:border-primary",
274
+ "bg-background focus-visible:border-primary data-[open=true]:border-primary",
199
275
  disabled && "pointer-events-none opacity-50",
200
276
  selectClassName,
201
277
  isOpen && "border-border",
202
278
  )}
279
+ onKeyDown={onKeyDown}
203
280
  onClick={() => {
204
281
  if (disabled) return;
205
- setIsOpen(!isOpen);
282
+ if (isOpen) close();
283
+ else setIsOpen(true);
206
284
  }}
207
285
  >
208
286
  <span className="flex w-full flex-wrap items-center gap-1">
209
- {multiple ? (
210
- (value as T[]).map((v, index) => {
211
- const optionValue = labeledOptions.find((option) => option.value === v);
212
- if (!optionValue) return null;
213
- return (
214
- <div
215
- key={index}
216
- className="flex items-center gap-2 rounded-field bg-primary px-2 py-1 text-primary-foreground text-xs"
217
- >
218
- {renderSelected ? renderSelected(optionValue.value) : optionValue.label}
219
- <button
220
- onClick={(e) => {
221
- e.stopPropagation();
222
- e.preventDefault();
223
- onSelect(optionValue.value, index);
224
- }}
225
- className="opacity-50 duration-300 hover:opacity-100"
287
+ {multiple
288
+ ? selectedValues.map((v, index) => {
289
+ const optionValue = labeledOptions.find((option) => option.value === v);
290
+ if (!optionValue) return null;
291
+ return (
292
+ <div
293
+ key={index}
294
+ className="flex items-center gap-2 rounded-field bg-primary px-2 py-1 text-primary-foreground text-xs"
226
295
  >
227
- <BiX className="text-base" />
228
- </button>
229
- </div>
230
- );
231
- })
232
- ) : (
296
+ {renderSelected ? renderSelected(optionValue.value) : optionValue.label}
297
+ <button
298
+ type="button"
299
+ onClick={(e) => {
300
+ e.stopPropagation();
301
+ e.preventDefault();
302
+ onSelect(optionValue.value);
303
+ }}
304
+ className="opacity-50 duration-300 hover:opacity-100"
305
+ >
306
+ <BiX className="text-base" />
307
+ </button>
308
+ </div>
309
+ );
310
+ })
311
+ : null}
312
+ {!multiple && hasSelection ? (
233
313
  <div className="px-2 py-1">
234
- {(value as T)
235
- ? (() => {
236
- const optionValue = searchOptions.find((option) => selected(option.value));
237
- if (!optionValue) return null;
238
- return renderSelected ? renderSelected(optionValue.value) : optionValue.label;
239
- })()
240
- : ""}
314
+ {(() => {
315
+
316
+ const optionValue = labeledOptions.find((option) => selected(option.value));
317
+ if (!optionValue) return null;
318
+ return renderSelected ? renderSelected(optionValue.value) : optionValue.label;
319
+ })()}
241
320
  </div>
242
- )}
321
+ ) : null}
322
+ {!hasSelection && !searchable && placeholder ? (
323
+ <span className="px-2 py-1 text-muted-foreground">{placeholder}</span>
324
+ ) : null}
243
325
  {searchable ? (
244
326
  <input
245
327
  type="text"
328
+ aria-autocomplete="list"
246
329
  className="w-full flex-1 border-none bg-transparent shadow-none outline-none focus:border-none focus:shadow-none focus:outline-none"
247
- placeholder={selectedValues.length > 0 ? "" : placeholder}
330
+ placeholder={hasSelection ? "" : placeholder}
248
331
  value={searchText}
249
332
  onChange={(e) => {
250
333
  if (!isOpen) setIsOpen(true);
@@ -266,18 +349,13 @@ const DefaultSelect = <
266
349
  />
267
350
  ) : null}
268
351
  </span>
269
- {(multiple && selectedValues.length) ||
270
- (!multiple && selectedValues[0] !== undefined && (selectedValues[0] as T) !== null) ? (
352
+ {nullable && hasSelection ? (
271
353
  <TiDelete
272
354
  className="absolute right-8 z-2 h-full text-lg duration-300 hover:cursor-pointer hover:opacity-50"
273
355
  onClick={(e) => {
274
356
  e.stopPropagation();
275
357
  e.preventDefault();
276
- setSearchText("");
277
- setSelectedValues([]);
278
- if (multiple) (onChange as (value: T[], prev: T[]) => void)([], selectedValues);
279
- else (onChange as (value: T | null, prev: T | null) => void)(null, selectedValues[0]);
280
- setIsOpen(false);
358
+ onClear();
281
359
  }}
282
360
  />
283
361
  ) : null}
@@ -289,6 +367,10 @@ const DefaultSelect = <
289
367
  ? createPortal(
290
368
  <div
291
369
  ref={optionsRef}
370
+ id={listId}
371
+ role="listbox"
372
+ aria-multiselectable={multiple}
373
+ aria-hidden={!isOpen}
292
374
  {...overlayLayerProps}
293
375
  data-open={isOpen}
294
376
 
@@ -307,49 +389,50 @@ const DefaultSelect = <
307
389
  selectorClassName,
308
390
  )}
309
391
  >
310
- {nullable && (
311
- <div
312
- className="cursor-pointer p-2"
313
- onClick={() => {
314
- setIsOpen(false);
315
- }}
316
- />
317
- )}
318
- {searchOptions.length > 0
319
- ? searchOptions.map((option, index) => {
320
- const isSelected = selected(option.value);
321
- return (
322
- <div key={index} className="group">
392
+ {rows.map((row, idx) => {
393
+ const isSelected = row ? selected(row.value) : !hasSelection;
394
+ return (
395
+ <div key={idx} className="group">
396
+ <div
397
+ id={`${listId}-${idx}`}
398
+ role="option"
399
+ aria-selected={isSelected}
400
+ data-idx={idx}
401
+ data-active={idx === activeIdx}
402
+ className={cn(
403
+ "relative mx-1 my-0.5 cursor-pointer rounded-field p-2 transition-colors last:border-b-0 hover:bg-muted data-[active=true]:bg-muted",
404
+ !row && "text-foreground/50",
405
+ !multiple && isSelected && selectedClassName,
406
+ isSelected && "bg-success/70 text-success-foreground",
407
+ )}
408
+ onClick={() => {
409
+ commit(idx);
410
+ }}
411
+ >
412
+ {row
413
+ ? renderOption
414
+ ? renderOption(row.value)
415
+ : row.label
416
+ : (placeholder ?? l("base.noSelection"))}
417
+ <div className="absolute top-1/2 right-2 -translate-y-1/2 flex-wrap duration-200">
323
418
  <div
324
419
  className={cn(
325
- "relative mx-1 my-0.5 cursor-pointer rounded-field p-2 transition-colors last:border-b-0 hover:bg-muted",
326
- !multiple && isSelected && selectedClassName,
327
- isSelected && "bg-success/70 text-success-foreground",
420
+ "duration-200",
421
+ isSelected ? "translate-y-0 opacity-100" : "-translate-y-full opacity-0",
328
422
  )}
329
- onClick={() => {
330
- onSelect(option.value, index);
331
- setSearchText("");
332
- }}
333
423
  >
334
- {/* {option.label} */}
335
- {renderOption ? renderOption(option.value) : option.label}
336
- <div className="absolute top-1/2 right-2 -translate-y-1/2 flex-wrap duration-200">
337
- <div
338
- className={cn(
339
- "duration-200",
340
- isSelected ? "translate-y-0 opacity-100" : "-translate-y-full opacity-0",
341
- )}
342
- >
343
- <BiCheck />
344
- </div>
345
- </div>
346
- </div>
347
- <div className="h-px w-full px-2 group-last:hidden">
348
- <div className="size-full bg-foreground/10" />
424
+ <BiCheck />
349
425
  </div>
350
426
  </div>
351
- );
352
- })
427
+ </div>
428
+ <div className="h-px w-full px-2 group-last:hidden">
429
+ <div className="size-full bg-foreground/10" />
430
+ </div>
431
+ </div>
432
+ );
433
+ })}
434
+ {searchOptions.length
435
+ ? null
353
436
  : (empty ?? (
354
437
  <div className="flex size-full flex-col items-center justify-center gap-2 p-2 text-center text-5xl text-foreground/50">
355
438
  <BsQuestionCircleFill />