@aiquants/contact-map 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -77
- package/dist/client.d.mts +7 -4
- package/dist/client.d.ts +7 -4
- package/dist/client.js +2 -2
- package/dist/client.mjs +2 -2
- package/dist/index.d.mts +10 -6
- package/dist/index.d.ts +10 -6
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/server.d.mts +3 -11
- package/dist/server.d.ts +3 -11
- package/dist/server.js +2 -2
- package/dist/server.mjs +2 -2
- package/dist/styles/contact-map.standalone.css +1 -1
- package/dist/{tree-types-CDq16q63.d.mts → tree-types-B9J5unS2.d.mts} +1 -1
- package/dist/{tree-types-CDq16q63.d.ts → tree-types-B9J5unS2.d.ts} +1 -1
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -49,12 +49,12 @@ Peer dependencies:
|
|
|
49
49
|
```json
|
|
50
50
|
{
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"@aiquants/directory-tree": "
|
|
53
|
-
"@aiquants/fuzzy-search": "
|
|
54
|
-
"@aiquants/resize-panels": "
|
|
55
|
-
"@aiquants/select-box": "
|
|
56
|
-
"@aiquants/swipe-overlay": "
|
|
57
|
-
"@aiquants/virtualscroll": "
|
|
52
|
+
"@aiquants/directory-tree": "^4.1.1",
|
|
53
|
+
"@aiquants/fuzzy-search": "^3.2.0",
|
|
54
|
+
"@aiquants/resize-panels": "^2.1.0",
|
|
55
|
+
"@aiquants/select-box": "^0.16.3",
|
|
56
|
+
"@aiquants/swipe-overlay": "^1.2.5",
|
|
57
|
+
"@aiquants/virtualscroll": "^3.16.0",
|
|
58
58
|
"drizzle-orm": ">=1.0.0-beta.4 <2.0.0",
|
|
59
59
|
"react": ">=18",
|
|
60
60
|
"react-dom": ">=18",
|
|
@@ -64,6 +64,9 @@ Peer dependencies:
|
|
|
64
64
|
}
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
+
These are the ranges this version is published with. The repository's `package.json` writes the `@aiquants` ones as `workspace:^`, which publishing expands to `^` and the workspace's version, so `pnpm view @aiquants/contact-map@<version> peerDependencies` prints the ranges of any version.
|
|
68
|
+
これはこの版を公開したときの範囲。リポジトリの `package.json` は `@aiquants` のものを `workspace:^` と書き、公開のときにワークスペースの版の `^` へ展開されるので、どの版の範囲も `pnpm view @aiquants/contact-map@<version> peerDependencies` で確かめられる。
|
|
69
|
+
|
|
67
70
|
---
|
|
68
71
|
|
|
69
72
|
## Architecture & Entry Points / アーキテクチャとエントリポイント
|
|
@@ -73,7 +76,8 @@ Peer dependencies:
|
|
|
73
76
|
| `@aiquants/contact-map` | Universal | Domain types, Zod schemas, wire protocols, geometry, parsers, tree builders, and search query normalization. |
|
|
74
77
|
| `@aiquants/contact-map/client` | Browser / SSR | React components (`ContactMapSearchPage`, `ContactMapSearchListView`, `ContactMapSearchTreeView`), hooks, and Worker clients. |
|
|
75
78
|
| `@aiquants/contact-map/server` | Node / Server | DI port interfaces (`ContactMapServerPorts`), streaming handlers, and Drizzle MSSQL service queries. |
|
|
76
|
-
| `@aiquants/contact-map/
|
|
79
|
+
| `@aiquants/contact-map/worker/treeWorker` | Web Worker (ES module) | The tree Worker script. The tree loads it from beside the client bundle (`dist/worker/treeWorker.mjs`) unless `workerUrls.treeWorker` points elsewhere or `treeWorkerFactory` builds the Worker. |
|
|
80
|
+
| `@aiquants/contact-map/styles/contact-map.standalone.css` | CSS | Pre-compiled Tailwind v4 utility styles for a host without Tailwind CSS (see Styling). |
|
|
77
81
|
|
|
78
82
|
---
|
|
79
83
|
|
|
@@ -105,33 +109,38 @@ export async function loader({ request }: { request: Request }) {
|
|
|
105
109
|
}
|
|
106
110
|
```
|
|
107
111
|
|
|
112
|
+
The handler calls `authenticate` in one way only, `authenticate(request, { failureRedirect: null })`, so the arrow function above takes its parameter types from the setting, and a host function with overloads passes as it is when one of them takes `{ failureRedirect: null }` (`authenticate: authenticateInLoader`).
|
|
113
|
+
ハンドラーは `authenticate` を 1 通りにだけ呼ぶ (`authenticate(request, { failureRedirect: null })`)。そのため上の矢印関数は引数の型を設定から得て、オーバーロードを持つホストの関数は、どれかが `{ failureRedirect: null }` を受け取ればそのまま渡せる (`authenticate: authenticateInLoader`)。
|
|
114
|
+
|
|
108
115
|
`db`, `table`, `ensureConnection` and `authenticate` are required, and `createContactMapStreamHandler` / `createContactMapServer` throw a `TypeError` when `db` or `table` is missing, `table.contacted_at` is missing or is not a Drizzle column, `ensureConnection` or `authenticate` is not a function, or `firstBatchSize` / `nextBatchSize` is given but is not a number.
|
|
109
116
|
They throw a `RangeError` when `table.contacted_at` is not a `datetime2` column (`datetime2` or `datetime2(0)` to `datetime2(7)`; a `datetime`, `datetimeoffset` or `date` column is refused) or `firstBatchSize` or `nextBatchSize` is a number but not an integer from 1 to `Number.MAX_SAFE_INTEGER`.
|
|
110
117
|
Each message names the setting and ends with the value given (`[contact-map] server config authenticate must be the function that returns the request's user (…); got undefined`, `[contact-map] server config table.contacted_at must be a Drizzle datetime2 column; got "datetime"`). A stream open to everyone says so with an `authenticate` that returns a user; leaving the setting out never serves the contacts unauthenticated.
|
|
111
|
-
The handler calls `authenticate(request, { failureRedirect: null })` after checking the resume cursor (a malformed cursor gets 400 without authenticating), answers 401 when no user comes back, and sends a returned `cookie` as `Set-Cookie` on the 401 and on the stream response alike, so a session the call refreshed reaches the browser. The stream is sent with `Cache-Control: no-store, no-transform`.
|
|
118
|
+
The handler calls `authenticate(request, { failureRedirect: null })` after checking the resume cursor (a malformed cursor gets 400 without authenticating), answers 401 when no user comes back, and sends a returned `cookie` as `Set-Cookie` on the 401 and on the stream response alike, so a session the call refreshed reaches the browser. When `authenticate` throws, the handler logs the error through `logger` and answers 500 with `{ "error": "authentication failed on the server" }`, so nothing of the session infrastructure reaches the client. The stream is sent with `Cache-Control: no-store, no-transform`.
|
|
112
119
|
|
|
113
120
|
The resume cursor (`StreamCursor`: each chunk line's `nextCursor`, sent back as `cursor_contacted_at` / `cursor_person_id`) is written by the server and opaque to clients. Its `contacted_at` is the last row's value in UTC with exactly 7 fractional digits (`YYYY-MM-DDThh:mm:ss.fffffffZ`, the 100 ns of `datetime2(7)`), read from the database as text so that no digit is lost to the driver's millisecond `Date`, or `null` once the stream has reached the rows without one; its `person_id` is the last row's id, any safe integer (a signed key may be negative).
|
|
114
121
|
The chunk query checks both values by the rules below when it writes the cursor, so the next request accepts every cursor the server writes; a chunk whose last row's `person_id` is not a safe integer fails with an error naming the value.
|
|
115
122
|
The handler accepts only that form naming a real instant from year 1 on and a `cursor_person_id` holding a safe integer written in canonical decimal (`0`, or digits without a leading zero after an optional minus sign); anything else (`-0`, `+5`, `05`, `1e3`, …) gets 400 before authenticating.
|
|
116
123
|
The next chunk compares the bare column with the cursor cast to `datetime2(7)`, so an index on (`is_active`, `contacted_at`, `person_id`) can seek. `createContactMapServer().getContactsStreamChunk(cursor)` checks the cursor by the same rules and throws a `TypeError` (a value of the wrong type or shape) or a `RangeError` (any other form) before connecting.
|
|
117
124
|
`ContactMapStreamClient` sends the cursor back unchanged. When a resumed request gets 400 (the cursor was written by a server of another version), it drops the cursor and loads from the beginning again once, keeping the records it has (they are keyed by `person_id`); a 400 to a request without a cursor, or a second 400 in the same load, ends the load at once with `[contact-map] the server rejected the stream request (400); reload the page`.
|
|
118
|
-
A 401 or 403 ends the load at once in phase `auth-required` with the error `Authentication required`, without retrying (`retryNow()` starts again from the last cursor, for example after the user signs in again). Any other failed response is retried with backoff and sets the error to `[contact-map] the stream request failed (status <status>)`.
|
|
125
|
+
A 401 or 403 ends the load at once in phase `auth-required` with the error `Authentication required`, without retrying (`retryNow()` starts again from the last cursor, for example after the user signs in again). Any other failed response (the 500 of a throwing `authenticate` included) is retried with backoff (1 s, doubling up to 30 s; the annotation shows `annotationReconnecting` meanwhile) and sets the error to `[contact-map] the stream request failed (status <status>)`. The sixth failure in a row ends the load in phase `failed` (`retryNow()` starts again from the last cursor).
|
|
119
126
|
|
|
120
127
|
`db`・`table`・`ensureConnection`・`authenticate` は必須で、`db` か `table` が無い、`table.contacted_at` が無いか Drizzle の列でない、`ensureConnection` か `authenticate` が関数でない、または `firstBatchSize` / `nextBatchSize` が数値でなければ `createContactMapStreamHandler` / `createContactMapServer` は `TypeError` を、`table.contacted_at` が `datetime2` の列 (`datetime2` か `datetime2(0)` から `datetime2(7)`。`datetime`・`datetimeoffset`・`date` の列は断る) でないか、`firstBatchSize` か `nextBatchSize` が 1 から `Number.MAX_SAFE_INTEGER` までの整数でない数値なら `RangeError` を投げる。
|
|
121
|
-
どの文言も設定を名指し、与えた値で終わる (`[contact-map] server config authenticate must be the function that returns the request's user (…); got undefined`、`[contact-map] server config table.contacted_at must be a Drizzle datetime2 column; got "datetime"`)。誰にでも開くストリームは user を返す `authenticate` で明示し、設定を書き漏らしても認証なしで連絡先を配ることはない。ハンドラーは再開カーソルを検査してから (不正な形は認証せずに 400) `authenticate(request, { failureRedirect: null })` を呼び、user が無ければ 401 を返し、返された `cookie` は 401 にもストリームの応答にも `Set-Cookie` で載せる (呼び出しが更新したセッションをブラウザへ届ける)
|
|
128
|
+
どの文言も設定を名指し、与えた値で終わる (`[contact-map] server config authenticate must be the function that returns the request's user (…); got undefined`、`[contact-map] server config table.contacted_at must be a Drizzle datetime2 column; got "datetime"`)。誰にでも開くストリームは user を返す `authenticate` で明示し、設定を書き漏らしても認証なしで連絡先を配ることはない。ハンドラーは再開カーソルを検査してから (不正な形は認証せずに 400) `authenticate(request, { failureRedirect: null })` を呼び、user が無ければ 401 を返し、返された `cookie` は 401 にもストリームの応答にも `Set-Cookie` で載せる (呼び出しが更新したセッションをブラウザへ届ける)。
|
|
129
|
+
`authenticate` が例外を投げると、ハンドラーはそのエラーを `logger` へ出し、500 (`{ "error": "authentication failed on the server" }`) を返す (セッションの基盤の詳細はクライアントへ出さない)。ストリームは `Cache-Control: no-store, no-transform` で送る。
|
|
122
130
|
|
|
123
131
|
再開カーソル (`StreamCursor`。各チャンク行の `nextCursor` で、`cursor_contacted_at` / `cursor_person_id` として送り返す) はサーバーが書き、クライアントにとって形は不透明。`contacted_at` は最後の行の値を UTC で小数部ちょうど 7 桁 (`YYYY-MM-DDThh:mm:ss.fffffffZ`、`datetime2(7)` の 100 ns) で表したもので、ドライバのミリ秒までの `Date` で桁を落とさないようデータベースから文字列で読む。日時の無い行へ進んだ後は `null`。`person_id` は最後の行の ID で、負を含む任意の安全な整数 (符号付きのキーは負にもなる)。
|
|
124
132
|
チャンクの照会はカーソルを書くときに両方の値を下の規則で確かめるので、サーバーが書いたカーソルは次の要求が必ず受け入れる。最後の行の `person_id` が安全な整数でないチャンクは、その値を示すエラーで失敗する。
|
|
125
133
|
ハンドラーは 1 年以降の実在する日時を表すこの形と、安全な整数を正規の 10 進 (`0` か、省略できる負の符号に続く先頭が 0 でない数字) で書いた `cursor_person_id` だけを受け入れ、それ以外 (`-0`・`+5`・`05`・`1e3` など) は認証の前に 400 で断る。次のチャンクは素の列と `datetime2(7)` へ CAST したカーソルを比べるので、(`is_active`, `contacted_at`, `person_id`) の索引でシークできる。`createContactMapServer().getContactsStreamChunk(cursor)` も同じ規則でカーソルを検査し、接続の前に `TypeError` (型や形の誤り) か `RangeError` (それ以外の形) を投げる。
|
|
126
134
|
`ContactMapStreamClient` はカーソルをそのまま送り返す。再開した要求が 400 を受けたら (別の版のサーバーが書いたカーソル)、カーソルを捨てて最初から 1 回だけ読み直し、受信済みのレコードは残す (`person_id` で重ねる)。カーソルの無い要求への 400 と、同じ読み込みでの 2 回目の 400 は、`[contact-map] the server rejected the stream request (400); reload the page` で即座に読み込みを終える。
|
|
127
|
-
401 と 403 は再試行せず、段階 `auth-required`・エラー `Authentication required` で即座に読み込みを終える (`retryNow()` で最終カーソルから読み直す。たとえば再ログインの後)
|
|
135
|
+
401 と 403 は再試行せず、段階 `auth-required`・エラー `Authentication required` で即座に読み込みを終える (`retryNow()` で最終カーソルから読み直す。たとえば再ログインの後)。それ以外の失敗の応答 (`authenticate` が投げたときの 500 を含む) はバックオフ (1 秒から倍々に最大 30 秒。その間、注記は `annotationReconnecting` を表示) で再試行し、エラーを `[contact-map] the stream request failed (status <status>)` にする。続けて 6 回目の失敗で、段階 `failed` として読み込みを終える (`retryNow()` で最終カーソルから始め直す)。
|
|
128
136
|
|
|
129
137
|
### 2. Client Page Setup / クライアント側ページの設定
|
|
130
138
|
|
|
131
139
|
```tsx
|
|
132
140
|
// app/routes/contact_map.tsx
|
|
133
|
-
import { ContactMapConfigProvider, ContactMapSearchPage } from "@aiquants/contact-map/client"
|
|
134
|
-
|
|
141
|
+
import { ContactMapConfigProvider, ContactMapSearchPage, contactMapShouldRevalidate } from "@aiquants/contact-map/client"
|
|
142
|
+
|
|
143
|
+
export const shouldRevalidate = contactMapShouldRevalidate
|
|
135
144
|
|
|
136
145
|
export default function ContactMapRoute() {
|
|
137
146
|
return (
|
|
@@ -147,6 +156,12 @@ export default function ContactMapRoute() {
|
|
|
147
156
|
}
|
|
148
157
|
```
|
|
149
158
|
|
|
159
|
+
Export `contactMapShouldRevalidate` as the route's `shouldRevalidate`. On the same path, a change of nothing but the page's own URL state (the list / tree tab `view` and the search term `q`) does not run the route's loader, since the records stream in on the client. Every other transition keeps React Router's decision (another path, another parameter, and the unchanged URL after an action, a `revalidate()` call or a link to the current page), so a loader that checks the session still runs then; a submission is decided by its URL the same way. It is built with `@aiquants/route-revalidation`.
|
|
160
|
+
ルートの `shouldRevalidate` には `contactMapShouldRevalidate` を公開する。同じパスでページ自身の URL の状態 (一覧とツリーのタブ `view` と検索語 `q`) だけが変わる遷移では、ルートのローダーを走らせない (レコードはクライアントへストリームで届く)。ほかの遷移 (別のパス・ほかのパラメータ・アクションの後や `revalidate()`・今のページへのリンクのような URL の変わらない場合) は React Router の判断を保つので、セッションを確かめるローダーはそのときも走る。送信も URL で同じに決める。`@aiquants/route-revalidation` で組んでいる。
|
|
161
|
+
|
|
162
|
+
The page needs its styles, which depend on the host: a host that builds with Tailwind CSS v4 generates the classes itself, and a host without it imports the standalone CSS (see Styling).
|
|
163
|
+
ページにはスタイルが要り、その用意はホストによって変わる: Tailwind CSS v4 でビルドするホストは自分のビルドでクラスを生成し、Tailwind CSS を使わないホストはスタンドアロンの CSS を読み込む (スタイルの節を参照)。
|
|
164
|
+
|
|
150
165
|
### 3. Stream Endpoint / ストリームのエンドポイント
|
|
151
166
|
|
|
152
167
|
The stream endpoint is a setting like `locale` and `workerUrls`: set `streamEndpoint` on `ContactMapAppView` or on a surrounding `ContactMapConfigProvider`, and when both set it the view's prop wins. It has no default; the stream reads the one value that results.
|
|
@@ -185,40 +200,119 @@ A Worker that cannot be created or fails is recreated twice, 250 ms and then 500
|
|
|
185
200
|
ビューが重ねる設定 (`ContactMapAppView` の `streamEndpoint`・`workerUrls`・`locale`・`labels`、`ContactMapSearchPage` の `workerUrls`) も、props が自身で持つときだけ読む。
|
|
186
201
|
Worker を作れないか Worker が落ちると、250 ms 後と 500 ms 後の 2 回作り直す (待つ間に届いたレコードは新しい Worker へ送り、Worker を前倒しでは作らない)。障害ごとに `[TreeWorker]` の警告をエラーと共にログへ出し、続けて 3 回目の障害は、Worker の出どころの設定とツリーを戻すものを挙げたエラーとして出す。モジュール常駐のセッション (`workerUrls.treeWorker`) はページの再読み込み、factory のセッション (`treeWorkerFactory`) はビューの再マウントで戻り、それまでツリーの更新は止まる。Worker が構築を終えると回数は 0 に戻る。
|
|
187
202
|
|
|
203
|
+
The tree view starts its session when it mounts. A host that wants the tree finished before the tree tab opens feeds the module-resident session itself: inside the same provider (so the fallback names agree), call `useTreeWorker` with the stream's records, for example in the layout that frames the page. The tree view then finds the session holding those records and shows its snapshot at once. A `treeWorkerFactory` gives every hook its own session, so this works only without one.
|
|
204
|
+
ツリービューはマウントのときにセッションを始める。ツリーのタブを開く前にツリーを組み終えておきたいホストは、モジュール常駐のセッションへ自分でレコードを渡す: 同じプロバイダーの中で (代替名を揃えるため)、ページを囲むレイアウトなどからストリームのレコードで `useTreeWorker` を呼ぶ。ツリービューはそのレコードを持つセッションを見つけ、スナップショットをすぐに出す。`treeWorkerFactory` はフックごとにセッションを持たせるので、これが効くのは factory を指定しないときだけ。
|
|
205
|
+
|
|
206
|
+
```tsx
|
|
207
|
+
import { useContactMapStream, useTreeWorker } from "@aiquants/contact-map/client"
|
|
208
|
+
|
|
209
|
+
// rendered inside ContactMapConfigProvider, next to the page
|
|
210
|
+
function PrebuildContactMapTree() {
|
|
211
|
+
const { records, isComplete } = useContactMapStream()
|
|
212
|
+
useTreeWorker(records, isComplete)
|
|
213
|
+
return null
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Styling / スタイル
|
|
218
|
+
|
|
219
|
+
The components are styled with Tailwind CSS v4 utility classes: the semantic colors of a shadcn/ui-style host (`bg-background`, `text-muted-foreground`, `border-border`, ...), read from the host's `--background`, `--foreground`, ... variables (HSL components), and the `dark:` variant of a `dark` class on an ancestor.
|
|
220
|
+
|
|
221
|
+
- A host that builds with Tailwind CSS v4 generates the classes in its own build: it adds an `@source` for the package's files (`@source "<path to node_modules>/@aiquants/contact-map/dist";` for the published package), keeps the semantic colors in its `@theme`, and defines the `animate-progress-indeterminate` utility of the indeterminate progress bar (the definition below). It does not import the standalone CSS as well, whose copy of the utilities would compete with its own.
|
|
222
|
+
- A host without Tailwind CSS imports `@aiquants/contact-map/styles/contact-map.standalone.css`: the package's utilities, the semantic color mapping and the progress animation, compiled. It reads the host's `--background`, ... variables at run time.
|
|
223
|
+
- Either way, the host imports the CSS of the embedded packages itself, which the standalone CSS leaves out so that an older copy baked into it never overrides the host's newer one: `@aiquants/virtualscroll/styles/virtualscroll.css`, `@aiquants/directory-tree/styles/directory-tree.css` and `@aiquants/resize-panels/styles/resize-panels.css`.
|
|
224
|
+
|
|
225
|
+
コンポーネントは Tailwind CSS v4 のユーティリティクラスで装飾する: shadcn/ui 流のホストの意味の色 (`bg-background`・`text-muted-foreground`・`border-border` など。ホストの `--background`・`--foreground` などの変数 (HSL の成分) から読む) と、祖先の `dark` クラスによる `dark:` バリアント。
|
|
226
|
+
|
|
227
|
+
- Tailwind CSS v4 でビルドするホストは、自分のビルドでクラスを生成する: パッケージのファイルへの `@source` を足し (公開版なら `@source "<node_modules への道>/@aiquants/contact-map/dist";`)、意味の色を自分の `@theme` に置き、不確定の進捗バーの `animate-progress-indeterminate` ユーティリティを定義する (定義は下)。スタンドアロンの CSS は併せて読み込まない (その写しのユーティリティが自分のものと競り合う)。
|
|
228
|
+
- Tailwind CSS を使わないホストは `@aiquants/contact-map/styles/contact-map.standalone.css` を読み込む: パッケージのユーティリティ・意味の色の対応・進捗のアニメーションをコンパイルしたもの。ホストの `--background` などの変数は実行時に読む。
|
|
229
|
+
- どちらの場合も、埋め込みのパッケージの CSS はホストが自分で読み込む (スタンドアロンの CSS は含めないので、焼き込んだ古い写しがホストの新しい CSS を上書きしない): `@aiquants/virtualscroll/styles/virtualscroll.css`・`@aiquants/directory-tree/styles/directory-tree.css`・`@aiquants/resize-panels/styles/resize-panels.css`。
|
|
230
|
+
|
|
231
|
+
A host that builds with Tailwind CSS v4 (in the CSS file its build reads; the `@source` path is relative to that file):
|
|
232
|
+
Tailwind CSS v4 でビルドするホスト (ビルドが読む CSS ファイルに書く。`@source` の道はそのファイルからの相対):
|
|
233
|
+
|
|
234
|
+
```css
|
|
235
|
+
@source "../node_modules/@aiquants/contact-map/dist";
|
|
236
|
+
|
|
237
|
+
@theme {
|
|
238
|
+
--animate-progress-indeterminate: progress-indeterminate 2s ease-in-out infinite;
|
|
239
|
+
|
|
240
|
+
@keyframes progress-indeterminate {
|
|
241
|
+
0% {
|
|
242
|
+
transform: translateX(-100%);
|
|
243
|
+
}
|
|
244
|
+
50% {
|
|
245
|
+
transform: translateX(300%);
|
|
246
|
+
}
|
|
247
|
+
100% {
|
|
248
|
+
transform: translateX(-100%);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
A host without Tailwind CSS (once, for example in its root module):
|
|
255
|
+
Tailwind CSS を使わないホスト (ルートのモジュールなどで 1 度だけ):
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
import "@aiquants/contact-map/styles/contact-map.standalone.css"
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
The package reads these host CSS variables, each `0px` while the host leaves it unset. It never names a variable of another package: a host binds them in its own layout layer.
|
|
262
|
+
パッケージは次のホストの CSS 変数を読み、ホストが設定しない間はどれも `0px`。ほかのパッケージの変数は名指さないので、ホストが自分のレイアウトの層で結ぶ。
|
|
263
|
+
|
|
264
|
+
| Variable | Read by | Meaning |
|
|
265
|
+
| --- | --- | --- |
|
|
266
|
+
| `--header-height` | The list view's height (on the root element) | The list never reaches above this distance from the top of the viewport (a fixed header's height). |
|
|
267
|
+
| `--footer-height` | The list view's height (on the root element) | Taken from the bottom of the viewport (a fixed footer's height). |
|
|
268
|
+
| `--scrollbar-height` | The list view's height (on the root element) | Taken from the bottom of the viewport as well (room kept for a horizontal scrollbar). |
|
|
269
|
+
| `--aqcm-overlay-right` | The detail popup and the swipe overlay | Their right inset, such as the width of a menu pinned to the right edge: `:root { --aqcm-overlay-right: var(--my-pinned-menu-width, 0px); }`. |
|
|
270
|
+
|
|
271
|
+
| 変数 | 読むもの | 意味 |
|
|
272
|
+
| --- | --- | --- |
|
|
273
|
+
| `--header-height` | 一覧ビューの高さ (ルート要素で読む) | 一覧はビューポートの上端からこの距離より上へ出ない (固定したヘッダーの高さ)。 |
|
|
274
|
+
| `--footer-height` | 一覧ビューの高さ (ルート要素で読む) | ビューポートの下端から差し引く (固定したフッターの高さ)。 |
|
|
275
|
+
| `--scrollbar-height` | 一覧ビューの高さ (ルート要素で読む) | 同じくビューポートの下端から差し引く (横のスクロールバーのために空ける高さ)。 |
|
|
276
|
+
| `--aqcm-overlay-right` | 詳細のポップアップとスワイプのオーバーレイ | 右端の余白 (右端にピン留めしたメニューの幅など): `:root { --aqcm-overlay-right: var(--my-pinned-menu-width, 0px); }`。 |
|
|
277
|
+
|
|
188
278
|
## Search Normalization / 検索語の正規化
|
|
189
279
|
|
|
190
280
|
The search box reads its text as keywords. Each whitespace-separated word is a keyword of its own, and the words between two quotation marks (`"`, `"`, `“`, `”`; any of them opens or closes a group, and a group left open runs to the end) are one keyword.
|
|
191
281
|
The page searches every keyword with `@aiquants/fuzzy-search`: `useFuzzySearch`'s `querySetsOf` reads the term with `parseQuerySets`, and `FuzzySearchManager.searchSets` searches one query set per keyword. The AND / OR button next to the search box combines the keywords' answers: AND keeps the contacts every keyword finds (intersection), OR the contacts any keyword finds (union). The button starts at OR, and switching it searches the current term again without rebuilding the index.
|
|
192
282
|
A quoted keyword is matched by fuzzy-search's multi-word matching, whose operator is the settings tab's "Operator for words inside quotes" select (`multiTermOperator`, AND at first): AND requires every word of the group to appear in some search field, OR does not. A keyword of one word matches the same under either operator.
|
|
193
283
|
A "Maximum results" cap of the index or Levenshtein settings (no limit at first) applies to each keyword's answer before the keywords are combined, so with a cap an AND search can miss a contact that one keyword's capped answer left out.
|
|
284
|
+
The settings tab's "Input delay (ms)" (`debounceMs`, 300 at first) is the one wait between typing and searching: the page searches that long after the text last changed and hands the engine `debounceMs: 0`. "Reset all" returns every search setting to the page's initial ones (fuzzy-search's defaults, with AND for the words inside quotes) and keeps the Worker URLs.
|
|
194
285
|
|
|
195
286
|
The search page normalizes the typed text with `normalizeSearchInput` (exported from `@aiquants/contact-map`) into the fuzzy search term. The contact fields (name, company, department, title, email, phone numbers) are matched as stored. Every keyword is therefore only brought into a form those fields can hold:
|
|
196
287
|
|
|
197
288
|
- Invisible characters (U+200B, U+FEFF) are removed before the keywords are read, and each keyword's surrounding whitespace (U+3000 and U+00A0 included) is trimmed.
|
|
198
289
|
- Width, kana (hiragana → katakana) and case are unified with select-box's `normalizeText` (`ABC商事` → `abc商事`, `さとう` → `サトウ`). Kana stays as written (`つづき` → `ツヅキ`).
|
|
290
|
+
Its options are `CONTACT_MAP_SEARCH_NORMALIZE_OPTIONS` (exported from `@aiquants/contact-map`), which sets all eight of select-box's `NormalizeOptions`, so a change of select-box's defaults never changes the search term: `convertWidth`, `convertKana` and `convertCase` on, `convertRomaji` off (the next rule reads romaji), and `normalizeVariants`, `ignoreSpaces`, `ignoreSymbols` and `ignoreLongVowels` off, because the fields hold spelling variants, spaces, symbols and prolonged sound marks as written (`佐々木`, `自由ヶ丘`, `ーヒー` and `don’t` stay).
|
|
199
291
|
- The keywords are replaced by their katakana readings only when every keyword is romaji that reads completely. Each must hold only Latin letters and whitespace, and select-box's `romajiToKatakana` must leave no letter untransliterated (`tanaka` → `タナカ`, `yamada taro` → `ヤマダ タロ`, `"yamada taro" suzuki` → `"ヤマダ タロ" スズキ`, `tsuduki` → `ツヅキ`).
|
|
200
292
|
- Mixed text, digits and symbols, and words that do not read completely keep their width / kana / case form, and so does every other keyword of the text (`user@example.com`, `営業部 tanaka`, `tanaka1`, `ke-su`, `sony`, `"yamada taro" sony`).
|
|
201
293
|
- The keywords are written back with fuzzy-search's `formatQuerySets`: single spaces between them, a keyword holding whitespace in ASCII quotation marks (`"ABC 商事"` → `"abc 商事"`), and neither a blank keyword nor a keyword that normalization made equal to an earlier one. `parseQuerySets` therefore reads exactly these keywords from the term. Text without quotation marks becomes its whitespace-separated words, each normalized as above and written once, separated by single spaces.
|
|
202
294
|
|
|
203
|
-
The result highlight and the exact-match ranking are one derivation from the text as typed. `createSearchQueryPattern(query)` finds the spellings that `getSearchQuerySpellings(query)` lists (select-box's `getNormalizedVariants` of each word of the text's keywords and of its normalized form's keywords, read with fuzzy-search's `parseQuerySets`, so a quotation mark is never part of a spelling), longest first and case-insensitively.
|
|
204
|
-
`HighlightedText` marks its matches, and `createExactMatchRanker(query, fields)` moves the results with a match in one of the fields to the top, keeping the fuzzy order within each group. A field is therefore highlighted exactly when it ranks its record as an exact match. A kana reading is marked only when every keyword reads as romaji.
|
|
295
|
+
The result highlight and the exact-match ranking are one derivation from the text as typed. `createSearchQueryPattern(query)` finds the spellings that `getSearchQuerySpellings(query)` lists (select-box's `getNormalizedVariants` of each word of the text's keywords and of its normalized form's keywords, read with fuzzy-search's `parseQuerySets`, so a quotation mark is never part of a spelling, each followed by its half-width katakana spelling when every kana of it has one: `タナカ` after `タナカ`, `サトウ` after `サトウ`, the form many stored fields hold), longest first and case-insensitively.
|
|
296
|
+
`HighlightedText` marks its matches in `mark` elements, dark text on yellow and, under the `dark` class, light text on dark amber (both above the 7:1 contrast of WCAG 2.2 AAA), and `createExactMatchRanker(query, fields)` moves the results with a match in one of the fields to the top, keeping the fuzzy order within each group. A field is therefore highlighted exactly when it ranks its record as an exact match. A kana reading is marked only when every keyword reads as romaji.
|
|
205
297
|
|
|
206
|
-
The typed query is used because normalization can drop a spelling it holds.
|
|
298
|
+
The typed query is used because normalization can drop a spelling it holds. The width unification (Unicode NFKC) reads `㈱` as `(株)`, so `㈱山田` becomes the search term `(株)山田`, while the highlight and the ranking still find `㈱山田` in a company stored with `㈱`. The search outlet context carries both: `searchQuery` (as typed) and `searchTerm` (`normalizeSearchInput(searchQuery)`).
|
|
207
299
|
|
|
208
300
|
検索欄はテキストをキーワードとして読む。空白で区切った語はそれぞれ 1 つのキーワードで、2 つの引用符 (`"`・`"`・`“`・`”`。どれでも組を開き閉じ、開いたままの組は終わりまで続く) の間の語はまとめて 1 つのキーワード。ページはどのキーワードも `@aiquants/fuzzy-search` で検索する: `useFuzzySearch` の `querySetsOf` が `parseQuerySets` で検索語を読み、`FuzzySearchManager.searchSets` がキーワードごとに 1 つの問い合わせのセットを検索する。検索欄の隣の AND / OR ボタンはキーワードの答えをまとめ、AND はどのキーワードも見つけた連絡先 (積集合)、OR はどれかのキーワードが見つけた連絡先 (和集合) を残す。ボタンは OR で始まり、切り替えると索引を建て直さずに今の検索語を検索し直す。
|
|
209
301
|
引用符で囲んだキーワードは fuzzy-search の複数語の照合で照合し、その演算子は設定タブの「引用符の中の語の演算子」セレクト (`multiTermOperator`。最初は AND): AND は組のどの語もいずれかの検索フィールドに現れることを求め、OR は求めない。1 語のキーワードはどちらの演算子でも同じに照合する。
|
|
302
|
+
索引か Levenshtein の設定の「最大結果数」(最初は上限なし) はキーワードの答えをまとめる前に各キーワードの答えへ効くので、上限があると AND の検索は、あるキーワードの上限で切られた答えから漏れた連絡先を見落とすことがある。
|
|
303
|
+
設定タブの「入力遅延 (ms)」(`debounceMs`。最初は 300) が入力から検索までの唯一の待ちで、ページはテキストが最後に変わってからその時間の後に検索し、エンジンへは `debounceMs: 0` を渡す。「すべてリセット」はどの検索設定もページの初期の設定 (fuzzy-search の既定に、引用符の中の語の AND を重ねたもの) へ戻し、Worker の URL は保つ。
|
|
210
304
|
|
|
211
305
|
検索ページは入力されたテキストを `normalizeSearchInput` (`@aiquants/contact-map` から公開) で正規化し、あいまい検索の検索語にする。連絡先のフィールド (氏名・会社・部署・役職・メール・電話番号) は保存されたままの値で照合する。そのため各キーワードは、それらのフィールドが持ちうる形に揃えるだけ:
|
|
212
306
|
|
|
213
307
|
- キーワードを読む前に不可視文字 (U+200B・U+FEFF) を除き、各キーワードの端の空白 (U+3000・U+00A0 を含む) を除く。
|
|
214
|
-
- select-box の `normalizeText` で全角/半角・かな (ひらがな → カタカナ)・大文字小文字を統一する (`ABC商事` → `abc商事`、`さとう` → `サトウ`)。かなは書いたまま (`つづき` → `ツヅキ`)。
|
|
308
|
+
- select-box の `normalizeText` で全角/半角・かな (ひらがな → カタカナ)・大文字小文字を統一する (`ABC商事` → `abc商事`、`さとう` → `サトウ`)。かなは書いたまま (`つづき` → `ツヅキ`)。設定は `CONTACT_MAP_SEARCH_NORMALIZE_OPTIONS` (`@aiquants/contact-map` から公開) で、select-box の `NormalizeOptions` の 8 つをすべて指定するので、select-box の既定が変わっても検索語は変わらない: `convertWidth`・`convertKana`・`convertCase` は有効、`convertRomaji` は無効 (ローマ字は次の規則で読む)、`normalizeVariants`・`ignoreSpaces`・`ignoreSymbols`・`ignoreLongVowels` は無効 (フィールドは異体字・空白・記号・長音記号を書いたまま持つため。`佐々木`・`自由ヶ丘`・`ーヒー`・`don’t` はそのまま)。
|
|
215
309
|
- どのキーワードも完全に読めるローマ字のときだけ、各キーワードをカタカナの読みに置き換える。どれもラテン文字と空白だけで、select-box の `romajiToKatakana` が音訳しない文字を残さないことが条件 (`tanaka` → `タナカ`、`yamada taro` → `ヤマダ タロ`、`"yamada taro" suzuki` → `"ヤマダ タロ" スズキ`、`tsuduki` → `ツヅキ`)。
|
|
216
310
|
- 混在テキスト・数字や記号・完全には読めない語は全角/半角・かな・大文字小文字の形のままで、そのテキストのほかのキーワードも同じ (`user@example.com`、`営業部 tanaka`、`tanaka1`、`ke-su`、`sony`、`"yamada taro" sony`)。
|
|
217
311
|
- キーワードは fuzzy-search の `formatQuerySets` で書き戻す: キーワードの間は 1 つの空白、空白を持つキーワードは ASCII の引用符で囲み (`"ABC 商事"` → `"abc 商事"`)、空のキーワードと、正規化で先のキーワードと等しくなったキーワードは書かない。そのため `parseQuerySets` は検索語からちょうどこれらのキーワードを読む。引用符の無いテキストは、空白で区切った各語を上のとおり正規化して 1 度ずつ並べ、1 つの空白で区切ったものになる。
|
|
218
312
|
|
|
219
|
-
検索結果のハイライトと完全一致による並べ替えは、入力されたままのテキストからの 1 つの導出。`createSearchQueryPattern(query)` は `getSearchQuerySpellings(query)` が並べる表記 (テキストのキーワードとその正規化形のキーワードの各語に対する select-box の `getNormalizedVariants`。語は fuzzy-search の `parseQuerySets`
|
|
313
|
+
検索結果のハイライトと完全一致による並べ替えは、入力されたままのテキストからの 1 つの導出。`createSearchQueryPattern(query)` は `getSearchQuerySpellings(query)` が並べる表記 (テキストのキーワードとその正規化形のキーワードの各語に対する select-box の `getNormalizedVariants`。語は fuzzy-search の `parseQuerySets` で読むため、引用符は表記の一部にならない。どの仮名も半角の形を持つ表記には、続けて半角カナの表記を置く: `タナカ` の次に `タナカ`、`サトウ` の次に `サトウ`。保存されたフィールドに多い形) を、最長のものから大文字小文字を区別せずに探す。`HighlightedText` はその一致に `mark` 要素で印を付け (明るいテーマは黄色の上の暗い文字、`dark` クラスの下は暗い琥珀色の上の明るい文字。どちらも WCAG 2.2 AAA の 7:1 を超える)、`createExactMatchRanker(query, fields)` はいずれかのフィールドに一致を持つ結果を先頭へ移す (各グループ内ではあいまい検索の順序を保つ)。そのため、フィールドにハイライトが付くのは、そのフィールドがレコードを完全一致として並べさせるときちょうど。かなの読みに印が付くのはどのキーワードもローマ字として読めるときだけ。
|
|
220
314
|
|
|
221
|
-
|
|
315
|
+
入力されたままの検索語を使うのは、正規化が検索語の持つ表記を落とすことがあるため。全角/半角の統一 (Unicode の NFKC) は `㈱` を `(株)` と読むので、`㈱山田` は検索語 `(株)山田` になるが、ハイライトと並べ替えは `㈱` で保存された会社の `㈱山田` を探し続ける。検索のアウトレットコンテキストは両方を運ぶ: `searchQuery` (入力されたまま) と `searchTerm` (`normalizeSearchInput(searchQuery)`)。
|
|
222
316
|
|
|
223
317
|
## Localization / UI 言語
|
|
224
318
|
|
|
@@ -236,6 +330,8 @@ Every string the client components render on their own (tabs, buttons, headings,
|
|
|
236
330
|
| `ContactMapConfigProvider` | `config.streamEndpoint` | `string` | Same-origin absolute path of the NDJSON stream. No default; it or the view's `streamEndpoint` must be set (see Stream Endpoint). |
|
|
237
331
|
| `ContactMapConfigProvider` | `config.locale` | `ContactMapLocale` (`"en" \| "ja"`) | Omitted ⇒ `"en"`. At mount a non-string throws a `TypeError` and any other string a `RangeError`, both naming `locale`. |
|
|
238
332
|
| `ContactMapConfigProvider` | `config.labels` | `ContactMapLabelOverrides` | Partial overrides over `CONTACT_MAP_LABEL_CATALOGS[locale]`, validated at mount; errors name `labels`. |
|
|
333
|
+
| `ContactMapConfigProvider` | `config.openEmailsWithGmail` | `boolean` | Initial value of the settings tab's Gmail toggle; omitted ⇒ `false`. While it is on, an email address links to Gmail's web compose window instead of `mailto:`, and double-clicking (or double-tapping) an address in the popup opens it. |
|
|
334
|
+
| `ContactMapConfigProvider` | `config.treeWorkerFactory` | `() => Worker` | Builds the tree Worker for a bundler that needs its own construction; every call must return a new Worker, and each hook then has its own session (see the tree paragraphs above). |
|
|
239
335
|
| `ContactMapConfigProvider` | `config.workerUrls` | `ContactMapWorkerUrls` | A plain object of only `indexWorker`, `levenshteinWorker` and `treeWorker`, each left out or a string with non-whitespace; checked in every render, a server render included (`assertContactMapWorkerUrls`: `TypeError` / `RangeError`); the views read only the checked copy. One `treeWorker` per browser tab (see the tree paragraph above). |
|
|
240
336
|
| `ContactMapAppView` | `streamEndpoint` | `string` | When given it wins over the surrounding provider's `streamEndpoint`. |
|
|
241
337
|
| `ContactMapAppView` | `locale` | `ContactMapLocale` | When given it wins over the surrounding provider's `locale`; its errors name `ContactMapAppView locale`. |
|
|
@@ -270,8 +366,8 @@ export default function ContactMapRoute() {
|
|
|
270
366
|
It names the setting as the surface calls it (`labels`, `ContactMapAppView labels`, `server config firstBatchSize`), and it ends with `; got <value>`: the rejected value, or the rejected key, written the same way everywhere (a quoted string, `undefined`, `null`, `-0`, `10n`, `Symbol(x)`, `[object URL]`).
|
|
271
367
|
- Two views of one browser tab that disagree on `streamEndpoint` or `workerUrls.treeWorker`, and a CommonJS build without `workerUrls.treeWorker`, throw a plain `Error` (no single value is wrong).
|
|
272
368
|
- A unit key (`recordUnit`, `companyUnit`, `resultUnit`, `timesUnit`, `itemsUnit`, `charactersUnit`) receives the number exactly as it is displayed (`"1"`, `"1,001"`, `"1.0"`), so the English singular / plural follows what the reader sees: only a displayed `"1"` is singular (`"1.0 items"`). `timesUnit` / `itemsUnit` / `charactersUnit` / `throughputUnit` are appended to the number without a separator and carry their own leading space.
|
|
273
|
-
- The
|
|
274
|
-
- The result is frozen. The list VirtualScroll receives exactly the
|
|
369
|
+
- The embedded keys (`VIRTUAL_SCROLL_LABEL_KEYS` of `@aiquants/virtualscroll` and the row kind words of `@aiquants/directory-tree`, together `DIRECTORY_TREE_LABEL_KEYS`) are checked here under the same rule before they are handed to those packages, so their errors follow the package's rule too.
|
|
370
|
+
- The result is frozen. The list VirtualScroll receives exactly the keys of `VIRTUAL_SCROLL_LABEL_KEYS` and the DirectoryTree exactly those of `DIRECTORY_TREE_LABEL_KEYS`, both kept value-stable, so an inline `labels` object recreated on every render does not rebuild the embedded scroll chrome.
|
|
275
371
|
- `overrides` 未指定なら凍結カタログそのもの (同一 identity) を返す。
|
|
276
372
|
- `overrides` は素の object (プロトタイプが `Object.prototype` か `null`) に限り、それ以外 (`null`・プリミティブ・配列・クラスインスタンス・他 object 継承) は `TypeError`。読むのは自身のプロパティのみで、各プロパティをちょうど 1 回読み、ラベル集合は検査した値そのものから作る。
|
|
277
373
|
- `CONTACT_MAP_LABEL_KEYS` 外の自身のキーは、最も近いキーを示す短い `RangeError` (`[contact-map] labels has a key that is not a label key (did you mean "searchTab"? CONTACT_MAP_LABEL_KEYS lists every key); got "serchTab"`)。
|
|
@@ -280,13 +376,19 @@ export default function ContactMapRoute() {
|
|
|
280
376
|
- 設定の検査 (`locale`・`labels`・`streamEndpoint`・`workerUrls`・サーバーの設定・行数・`getContactsStreamChunkQuery` か `createContactMapServer().getContactsStreamChunk` に渡す再開カーソル (文言ではどちらも `getContactsStreamChunkQuery cursor`)・代替名) は、型や形の誤り (必須の設定が無い場合を含む) を `TypeError`、型は合うが許されない値 (設定が受け取らないキーを含む) を `RangeError` にする。設定は面の呼び名で名指し (`labels`・`ContactMapAppView labels`・`server config firstBatchSize`)、末尾は `; got <値>` で、拒否した値か拒否したキーをどこでも同じ表記で書く (引用符付きの文字列・`undefined`・`null`・`-0`・`10n`・`Symbol(x)`・`[object URL]`)。
|
|
281
377
|
- ブラウザのタブの 2 つのビューが `streamEndpoint` か `workerUrls.treeWorker` で食い違う場合と、`workerUrls.treeWorker` の無い CommonJS のビルドは、どの値 1 つが誤りでもないので素の `Error`。
|
|
282
378
|
- 単位キー (`recordUnit`、`companyUnit`、`resultUnit`、`timesUnit`、`itemsUnit`、`charactersUnit`) は表示どおりの数値文字列 (`"1"`、`"1,001"`、`"1.0"`) を受け取るため、英語の単数 / 複数は読み手が見る値に従う (単数は表示 `"1"` のときだけで `"1.0 items"`)。`timesUnit` / `itemsUnit` / `charactersUnit` / `throughputUnit` は数値の直後に区切りなしで連結されるため、先頭の空白は単位側が持つ。
|
|
283
|
-
-
|
|
284
|
-
- 結果は凍結される。一覧の VirtualScroll
|
|
379
|
+
- 埋込みキー (`@aiquants/virtualscroll` の `VIRTUAL_SCROLL_LABEL_KEYS` と `@aiquants/directory-tree` の行種別語。合わせて `DIRECTORY_TREE_LABEL_KEYS`) も、それらのパッケージへ渡す前にここで同じ規則で検査するので、その例外もパッケージの規則に従う。
|
|
380
|
+
- 結果は凍結される。一覧の VirtualScroll には `VIRTUAL_SCROLL_LABEL_KEYS` のキーだけ、DirectoryTree には `DIRECTORY_TREE_LABEL_KEYS` のキーだけを値安定のまま渡すため、毎レンダー作り直すインライン `labels` でも埋込みスクロールのクロームは再構築されない。
|
|
285
381
|
|
|
286
382
|
### Chrome locale and number formatting / クロームロケールと数値書式
|
|
287
383
|
|
|
288
|
-
`locale` is the UI chrome language (`"en"` | `"ja"`), not a BCP 47 tag. contact-map has no separate formatting-locale prop (`formatLocale`): the chrome locale also fixes digit grouping through a spec-defined mapping, `en` → `"en-US"` and `ja` → `"ja-JP"`. Every count the package renders (annotation, registered / result counts, tree node counts, member counts in the tree, statistics) is grouped with that tag, never with the runtime default locale, so the server render and the browser render agree.
|
|
289
|
-
`locale` は UI クロームの言語 (`"en"` | `"ja"`) で BCP 47 タグではない。contact-map には書式ロケール専用の prop (`formatLocale`) は無く、クロームロケールが仕様で定めた対応 (`en` → `"en-US"`、`ja` → `"ja-JP"`) で桁区切りも決める。パッケージが描画する件数 (注記、登録件数 / 検索結果件数、ツリーのノード数・所属人数、統計)
|
|
384
|
+
`locale` is the UI chrome language (`"en"` | `"ja"`), not a BCP 47 tag. contact-map has no separate formatting-locale prop (`formatLocale`): the chrome locale also fixes digit grouping through a spec-defined mapping, `en` → `"en-US"` and `ja` → `"ja-JP"`. Every count the package renders (annotation, registered / result counts, tree node counts, member counts in the tree, statistics) is grouped with that tag, never with the runtime default locale, so the server render and the browser render agree.
|
|
385
|
+
`locale` は UI クロームの言語 (`"en"` | `"ja"`) で BCP 47 タグではない。contact-map には書式ロケール専用の prop (`formatLocale`) は無く、クロームロケールが仕様で定めた対応 (`en` → `"en-US"`、`ja` → `"ja-JP"`) で桁区切りも決める。パッケージが描画する件数 (注記、登録件数 / 検索結果件数、ツリーのノード数・所属人数、統計) はすべてこのタグで桁区切りし、実行環境の既定ロケールは使わないため、サーバー描画とブラウザ描画が一致する。
|
|
386
|
+
|
|
387
|
+
### Dates / 日時
|
|
388
|
+
|
|
389
|
+
A record's timestamps (`contacted_at`, `created_at`, `updated_at`) are written as the database stores them. The driver reads a zone-less `datetime2` value as UTC (the mssql driver's default, `useUTC: true`), so the stream carries a stored `2026-10-03 09:00:00` as `2026-10-03T09:00:00.000Z`, and the default `recordDateTime` / `recordDate` labels (`formatStoredDateTime` / `formatStoredDate`, exported from `@aiquants/contact-map`) write its UTC fields without a zone (`2026-10-03 09:00:00`, `2026-10-03`), the same in every locale and in every browser time zone.
|
|
390
|
+
The statistics panel's "Statistics updated at" is a moment of the browser session, so its default `statsTimestamp` label (`formatLocalDateTime`) writes the browser's local time. A host overrides any of the three through `labels` (each receives a `Date`); a timestamp that is not a date is shown as it came, and a missing one as `-`.
|
|
391
|
+
レコードの日時 (`contacted_at`・`created_at`・`updated_at`) はデータベースが保存しているとおりに書く。ドライバはゾーンの無い `datetime2` の値を UTC として読む (mssql のドライバの既定 `useUTC: true`) ので、ストリームは保存された `2026-10-03 09:00:00` を `2026-10-03T09:00:00.000Z` として運び、既定の `recordDateTime` / `recordDate` ラベル (`formatStoredDateTime` / `formatStoredDate`。`@aiquants/contact-map` から公開) は UTC の各欄をゾーンを付けずに書く (`2026-10-03 09:00:00`・`2026-10-03`)。どのロケールでも、ブラウザのどの時間帯でも同じ。統計パネルの「統計更新時刻」はブラウザのセッションの中の時刻なので、既定の `statsTimestamp` ラベル (`formatLocalDateTime`) はブラウザの現地時刻で書く。ホストは 3 つのどれも `labels` で上書きできる (どれも `Date` を受け取る)。日時として読めない値は届いたまま、無い値は `-` と表示する。
|
|
290
392
|
|
|
291
393
|
### Tree fallback names / ツリーの代替名
|
|
292
394
|
|
|
@@ -309,8 +411,8 @@ Not part of the catalog / カタログ対象外:
|
|
|
309
411
|
|
|
310
412
|
### Catalog / カタログ
|
|
311
413
|
|
|
312
|
-
Function keys are shown with their arguments; the cells show sample output (`count = 2`, `displayed = "2"`, `loaded = 1200`, `name = "<name>"`, `memberCount = 1200`, `fieldLabel = "<label>"`).
|
|
313
|
-
関数キーは引数付きで示し、セルには出力例 (`count = 2`、`displayed = "2"`、`loaded = 1200`、`name = "<name>"`、`memberCount = 1200`、`fieldLabel = "<label>"`) を載せる。
|
|
414
|
+
Function keys are shown with their arguments; the cells show sample output (`count = 2`, `displayed = "2"`, `loaded = 1200`, `name = "<name>"`, `memberCount = 1200`, `fieldLabel = "<label>"`, `value = new Date("2026-10-03T09:00:00Z")` in a browser at UTC+9).
|
|
415
|
+
関数キーは引数付きで示し、セルには出力例 (`count = 2`、`displayed = "2"`、`loaded = 1200`、`name = "<name>"`、`memberCount = 1200`、`fieldLabel = "<label>"`、UTC+9 のブラウザで `value = new Date("2026-10-03T09:00:00Z")`) を載せる。
|
|
314
416
|
|
|
315
417
|
| Key | en | ja |
|
|
316
418
|
| --- | --- | --- |
|
|
@@ -318,6 +420,10 @@ Function keys are shown with their arguments; the cells show sample output (`cou
|
|
|
318
420
|
| `scrollDown` | `"Scroll down"` | `"下へスクロール"` |
|
|
319
421
|
| `scrollLeft` | `"Scroll left"` | `"左へスクロール"` |
|
|
320
422
|
| `scrollRight` | `"Scroll right"` | `"右へスクロール"` |
|
|
423
|
+
| `verticalScrollBar` | `"Vertical"` | `"縦方向"` |
|
|
424
|
+
| `horizontalScrollBar` | `"Horizontal"` | `"横方向"` |
|
|
425
|
+
| `verticalScrollThumb` | `"Vertical scroll position"` | `"縦スクロール位置"` |
|
|
426
|
+
| `horizontalScrollThumb` | `"Horizontal scroll position"` | `"横スクロール位置"` |
|
|
321
427
|
| `scrollToTop` | `"Top"` | `"先頭へ"` |
|
|
322
428
|
| `scrollToBottom` | `"Bottom"` | `"末尾へ"` |
|
|
323
429
|
| `noItems` | `"No items"` | `"項目がありません"` |
|
|
@@ -329,6 +435,8 @@ Function keys are shown with their arguments; the cells show sample output (`cou
|
|
|
329
435
|
| `annotationStreamingProgress({ loaded, total, percent })` | `"Streaming (1,200 / 4,800 · 25%)"` | `"ストリーミング読み込み中 (1,200 / 4,800 件・25%)"` |
|
|
330
436
|
| `annotationStreamingCount(loaded)` | `"Streaming (1,200 loaded)"` | `"ストリーミング読み込み中 (1,200 件)"` |
|
|
331
437
|
| `annotationLoadFailed(loaded)` | `"Failed to load (1,200 received)"` | `"読み込みに失敗しました (1,200 件受信済)"` |
|
|
438
|
+
| `annotationReconnecting(loaded)` | `"Reconnecting (1,200 received)"` | `"再接続中 (1,200 件受信済)"` |
|
|
439
|
+
| `annotationAuthRequired` | `"Session expired: sign in again, then retry"` | `"セッションが切れました。サインインし直してから再試行してください"` |
|
|
332
440
|
| `annotationRetry` | `"Retry"` | `"再試行"` |
|
|
333
441
|
| `recordUnit(displayed)` | `"records"` | `"件"` |
|
|
334
442
|
| `companyUnit(displayed)` | `"companies"` | `"社"` |
|
|
@@ -426,6 +534,8 @@ Function keys are shown with their arguments; the cells show sample output (`cou
|
|
|
426
534
|
| `fieldCreatedAt` | `"Created at"` | `"作成日時"` |
|
|
427
535
|
| `fieldUpdatedBy` | `"Updated by"` | `"更新者"` |
|
|
428
536
|
| `fieldUpdatedAt` | `"Updated at"` | `"更新日時"` |
|
|
537
|
+
| `recordDateTime(value)` | `"2026-10-03 09:00:00"` | `"2026-10-03 09:00:00"` |
|
|
538
|
+
| `recordDate(value)` | `"2026-10-03"` | `"2026-10-03"` |
|
|
429
539
|
| `popupCustomMobilePhone` | `"Mobile (custom)"` | `"携帯電話 (カスタム)"` |
|
|
430
540
|
| `popupMobilePhone` | `"Mobile"` | `"携帯電話"` |
|
|
431
541
|
| `popupCustomCompanyPhone` | `"Company phone (custom)"` | `"会社電話 (カスタム)"` |
|
|
@@ -552,6 +662,7 @@ Function keys are shown with their arguments; the cells show sample output (`cou
|
|
|
552
662
|
| `statLastSearchTime` | `"Last search time"` | `"最後の検索時間"` |
|
|
553
663
|
| `statErrorCount` | `"Errors"` | `"エラー数"` |
|
|
554
664
|
| `statUpdatedAt` | `"Statistics updated at"` | `"統計更新時刻"` |
|
|
665
|
+
| `statsTimestamp(value)` | `"2026-10-03 18:00:00"` | `"2026-10-03 18:00:00"` |
|
|
555
666
|
| `statsOperationsHeading` | `"Operations"` | `"操作統計"` |
|
|
556
667
|
| `statTotalOperations` | `"Total operations"` | `"総操作数"` |
|
|
557
668
|
| `statsIndexHeading` | `"Index statistics"` | `"インデックス統計"` |
|
|
@@ -606,56 +717,6 @@ Function keys are shown with their arguments; the cells show sample output (`cou
|
|
|
606
717
|
| `charactersUnit(displayed)` | `" characters"` | `" 文字"` |
|
|
607
718
|
| `throughputUnit` | `" ops/s"` | `" ops/秒"` |
|
|
608
719
|
|
|
609
|
-
### Migrating from 0.1.x to 0.2.0 / 0.1.x から 0.2.0 への移行
|
|
610
|
-
|
|
611
|
-
Breaking changes / 破壊的変更:
|
|
612
|
-
|
|
613
|
-
- **Default chrome language is `en`.** Hosts that relied on the former Japanese chrome must opt in with `locale="ja"` on `ContactMapAppView` or `config.locale = "ja"` in `ContactMapConfigProvider`. `locale` is the chrome language only; there is no `formatLocale` prop (digit grouping follows `locale`, see above).
|
|
614
|
-
- **Invalid chrome settings fail fast.** When the provider or the view mounts, an unsupported `locale` string (`"ja-JP"`, `"EN"`, `""`, ...), an unknown label key or a blank label throws a `RangeError`, and a value of the wrong type (a non-string `locale`, a `labels` that is not a plain object, a non-string for a string key, a non-function for a function key) a `TypeError`.
|
|
615
|
-
- **`WorkerStatsSection`** takes `kind: "index" | "levenshtein"` instead of `title: string`.
|
|
616
|
-
- **`SwipeOverlay`'s `title`** no longer defaults to `"詳細"` and **`ContactMapAppView`'s `title`** has no hard-coded default; omitted, they use the `overlayTitle` / `appTitle` labels.
|
|
617
|
-
- **Tree builder API.** `new IncrementalTreeBuilder(naming)` and `buildDirectoryTree(records, naming)` require `naming: ContactMapTreeNaming`. A directory entry's `name` is now the bare company / department name and its member count is `memberCount` (the former `"<name> (N)"` is composed by the client with `treeDirectoryName`).
|
|
618
|
-
- **Tree worker API.** `TreeWorkerSession.syncRecords(records, naming)` and `syncTreeWorkerRecords(records, naming)` take the names; the `RESET_TREE` message carries `naming` and must precede a generation's `APPEND_RECORDS` / `FINALIZE_TREE` (the worker throws otherwise). `useTreeWorker(records, isComplete)` keeps its signature and reads the names from the provider.
|
|
619
|
-
- **Fallback names are chrome.** In `en` a contact without a company is under `"Uncategorized"` and a nameless person is `"(No Name)"` everywhere (formerly the fixed Japanese `未分類` / `名前未設定` in the tree). With `locale="ja"` they stay `未分類` / `名前未設定`; `noName` in the detail panel, popup and list is `名前未設定` too.
|
|
620
|
-
- **Counts are grouped with `"en-US"` / `"ja-JP"`** by chrome locale instead of the runtime default; the registered count, the result count and the total search count are now grouped as well (`1,234` instead of `1234`).
|
|
621
|
-
- **Peer floors.** `@aiquants/virtualscroll` ^3.7.0, `@aiquants/directory-tree` ^3.12.0 and `@aiquants/select-box` ^0.10.0 (all expanded from `workspace:^` at publish). contact-map uses only the text normalization helpers of select-box and renders no `SelectBox`, so the select-box floor comes from the `workspace:^` expansion, not from a new API.
|
|
622
|
-
|
|
623
|
-
- **既定のクローム言語は `en`。** 従来の日本語クロームに依存していたホストは `ContactMapAppView` に `locale="ja"`、または `ContactMapConfigProvider` の `config.locale = "ja"` で明示的に選ぶ。`locale` はクローム言語だけで、`formatLocale` prop は無い (桁区切りは `locale` に従う。上記参照)。
|
|
624
|
-
- **不正なクローム設定は即座に失敗。** プロバイダーかビューのマウント時に、非対応の `locale` の文字列 (`"ja-JP"`、`"EN"`、`""` など)・未知のラベルキー・空白のラベルは `RangeError`、型の誤り (文字列でない `locale`、素の object でない `labels`、文字列キーに文字列以外、関数キーに関数以外) は `TypeError`。
|
|
625
|
-
- **`WorkerStatsSection`** は `title: string` の代わりに `kind: "index" | "levenshtein"` を受け取る。
|
|
626
|
-
- **`SwipeOverlay` の `title`** は `"詳細"` を既定にせず、**`ContactMapAppView` の `title`** も固定の既定値を持たない。省略時は `overlayTitle` / `appTitle` ラベル。
|
|
627
|
-
- **ツリービルダー API。** `new IncrementalTreeBuilder(naming)` と `buildDirectoryTree(records, naming)` は `naming: ContactMapTreeNaming` が必須。ディレクトリエントリの `name` は会社名 / 部署名そのもので、所属人数は `memberCount` (従来の `"<名前> (N)"` はクライアントが `treeDirectoryName` で組み立てる)。
|
|
628
|
-
- **ツリー Worker API。** `TreeWorkerSession.syncRecords(records, naming)` と `syncTreeWorkerRecords(records, naming)` は代替名を受け取る。`RESET_TREE` メッセージは `naming` を運び、世代の `APPEND_RECORDS` / `FINALIZE_TREE` より先に送ることが必須 (違反すると Worker が例外)。`useTreeWorker(records, isComplete)` のシグネチャは変わらず、代替名はプロバイダーから読む。
|
|
629
|
-
- **代替名はクローム。** `en` では会社名の無い連絡先は `"Uncategorized"` の下、氏名の無い人物はどこでも `"(No Name)"` (従来ツリーでは固定の日本語 `未分類` / `名前未設定`)。`locale="ja"` では `未分類` / `名前未設定` のままで、詳細パネル・ポップアップ・一覧の `noName` も `名前未設定`。
|
|
630
|
-
- **件数はクロームロケールに応じて `"en-US"` / `"ja-JP"` で桁区切り** (実行環境の既定ではない)。登録件数・検索結果件数・総検索回数も桁区切りされる (`1234` ではなく `1,234`)。
|
|
631
|
-
- **ピア下限。** `@aiquants/virtualscroll` ^3.7.0、`@aiquants/directory-tree` ^3.12.0、`@aiquants/select-box` ^0.10.0 (いずれも公開時に `workspace:^` から展開)。contact-map は select-box のテキスト正規化関数だけを使い `SelectBox` を描画しないため、select-box の下限は新 API ではなく `workspace:^` の展開による。
|
|
632
|
-
|
|
633
|
-
New API / 新しい API:
|
|
634
|
-
|
|
635
|
-
- `locale` and `labels` on `ContactMapConfig` and `ContactMapAppViewProps`; `@aiquants/contact-map/client` exports `CONTACT_MAP_LABEL_KEYS`, `CONTACT_MAP_LABEL_CATALOGS`, `resolveContactMapLabels`, `useContactMapLabels` and the types `ContactMapLocale`, `ContactMapLabels`, `ContactMapOwnLabels`, `ContactMapLabelOverrides`, `ContactMapStreamProgress`, `ContactMapQueryLengthStat`, `ContactMapHistoryEntry`, `WorkerStatsKind`.
|
|
636
|
-
- `@aiquants/contact-map` exports `ContactMapTreeNaming` and `assertContactMapTreeNaming`; `ContactMapDirectoryEntry` gains `memberCount`.
|
|
637
|
-
- `ContactMapConfig` と `ContactMapAppViewProps` の `locale` / `labels`。`@aiquants/contact-map/client` から `CONTACT_MAP_LABEL_KEYS`、`CONTACT_MAP_LABEL_CATALOGS`、`resolveContactMapLabels`、`useContactMapLabels` と型 `ContactMapLocale`、`ContactMapLabels`、`ContactMapOwnLabels`、`ContactMapLabelOverrides`、`ContactMapStreamProgress`、`ContactMapQueryLengthStat`、`ContactMapHistoryEntry`、`WorkerStatsKind` を公開。
|
|
638
|
-
- `@aiquants/contact-map` から `ContactMapTreeNaming` と `assertContactMapTreeNaming` を公開し、`ContactMapDirectoryEntry` に `memberCount` を追加。
|
|
639
|
-
|
|
640
|
-
```tsx
|
|
641
|
-
// before (0.1.x): Japanese chrome without any prop
|
|
642
|
-
<ContactMapAppView renderHeader={renderHeader} workerUrls={workerUrls} />
|
|
643
|
-
|
|
644
|
-
// after (0.2.0): opt into the Japanese catalog explicitly
|
|
645
|
-
<ContactMapAppView locale="ja" renderHeader={renderHeader} workerUrls={workerUrls} />
|
|
646
|
-
```
|
|
647
|
-
|
|
648
|
-
```ts
|
|
649
|
-
// before (0.1.x)
|
|
650
|
-
const { tree } = buildDirectoryTree(records)
|
|
651
|
-
session.syncRecords(records)
|
|
652
|
-
|
|
653
|
-
// after (0.2.0): pass the fallback names of the chrome locale
|
|
654
|
-
const naming = { uncategorizedCompany: labels.uncategorizedCompany, unnamedPerson: labels.noName }
|
|
655
|
-
const { tree } = buildDirectoryTree(records, naming)
|
|
656
|
-
session.syncRecords(records, naming)
|
|
657
|
-
```
|
|
658
|
-
|
|
659
720
|
---
|
|
660
721
|
|
|
661
722
|
## License / ライセンス
|
package/dist/client.d.mts
CHANGED
|
@@ -7,7 +7,8 @@ import * as _aiquants_virtualscroll from "@aiquants/virtualscroll";
|
|
|
7
7
|
import { VirtualScrollLocale } from "@aiquants/virtualscroll";
|
|
8
8
|
import * as _aiquants_fuzzy_search from "@aiquants/fuzzy-search";
|
|
9
9
|
import { UseFuzzySearchOptions as UseFuzzySearchOptions$1, UnifiedWorkerStats } from "@aiquants/fuzzy-search";
|
|
10
|
-
import { R as
|
|
10
|
+
import { R as ResponsiveThresholds, C as ContactMapDirectoryEntry, a as ContactRecordForTree, b as ContactMapTreeNaming } from "./tree-types-B9J5unS2.mjs";
|
|
11
|
+
import * as _aiquants_route_revalidation from "@aiquants/route-revalidation";
|
|
11
12
|
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
|
12
13
|
interface AdvancedSearchSettingsProps {
|
|
13
14
|
searchOptions: UseFuzzySearchOptions;
|
|
@@ -93,6 +94,7 @@ type ContactMapOwnLabels = {
|
|
|
93
94
|
readonly annotationStreamingProgress: (progress: ContactMapStreamProgress) => string;
|
|
94
95
|
readonly annotationStreamingCount: (loaded: number) => string;
|
|
95
96
|
readonly annotationLoadFailed: (loaded: number) => string;
|
|
97
|
+
readonly annotationReconnecting: (loaded: number) => string;
|
|
96
98
|
readonly annotationAuthRequired: string;
|
|
97
99
|
readonly annotationRetry: string;
|
|
98
100
|
readonly recordUnit: (displayed: string) => string;
|
|
@@ -191,6 +193,8 @@ type ContactMapOwnLabels = {
|
|
|
191
193
|
readonly fieldCreatedAt: string;
|
|
192
194
|
readonly fieldUpdatedBy: string;
|
|
193
195
|
readonly fieldUpdatedAt: string;
|
|
196
|
+
readonly recordDateTime: (value: Date) => string;
|
|
197
|
+
readonly recordDate: (value: Date) => string;
|
|
194
198
|
readonly popupCustomMobilePhone: string;
|
|
195
199
|
readonly popupMobilePhone: string;
|
|
196
200
|
readonly popupCustomCompanyPhone: string;
|
|
@@ -317,6 +321,7 @@ type ContactMapOwnLabels = {
|
|
|
317
321
|
readonly statLastSearchTime: string;
|
|
318
322
|
readonly statErrorCount: string;
|
|
319
323
|
readonly statUpdatedAt: string;
|
|
324
|
+
readonly statsTimestamp: (value: Date) => string;
|
|
320
325
|
readonly statsOperationsHeading: string;
|
|
321
326
|
readonly statTotalOperations: string;
|
|
322
327
|
readonly statsIndexHeading: string;
|
|
@@ -536,7 +541,6 @@ interface ContactMapConfig {
|
|
|
536
541
|
openEmailsWithGmail?: boolean;
|
|
537
542
|
treeWorkerFactory?: () => Worker;
|
|
538
543
|
workerUrls?: ContactMapWorkerUrls;
|
|
539
|
-
layoutSpec?: Partial<ResponsiveLayoutSpec>;
|
|
540
544
|
locale?: ContactMapLocale;
|
|
541
545
|
labels?: ContactMapLabelOverrides;
|
|
542
546
|
}
|
|
@@ -706,7 +710,7 @@ declare const getTreeWorkerSession: (treeWorkerUrl?: string) => TreeWorkerSessio
|
|
|
706
710
|
declare const resetTreeWorkerSessionForTesting: () => void;
|
|
707
711
|
declare function useTreeWorker(records: readonly ContactRecordForTree[], isComplete?: boolean): TreeWorkerState;
|
|
708
712
|
declare const useContactMapLabels: () => ContactMapLabels;
|
|
709
|
-
declare
|
|
713
|
+
declare const contactMapShouldRevalidate: _aiquants_route_revalidation.RouteShouldRevalidate;
|
|
710
714
|
type ProgressProps = react.ComponentPropsWithoutRef<"div"> & {
|
|
711
715
|
value?: number;
|
|
712
716
|
max?: number;
|
|
@@ -719,7 +723,6 @@ interface SwipeOverlayProps {
|
|
|
719
723
|
children: ReactNode;
|
|
720
724
|
className?: string;
|
|
721
725
|
title?: string;
|
|
722
|
-
direction?: "bottom" | "top" | "left" | "right";
|
|
723
726
|
}
|
|
724
727
|
declare const SwipeOverlay: ({ isOpen, onClose, children, className, title }: SwipeOverlayProps) => react.JSX.Element | null;
|
|
725
728
|
declare const Tabs: react.ForwardRefExoticComponent<TabsPrimitive.TabsProps & react.RefAttributes<HTMLDivElement>>;
|
package/dist/client.d.ts
CHANGED
|
@@ -7,7 +7,8 @@ import * as _aiquants_virtualscroll from "@aiquants/virtualscroll";
|
|
|
7
7
|
import { VirtualScrollLocale } from "@aiquants/virtualscroll";
|
|
8
8
|
import * as _aiquants_fuzzy_search from "@aiquants/fuzzy-search";
|
|
9
9
|
import { UseFuzzySearchOptions as UseFuzzySearchOptions$1, UnifiedWorkerStats } from "@aiquants/fuzzy-search";
|
|
10
|
-
import { R as
|
|
10
|
+
import { R as ResponsiveThresholds, C as ContactMapDirectoryEntry, a as ContactRecordForTree, b as ContactMapTreeNaming } from "./tree-types-B9J5unS2.js";
|
|
11
|
+
import * as _aiquants_route_revalidation from "@aiquants/route-revalidation";
|
|
11
12
|
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
|
12
13
|
interface AdvancedSearchSettingsProps {
|
|
13
14
|
searchOptions: UseFuzzySearchOptions;
|
|
@@ -93,6 +94,7 @@ type ContactMapOwnLabels = {
|
|
|
93
94
|
readonly annotationStreamingProgress: (progress: ContactMapStreamProgress) => string;
|
|
94
95
|
readonly annotationStreamingCount: (loaded: number) => string;
|
|
95
96
|
readonly annotationLoadFailed: (loaded: number) => string;
|
|
97
|
+
readonly annotationReconnecting: (loaded: number) => string;
|
|
96
98
|
readonly annotationAuthRequired: string;
|
|
97
99
|
readonly annotationRetry: string;
|
|
98
100
|
readonly recordUnit: (displayed: string) => string;
|
|
@@ -191,6 +193,8 @@ type ContactMapOwnLabels = {
|
|
|
191
193
|
readonly fieldCreatedAt: string;
|
|
192
194
|
readonly fieldUpdatedBy: string;
|
|
193
195
|
readonly fieldUpdatedAt: string;
|
|
196
|
+
readonly recordDateTime: (value: Date) => string;
|
|
197
|
+
readonly recordDate: (value: Date) => string;
|
|
194
198
|
readonly popupCustomMobilePhone: string;
|
|
195
199
|
readonly popupMobilePhone: string;
|
|
196
200
|
readonly popupCustomCompanyPhone: string;
|
|
@@ -317,6 +321,7 @@ type ContactMapOwnLabels = {
|
|
|
317
321
|
readonly statLastSearchTime: string;
|
|
318
322
|
readonly statErrorCount: string;
|
|
319
323
|
readonly statUpdatedAt: string;
|
|
324
|
+
readonly statsTimestamp: (value: Date) => string;
|
|
320
325
|
readonly statsOperationsHeading: string;
|
|
321
326
|
readonly statTotalOperations: string;
|
|
322
327
|
readonly statsIndexHeading: string;
|
|
@@ -536,7 +541,6 @@ interface ContactMapConfig {
|
|
|
536
541
|
openEmailsWithGmail?: boolean;
|
|
537
542
|
treeWorkerFactory?: () => Worker;
|
|
538
543
|
workerUrls?: ContactMapWorkerUrls;
|
|
539
|
-
layoutSpec?: Partial<ResponsiveLayoutSpec>;
|
|
540
544
|
locale?: ContactMapLocale;
|
|
541
545
|
labels?: ContactMapLabelOverrides;
|
|
542
546
|
}
|
|
@@ -706,7 +710,7 @@ declare const getTreeWorkerSession: (treeWorkerUrl?: string) => TreeWorkerSessio
|
|
|
706
710
|
declare const resetTreeWorkerSessionForTesting: () => void;
|
|
707
711
|
declare function useTreeWorker(records: readonly ContactRecordForTree[], isComplete?: boolean): TreeWorkerState;
|
|
708
712
|
declare const useContactMapLabels: () => ContactMapLabels;
|
|
709
|
-
declare
|
|
713
|
+
declare const contactMapShouldRevalidate: _aiquants_route_revalidation.RouteShouldRevalidate;
|
|
710
714
|
type ProgressProps = react.ComponentPropsWithoutRef<"div"> & {
|
|
711
715
|
value?: number;
|
|
712
716
|
max?: number;
|
|
@@ -719,7 +723,6 @@ interface SwipeOverlayProps {
|
|
|
719
723
|
children: ReactNode;
|
|
720
724
|
className?: string;
|
|
721
725
|
title?: string;
|
|
722
|
-
direction?: "bottom" | "top" | "left" | "right";
|
|
723
726
|
}
|
|
724
727
|
declare const SwipeOverlay: ({ isOpen, onClose, children, className, title }: SwipeOverlayProps) => react.JSX.Element | null;
|
|
725
728
|
declare const Tabs: react.ForwardRefExoticComponent<TabsPrimitive.TabsProps & react.RefAttributes<HTMLDivElement>>;
|