@cp949/japanpost-react 1.0.3 → 1.0.4
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/dist/client.d.ts +296 -1
- package/dist/client.es.js +493 -1
- package/dist/index.d.ts +291 -1
- package/dist/index.es.js +512 -281
- package/package.json +4 -7
- package/dist/src/client.d.ts +0 -7
- package/dist/src/client.d.ts.map +0 -1
- package/dist/src/components/AddressSearchInput.d.ts +0 -8
- package/dist/src/components/AddressSearchInput.d.ts.map +0 -1
- package/dist/src/components/PostalCodeInput.d.ts +0 -8
- package/dist/src/components/PostalCodeInput.d.ts.map +0 -1
- package/dist/src/core/errors.d.ts +0 -11
- package/dist/src/core/errors.d.ts.map +0 -1
- package/dist/src/core/formatters.d.ts +0 -12
- package/dist/src/core/formatters.d.ts.map +0 -1
- package/dist/src/core/normalizers.d.ts +0 -7
- package/dist/src/core/normalizers.d.ts.map +0 -1
- package/dist/src/core/types.d.ts +0 -261
- package/dist/src/core/types.d.ts.map +0 -1
- package/dist/src/core/validators.d.ts +0 -6
- package/dist/src/core/validators.d.ts.map +0 -1
- package/dist/src/index.d.ts +0 -11
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/react/toJapanAddressError.d.ts +0 -8
- package/dist/src/react/toJapanAddressError.d.ts.map +0 -1
- package/dist/src/react/useJapanAddress.d.ts +0 -8
- package/dist/src/react/useJapanAddress.d.ts.map +0 -1
- package/dist/src/react/useJapanAddressSearch.d.ts +0 -7
- package/dist/src/react/useJapanAddressSearch.d.ts.map +0 -1
- package/dist/src/react/useJapanPostalCode.d.ts +0 -7
- package/dist/src/react/useJapanPostalCode.d.ts.map +0 -1
- package/dist/src/react/useLatestRequestState.d.ts +0 -23
- package/dist/src/react/useLatestRequestState.d.ts.map +0 -1
package/dist/index.es.js
CHANGED
|
@@ -1,299 +1,530 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
1
|
+
import { useState, useMemo, useRef, useCallback, useEffect } from 'react';
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
// src/components/AddressSearchInput.tsx
|
|
5
|
+
function AddressSearchInput({
|
|
6
|
+
defaultValue = "",
|
|
7
|
+
value,
|
|
8
|
+
disabled,
|
|
9
|
+
label = "Address keyword",
|
|
10
|
+
buttonLabel = "Search",
|
|
11
|
+
inputProps,
|
|
12
|
+
buttonProps,
|
|
13
|
+
onChange,
|
|
14
|
+
onSearch
|
|
15
|
+
}) {
|
|
16
|
+
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
17
|
+
const currentValue = value ?? internalValue;
|
|
18
|
+
function handleSubmit(event) {
|
|
19
|
+
event.preventDefault();
|
|
20
|
+
onSearch(currentValue.trim());
|
|
21
|
+
}
|
|
22
|
+
function handleChange(nextValue) {
|
|
23
|
+
if (value === void 0) {
|
|
24
|
+
setInternalValue(nextValue);
|
|
25
|
+
}
|
|
26
|
+
onChange?.(nextValue);
|
|
27
|
+
}
|
|
28
|
+
return /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, children: [
|
|
29
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
30
|
+
label,
|
|
31
|
+
/* @__PURE__ */ jsx(
|
|
32
|
+
"input",
|
|
33
|
+
{
|
|
34
|
+
...inputProps,
|
|
35
|
+
disabled,
|
|
36
|
+
value: currentValue,
|
|
37
|
+
onChange: (event) => handleChange(event.target.value)
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
] }),
|
|
41
|
+
/* @__PURE__ */ jsx("button", { ...buttonProps, disabled, type: "submit", children: buttonLabel })
|
|
42
|
+
] });
|
|
26
43
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
function
|
|
30
|
-
|
|
44
|
+
|
|
45
|
+
// src/core/formatters.ts
|
|
46
|
+
function normalizeJapanPostalCode(value) {
|
|
47
|
+
return value.replace(/[^\d]/g, "");
|
|
31
48
|
}
|
|
32
|
-
function
|
|
33
|
-
|
|
34
|
-
|
|
49
|
+
function formatJapanPostalCode(value) {
|
|
50
|
+
const normalized = normalizeJapanPostalCode(value);
|
|
51
|
+
if (normalized.length !== 7) {
|
|
52
|
+
return normalized;
|
|
53
|
+
}
|
|
54
|
+
return `${normalized.slice(0, 3)}-${normalized.slice(3)}`;
|
|
35
55
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
function PostalCodeInput({
|
|
57
|
+
defaultValue = "",
|
|
58
|
+
value,
|
|
59
|
+
disabled,
|
|
60
|
+
label = "Postal code",
|
|
61
|
+
buttonLabel = "Search",
|
|
62
|
+
inputProps,
|
|
63
|
+
buttonProps,
|
|
64
|
+
onChange,
|
|
65
|
+
onSearch
|
|
66
|
+
}) {
|
|
67
|
+
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
68
|
+
const currentValue = value ?? internalValue;
|
|
69
|
+
function handleSubmit(event) {
|
|
70
|
+
event.preventDefault();
|
|
71
|
+
onSearch(normalizeJapanPostalCode(currentValue));
|
|
72
|
+
}
|
|
73
|
+
function handleChange(nextValue) {
|
|
74
|
+
if (value === void 0) {
|
|
75
|
+
setInternalValue(nextValue);
|
|
76
|
+
}
|
|
77
|
+
onChange?.(nextValue);
|
|
78
|
+
}
|
|
79
|
+
return /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, children: [
|
|
80
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
81
|
+
label,
|
|
82
|
+
/* @__PURE__ */ jsx(
|
|
83
|
+
"input",
|
|
84
|
+
{
|
|
85
|
+
...inputProps,
|
|
86
|
+
disabled,
|
|
87
|
+
inputMode: inputProps?.inputMode ?? "numeric",
|
|
88
|
+
value: currentValue,
|
|
89
|
+
onChange: (event) => handleChange(event.target.value)
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
] }),
|
|
93
|
+
/* @__PURE__ */ jsx("button", { ...buttonProps, disabled, type: "submit", children: buttonLabel })
|
|
94
|
+
] });
|
|
61
95
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
function
|
|
65
|
-
|
|
66
|
-
|
|
96
|
+
|
|
97
|
+
// src/core/errors.ts
|
|
98
|
+
function createJapanAddressError(code, message, options) {
|
|
99
|
+
const error = new Error(message);
|
|
100
|
+
error.name = "JapanAddressError";
|
|
101
|
+
error.code = code;
|
|
102
|
+
error.cause = options?.cause;
|
|
103
|
+
error.status = options?.status;
|
|
104
|
+
return error;
|
|
67
105
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
function
|
|
71
|
-
|
|
106
|
+
|
|
107
|
+
// src/core/normalizers.ts
|
|
108
|
+
function joinAddressParts(parts) {
|
|
109
|
+
return parts.filter(Boolean).join(" ").trim();
|
|
72
110
|
}
|
|
73
|
-
function
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
111
|
+
function normalizeJapanPostAddressRecord(record) {
|
|
112
|
+
const address = joinAddressParts([
|
|
113
|
+
record.prefecture,
|
|
114
|
+
record.city,
|
|
115
|
+
record.town,
|
|
116
|
+
record.detail ?? ""
|
|
117
|
+
]);
|
|
118
|
+
return {
|
|
119
|
+
postalCode: record.postalCode,
|
|
120
|
+
prefecture: record.prefecture,
|
|
121
|
+
prefectureKana: record.prefectureKana,
|
|
122
|
+
city: record.city,
|
|
123
|
+
cityKana: record.cityKana,
|
|
124
|
+
town: record.town,
|
|
125
|
+
townKana: record.townKana,
|
|
126
|
+
address,
|
|
127
|
+
provider: "japan-post"
|
|
128
|
+
};
|
|
91
129
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
function
|
|
95
|
-
|
|
130
|
+
|
|
131
|
+
// src/core/validators.ts
|
|
132
|
+
function isValidJapanPostalCode(value) {
|
|
133
|
+
return /^\d{7}$/.test(normalizeJapanPostalCode(value));
|
|
96
134
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
function
|
|
100
|
-
|
|
135
|
+
|
|
136
|
+
// src/react/toJapanAddressError.ts
|
|
137
|
+
function toJapanAddressError(error) {
|
|
138
|
+
if (typeof error === "object" && error !== null && "code" in error && typeof error.code === "string") {
|
|
139
|
+
return error;
|
|
140
|
+
}
|
|
141
|
+
return createJapanAddressError(
|
|
142
|
+
"data_source_error",
|
|
143
|
+
error instanceof Error ? error.message : "Unknown error",
|
|
144
|
+
{
|
|
145
|
+
cause: error
|
|
146
|
+
}
|
|
147
|
+
);
|
|
101
148
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
149
|
+
function useLatestRequestState() {
|
|
150
|
+
const requestIdRef = useRef(0);
|
|
151
|
+
const isMountedRef = useRef(true);
|
|
152
|
+
const abortControllerRef = useRef(null);
|
|
153
|
+
const [loading, setLoading] = useState(false);
|
|
154
|
+
const [data, setData] = useState(null);
|
|
155
|
+
const [error, setError] = useState(null);
|
|
156
|
+
const isCurrentRequest = useCallback((requestId) => {
|
|
157
|
+
return isMountedRef.current && requestId === requestIdRef.current;
|
|
158
|
+
}, []);
|
|
159
|
+
const invalidateCurrentRequest = useCallback(() => {
|
|
160
|
+
requestIdRef.current += 1;
|
|
161
|
+
abortControllerRef.current?.abort();
|
|
162
|
+
abortControllerRef.current = null;
|
|
163
|
+
}, []);
|
|
164
|
+
useEffect(() => {
|
|
165
|
+
isMountedRef.current = true;
|
|
166
|
+
return () => {
|
|
167
|
+
isMountedRef.current = false;
|
|
168
|
+
invalidateCurrentRequest();
|
|
169
|
+
};
|
|
170
|
+
}, [invalidateCurrentRequest]);
|
|
171
|
+
const beginRequest = useCallback(() => {
|
|
172
|
+
const requestId = requestIdRef.current + 1;
|
|
173
|
+
requestIdRef.current = requestId;
|
|
174
|
+
abortControllerRef.current?.abort();
|
|
175
|
+
const abortController = new AbortController();
|
|
176
|
+
abortControllerRef.current = abortController;
|
|
177
|
+
setLoading(true);
|
|
178
|
+
setError(null);
|
|
179
|
+
return {
|
|
180
|
+
requestId,
|
|
181
|
+
signal: abortController.signal
|
|
182
|
+
};
|
|
183
|
+
}, []);
|
|
184
|
+
const setSuccess = useCallback((requestId, result) => {
|
|
185
|
+
if (isCurrentRequest(requestId)) {
|
|
186
|
+
setData(result);
|
|
187
|
+
}
|
|
188
|
+
}, [isCurrentRequest]);
|
|
189
|
+
const setFailure = useCallback(
|
|
190
|
+
(requestId, nextError) => {
|
|
191
|
+
if (isCurrentRequest(requestId)) {
|
|
192
|
+
setError(nextError);
|
|
193
|
+
setData(null);
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
},
|
|
197
|
+
[isCurrentRequest]
|
|
198
|
+
);
|
|
199
|
+
const cancel = useCallback(() => {
|
|
200
|
+
invalidateCurrentRequest();
|
|
201
|
+
setLoading(false);
|
|
202
|
+
}, [invalidateCurrentRequest]);
|
|
203
|
+
const finishRequest = useCallback((requestId) => {
|
|
204
|
+
if (isCurrentRequest(requestId)) {
|
|
205
|
+
setLoading(false);
|
|
206
|
+
abortControllerRef.current = null;
|
|
207
|
+
}
|
|
208
|
+
}, [isCurrentRequest]);
|
|
209
|
+
const reset = useCallback(() => {
|
|
210
|
+
cancel();
|
|
211
|
+
setData(null);
|
|
212
|
+
setError(null);
|
|
213
|
+
}, [cancel]);
|
|
214
|
+
return {
|
|
215
|
+
loading,
|
|
216
|
+
data,
|
|
217
|
+
error,
|
|
218
|
+
beginRequest,
|
|
219
|
+
setSuccess,
|
|
220
|
+
setFailure,
|
|
221
|
+
finishRequest,
|
|
222
|
+
cancel,
|
|
223
|
+
reset
|
|
224
|
+
};
|
|
139
225
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
function
|
|
143
|
-
|
|
144
|
-
|
|
226
|
+
|
|
227
|
+
// src/react/useJapanAddressSearch.ts
|
|
228
|
+
function resolveAddressSearchDataSource(dataSource) {
|
|
229
|
+
if (dataSource) {
|
|
230
|
+
return dataSource;
|
|
231
|
+
}
|
|
232
|
+
throw new Error("useJapanAddressSearch requires options.dataSource");
|
|
145
233
|
}
|
|
146
|
-
function
|
|
147
|
-
|
|
234
|
+
function normalizeSearchText(value) {
|
|
235
|
+
const normalized = value?.trim();
|
|
236
|
+
return normalized ? normalized : void 0;
|
|
148
237
|
}
|
|
149
|
-
function
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
238
|
+
function normalizeAddressSearchInput(input) {
|
|
239
|
+
const request = typeof input === "string" ? { addressQuery: input } : input;
|
|
240
|
+
const addressQuery = normalizeSearchText(request.addressQuery);
|
|
241
|
+
const prefCode = normalizeSearchText(request.prefCode);
|
|
242
|
+
const prefName = normalizeSearchText(request.prefName);
|
|
243
|
+
const prefKana = normalizeSearchText(request.prefKana);
|
|
244
|
+
const prefRoma = normalizeSearchText(request.prefRoma);
|
|
245
|
+
const cityCode = normalizeSearchText(request.cityCode);
|
|
246
|
+
const cityName = normalizeSearchText(request.cityName);
|
|
247
|
+
const cityKana = normalizeSearchText(request.cityKana);
|
|
248
|
+
const cityRoma = normalizeSearchText(request.cityRoma);
|
|
249
|
+
const townName = normalizeSearchText(request.townName);
|
|
250
|
+
const townKana = normalizeSearchText(request.townKana);
|
|
251
|
+
const townRoma = normalizeSearchText(request.townRoma);
|
|
252
|
+
if (addressQuery === void 0 && prefCode === void 0 && prefName === void 0 && prefKana === void 0 && prefRoma === void 0 && cityCode === void 0 && cityName === void 0 && cityKana === void 0 && cityRoma === void 0 && townName === void 0 && townKana === void 0 && townRoma === void 0) {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
...addressQuery === void 0 ? {} : { addressQuery },
|
|
257
|
+
...prefCode === void 0 ? {} : { prefCode },
|
|
258
|
+
...prefName === void 0 ? {} : { prefName },
|
|
259
|
+
...prefKana === void 0 ? {} : { prefKana },
|
|
260
|
+
...prefRoma === void 0 ? {} : { prefRoma },
|
|
261
|
+
...cityCode === void 0 ? {} : { cityCode },
|
|
262
|
+
...cityName === void 0 ? {} : { cityName },
|
|
263
|
+
...cityKana === void 0 ? {} : { cityKana },
|
|
264
|
+
...cityRoma === void 0 ? {} : { cityRoma },
|
|
265
|
+
...townName === void 0 ? {} : { townName },
|
|
266
|
+
...townKana === void 0 ? {} : { townKana },
|
|
267
|
+
...townRoma === void 0 ? {} : { townRoma },
|
|
268
|
+
pageNumber: request.pageNumber ?? 0,
|
|
269
|
+
rowsPerPage: request.rowsPerPage ?? 100,
|
|
270
|
+
...request.includeCityDetails === void 0 ? {} : {
|
|
271
|
+
includeCityDetails: request.includeCityDetails
|
|
272
|
+
},
|
|
273
|
+
...request.includePrefectureDetails === void 0 ? {} : {
|
|
274
|
+
includePrefectureDetails: request.includePrefectureDetails
|
|
275
|
+
}
|
|
276
|
+
};
|
|
169
277
|
}
|
|
170
|
-
function
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
278
|
+
function waitForAbort(signal) {
|
|
279
|
+
if (signal.aborted) {
|
|
280
|
+
return Promise.resolve(null);
|
|
281
|
+
}
|
|
282
|
+
return new Promise((resolve) => {
|
|
283
|
+
const onAbort = () => {
|
|
284
|
+
signal.removeEventListener("abort", onAbort);
|
|
285
|
+
resolve(null);
|
|
286
|
+
};
|
|
287
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
288
|
+
});
|
|
177
289
|
}
|
|
178
|
-
function
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
290
|
+
function useJapanAddressSearch(options) {
|
|
291
|
+
const dataSource = useMemo(
|
|
292
|
+
() => resolveAddressSearchDataSource(options.dataSource),
|
|
293
|
+
[options.dataSource]
|
|
294
|
+
);
|
|
295
|
+
const debounceMs = options.debounceMs ?? 0;
|
|
296
|
+
const timeoutRef = useRef(
|
|
297
|
+
null
|
|
298
|
+
);
|
|
299
|
+
const pendingResolveRef = useRef(null);
|
|
300
|
+
const {
|
|
301
|
+
loading,
|
|
302
|
+
data,
|
|
303
|
+
error,
|
|
304
|
+
beginRequest,
|
|
305
|
+
setSuccess,
|
|
306
|
+
setFailure,
|
|
307
|
+
finishRequest,
|
|
308
|
+
cancel: cancelRequestState,
|
|
309
|
+
reset: resetRequestState
|
|
310
|
+
} = useLatestRequestState();
|
|
311
|
+
const clearPendingDebounce = useCallback(
|
|
312
|
+
(result) => {
|
|
313
|
+
if (timeoutRef.current !== null) {
|
|
314
|
+
globalThis.clearTimeout(timeoutRef.current);
|
|
315
|
+
timeoutRef.current = null;
|
|
316
|
+
}
|
|
317
|
+
pendingResolveRef.current?.(result);
|
|
318
|
+
pendingResolveRef.current = null;
|
|
319
|
+
},
|
|
320
|
+
[]
|
|
321
|
+
);
|
|
322
|
+
useEffect(() => {
|
|
323
|
+
return () => {
|
|
324
|
+
clearPendingDebounce(null);
|
|
325
|
+
};
|
|
326
|
+
}, [clearPendingDebounce]);
|
|
327
|
+
const reset = useCallback(() => {
|
|
328
|
+
clearPendingDebounce(null);
|
|
329
|
+
resetRequestState();
|
|
330
|
+
}, [clearPendingDebounce, resetRequestState]);
|
|
331
|
+
const cancel = useCallback(() => {
|
|
332
|
+
clearPendingDebounce(null);
|
|
333
|
+
cancelRequestState();
|
|
334
|
+
}, [clearPendingDebounce, cancelRequestState]);
|
|
335
|
+
const runSearch = useCallback(async (requestId, signal, request) => {
|
|
336
|
+
try {
|
|
337
|
+
const requestOptions = {
|
|
338
|
+
signal
|
|
339
|
+
};
|
|
340
|
+
const searchPromise = dataSource.searchAddress(
|
|
341
|
+
request,
|
|
342
|
+
requestOptions
|
|
343
|
+
);
|
|
344
|
+
const result = await Promise.race([
|
|
345
|
+
searchPromise,
|
|
346
|
+
waitForAbort(signal)
|
|
347
|
+
]);
|
|
348
|
+
if (signal.aborted || result === null) {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
setSuccess(requestId, result);
|
|
352
|
+
return result;
|
|
353
|
+
} catch (caughtError) {
|
|
354
|
+
if (signal.aborted) {
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
return setFailure(requestId, toJapanAddressError(caughtError));
|
|
358
|
+
} finally {
|
|
359
|
+
finishRequest(requestId);
|
|
360
|
+
}
|
|
361
|
+
}, [dataSource, finishRequest, setFailure, setSuccess]);
|
|
362
|
+
const search = useCallback((input) => {
|
|
363
|
+
const request = normalizeAddressSearchInput(input);
|
|
364
|
+
const { requestId, signal } = beginRequest();
|
|
365
|
+
clearPendingDebounce(null);
|
|
366
|
+
if (request === null) {
|
|
367
|
+
const result = setFailure(
|
|
368
|
+
requestId,
|
|
369
|
+
createJapanAddressError(
|
|
370
|
+
"invalid_query",
|
|
371
|
+
"Address query is required"
|
|
372
|
+
)
|
|
373
|
+
);
|
|
374
|
+
finishRequest(requestId);
|
|
375
|
+
return Promise.resolve(result);
|
|
376
|
+
}
|
|
377
|
+
if (debounceMs <= 0) {
|
|
378
|
+
return new Promise((resolve) => {
|
|
379
|
+
pendingResolveRef.current = resolve;
|
|
380
|
+
void runSearch(requestId, signal, request).then((result) => {
|
|
381
|
+
resolve(result);
|
|
382
|
+
if (pendingResolveRef.current === resolve) {
|
|
383
|
+
pendingResolveRef.current = null;
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
return new Promise((resolve) => {
|
|
389
|
+
pendingResolveRef.current = resolve;
|
|
390
|
+
timeoutRef.current = globalThis.setTimeout(() => {
|
|
391
|
+
timeoutRef.current = null;
|
|
392
|
+
const pendingResolve = pendingResolveRef.current;
|
|
393
|
+
pendingResolveRef.current = null;
|
|
394
|
+
void runSearch(requestId, signal, request).then((result) => {
|
|
395
|
+
pendingResolve?.(result);
|
|
396
|
+
});
|
|
397
|
+
}, debounceMs);
|
|
398
|
+
});
|
|
399
|
+
}, [beginRequest, clearPendingDebounce, debounceMs, runSearch]);
|
|
400
|
+
return {
|
|
401
|
+
loading,
|
|
402
|
+
data,
|
|
403
|
+
error,
|
|
404
|
+
cancel,
|
|
405
|
+
reset,
|
|
406
|
+
search
|
|
407
|
+
};
|
|
236
408
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
409
|
+
function resolvePostalCodeDataSource(dataSource) {
|
|
410
|
+
if (dataSource) {
|
|
411
|
+
return dataSource;
|
|
412
|
+
}
|
|
413
|
+
throw new Error("useJapanPostalCode requires options.dataSource");
|
|
242
414
|
}
|
|
243
|
-
function
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
415
|
+
function useJapanPostalCode(options) {
|
|
416
|
+
const dataSource = useMemo(
|
|
417
|
+
() => resolvePostalCodeDataSource(options.dataSource),
|
|
418
|
+
[options.dataSource]
|
|
419
|
+
);
|
|
420
|
+
const {
|
|
421
|
+
loading,
|
|
422
|
+
data,
|
|
423
|
+
error,
|
|
424
|
+
beginRequest,
|
|
425
|
+
setSuccess,
|
|
426
|
+
setFailure,
|
|
427
|
+
finishRequest,
|
|
428
|
+
cancel,
|
|
429
|
+
reset
|
|
430
|
+
} = useLatestRequestState();
|
|
431
|
+
const search = useCallback(
|
|
432
|
+
async (input) => {
|
|
433
|
+
const { requestId, signal } = beginRequest();
|
|
434
|
+
try {
|
|
435
|
+
const requestInput = typeof input === "string" ? { postalCode: input } : input;
|
|
436
|
+
const postalCode = normalizeJapanPostalCode(requestInput.postalCode);
|
|
437
|
+
if (!/^\d{3,7}$/.test(postalCode)) {
|
|
438
|
+
throw createJapanAddressError(
|
|
439
|
+
"invalid_postal_code",
|
|
440
|
+
"Postal code must contain between 3 and 7 digits"
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
const requestOptions = {
|
|
444
|
+
signal
|
|
445
|
+
};
|
|
446
|
+
const request = {
|
|
447
|
+
postalCode,
|
|
448
|
+
pageNumber: requestInput.pageNumber ?? 0,
|
|
449
|
+
rowsPerPage: requestInput.rowsPerPage ?? 100,
|
|
450
|
+
...requestInput.includeParenthesesTown === void 0 ? {} : {
|
|
451
|
+
includeParenthesesTown: requestInput.includeParenthesesTown
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
const result = await dataSource.lookupPostalCode(
|
|
455
|
+
request,
|
|
456
|
+
requestOptions
|
|
457
|
+
);
|
|
458
|
+
if (signal.aborted) {
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
setSuccess(requestId, result);
|
|
462
|
+
return result;
|
|
463
|
+
} catch (caughtError) {
|
|
464
|
+
if (signal.aborted) {
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
return setFailure(requestId, toJapanAddressError(caughtError));
|
|
468
|
+
} finally {
|
|
469
|
+
finishRequest(requestId);
|
|
470
|
+
}
|
|
471
|
+
},
|
|
472
|
+
[beginRequest, dataSource, finishRequest, setFailure, setSuccess]
|
|
473
|
+
);
|
|
474
|
+
return {
|
|
475
|
+
loading,
|
|
476
|
+
data,
|
|
477
|
+
error,
|
|
478
|
+
cancel,
|
|
479
|
+
reset,
|
|
480
|
+
search
|
|
481
|
+
};
|
|
276
482
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
function
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
483
|
+
|
|
484
|
+
// src/react/useJapanAddress.ts
|
|
485
|
+
function useJapanAddress(options) {
|
|
486
|
+
const dataSource = useMemo(() => {
|
|
487
|
+
if (options.dataSource) {
|
|
488
|
+
return options.dataSource;
|
|
489
|
+
}
|
|
490
|
+
throw new Error("useJapanAddress requires options.dataSource");
|
|
491
|
+
}, [options.dataSource]);
|
|
492
|
+
const postalCode = useJapanPostalCode({ dataSource });
|
|
493
|
+
const addressSearch = useJapanAddressSearch({
|
|
494
|
+
dataSource,
|
|
495
|
+
debounceMs: options.debounceMs
|
|
496
|
+
});
|
|
497
|
+
const resetPostalCode = postalCode.reset;
|
|
498
|
+
const searchPostalCode = postalCode.search;
|
|
499
|
+
const resetAddressSearch = addressSearch.reset;
|
|
500
|
+
const searchAddressKeyword = addressSearch.search;
|
|
501
|
+
const [activeSearch, setActiveSearch] = useState(null);
|
|
502
|
+
const searchByPostalCode = useCallback(async (input) => {
|
|
503
|
+
resetAddressSearch();
|
|
504
|
+
setActiveSearch("postalCode");
|
|
505
|
+
return searchPostalCode(input);
|
|
506
|
+
}, [resetAddressSearch, searchPostalCode]);
|
|
507
|
+
const searchByAddressQuery = useCallback(async (input) => {
|
|
508
|
+
resetPostalCode();
|
|
509
|
+
setActiveSearch("addressQuery");
|
|
510
|
+
return searchAddressKeyword(input);
|
|
511
|
+
}, [resetPostalCode, searchAddressKeyword]);
|
|
512
|
+
const reset = useCallback(() => {
|
|
513
|
+
resetPostalCode();
|
|
514
|
+
resetAddressSearch();
|
|
515
|
+
setActiveSearch(null);
|
|
516
|
+
}, [resetAddressSearch, resetPostalCode]);
|
|
517
|
+
const data = activeSearch === "postalCode" ? postalCode.data : activeSearch === "addressQuery" ? addressSearch.data : null;
|
|
518
|
+
const error = activeSearch === "postalCode" ? postalCode.error : activeSearch === "addressQuery" ? addressSearch.error : null;
|
|
519
|
+
return {
|
|
520
|
+
// 사용자는 현재 활성 모드만 보더라도, 내부에서는 두 훅 중 하나라도 정리 중이면 로딩으로 본다.
|
|
521
|
+
loading: postalCode.loading || addressSearch.loading,
|
|
522
|
+
data,
|
|
523
|
+
error,
|
|
524
|
+
reset,
|
|
525
|
+
searchByPostalCode,
|
|
526
|
+
searchByAddressQuery
|
|
527
|
+
};
|
|
297
528
|
}
|
|
298
|
-
|
|
299
|
-
export {
|
|
529
|
+
|
|
530
|
+
export { AddressSearchInput, PostalCodeInput, createJapanAddressError, formatJapanPostalCode, isValidJapanPostalCode, normalizeJapanPostAddressRecord, normalizeJapanPostalCode, useJapanAddress, useJapanAddressSearch, useJapanPostalCode };
|