@yeongseoksong/framework 1.6.1 → 1.7.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.
@@ -1,4 +1,5 @@
1
1
  import { CommonInfo } from '../types/index.mjs';
2
+ import { AxiosInstance } from 'axios';
2
3
  import 'react';
3
4
 
4
5
  /**
@@ -17,6 +18,8 @@ declare const COMPANY_NAME: string;
17
18
  declare const LOGO_SRC: string;
18
19
  /** 헤더 로고 대체 텍스트. */
19
20
  declare const LOGO_ALT: string;
21
+ /** API 서버 base URL. 미지정 시 빈 문자열 — axios가 상대 경로로 요청한다(같은 오리진). */
22
+ declare const API_BASE_URL: string;
20
23
 
21
24
  /** 문자열 안의 `%c`를 회사명으로 치환한다. 서버/클라이언트 양쪽에서 안전하다. */
22
25
  declare function t(text: string): string;
@@ -111,4 +114,50 @@ declare function withJosa(word: string, pair: JosaPair): string;
111
114
  */
112
115
  declare function fixJosa(text: string): string;
113
116
 
114
- export { COMPANY_NAME, type Finalizer, type Finalizers, type JosaPair, LOGO_ALT, LOGO_SRC, type SortDirection, filterAndSort, fixJosa, getFinalSound, hasFinalConsonant, josa, runFinalizers, t, withJosa };
117
+ /**
118
+ * 표준 API 클라이언트.
119
+ *
120
+ * 인증은 기본적으로 httpOnly 쿠키(`withCredentials`)로 실어 보낸다 — `useAuthStore`가
121
+ * 토큰을 들고 있지 않는 이유(XSS)와 같은 전제다. 쿠키를 안 쓰는 소비자 앱은
122
+ * {@link setAccessTokenGetter}로 Authorization 헤더 주입을 추가로 켤 수 있다.
123
+ */
124
+ declare const apiClient: AxiosInstance;
125
+ type AccessTokenGetter = () => string | null | undefined;
126
+ /**
127
+ * 매 요청에 `Authorization: Bearer <token>` 헤더를 자동으로 붙이려면 등록한다.
128
+ * `null`을 넘기면 해제된다. 토큰 자체는 여기서 들고 있지 않고 호출 시점에
129
+ * getter를 통해서만 읽는다 — 보관 방식(메모리·httpOnly 쿠키 등)은 소비자 몫이다.
130
+ */
131
+ declare function setAccessTokenGetter(getter: AccessTokenGetter | null): void;
132
+ type RefreshHandler = () => Promise<boolean>;
133
+ /**
134
+ * Access 토큰 만료(401) 시 재발급을 시도할 핸들러를 등록한다. `null`을 넘기면 해제된다.
135
+ *
136
+ * 핸들러가 `true`를 반환하면 실패했던 요청을 한 번만 재시도하고, `false`나 예외면
137
+ * {@link setUnauthorizedHandler}로 등록한 콜백으로 넘어간다. 재발급 자체를 어떻게
138
+ * 하는지는 소비자 몫이다 — 쿠키 인증이면 핸들러 안에서 refresh 엔드포인트를
139
+ * (`withCredentials`로) 호출해 새 access 쿠키를 받으면 되고, Bearer 토큰이면 핸들러가
140
+ * 새 토큰을 받아 자체 저장소에 반영해야 한다(다음 요청은 `setAccessTokenGetter`가
141
+ * 그 저장소에서 새 값을 읽는다).
142
+ */
143
+ declare function setRefreshHandler(handler: RefreshHandler | null): void;
144
+ /** 서버가 내려주는 표준 에러 바디. 소비자 API가 이 형태가 아니면 message는 axios 기본 메시지로 폴백한다. */
145
+ interface ApiErrorBody {
146
+ message?: string;
147
+ code?: string;
148
+ }
149
+ /** 인터셉터가 모든 실패 요청을 이 형태로 통일해 던진다 — 호출부가 axios 세부 타입을 몰라도 된다. */
150
+ declare class ApiError extends Error {
151
+ readonly status?: number;
152
+ readonly code?: string;
153
+ constructor(message: string, status?: number, code?: string);
154
+ }
155
+ type UnauthorizedHandler = () => void;
156
+ /**
157
+ * 401 응답을 받았을 때 실행할 핸들러(로그아웃 등)를 등록한다. `null`을 넘기면 해제된다.
158
+ * `platform/`은 store를 모르므로(아래 index.ts 상단 주석 참고) `useAuthStore`를
159
+ * 직접 참조하지 않고, 소비자 앱이 이 콜백으로 연결한다.
160
+ */
161
+ declare function setUnauthorizedHandler(handler: UnauthorizedHandler | null): void;
162
+
163
+ export { API_BASE_URL, ApiError, type ApiErrorBody, COMPANY_NAME, type Finalizer, type Finalizers, type JosaPair, LOGO_ALT, LOGO_SRC, type SortDirection, apiClient, filterAndSort, fixJosa, getFinalSound, hasFinalConsonant, josa, runFinalizers, setAccessTokenGetter, setRefreshHandler, setUnauthorizedHandler, t, withJosa };
@@ -26,6 +26,8 @@ var _a2;
26
26
  var LOGO_SRC = (_a2 = process.env.NEXT_PUBLIC_LOGO_SRC) != null ? _a2 : "/logo.svg";
27
27
  var _a3;
28
28
  var LOGO_ALT = (_a3 = process.env.NEXT_PUBLIC_LOGO_ALT) != null ? _a3 : "\uB85C\uACE0";
29
+ var _a4;
30
+ var API_BASE_URL = (_a4 = process.env.NEXT_PUBLIC_API_BASE_URL) != null ? _a4 : "";
29
31
 
30
32
  // util/text.util.ts
31
33
  var COMPANY_TOKEN = "%c";
@@ -148,16 +150,82 @@ function fixJosa(text) {
148
150
  );
149
151
  }
150
152
  var WITH_FINAL = /* @__PURE__ */ new Set(["\uC740", "\uC774", "\uC744", "\uACFC"]);
153
+
154
+ // util/axios.util.ts
155
+ import axios from "axios";
156
+ var apiClient = axios.create({
157
+ baseURL: API_BASE_URL,
158
+ withCredentials: true,
159
+ timeout: 15e3,
160
+ headers: { Accept: "application/json" }
161
+ });
162
+ var getAccessToken = null;
163
+ function setAccessTokenGetter(getter) {
164
+ getAccessToken = getter;
165
+ }
166
+ apiClient.interceptors.request.use((config) => {
167
+ const token = getAccessToken == null ? void 0 : getAccessToken();
168
+ if (token) config.headers.set("Authorization", `Bearer ${token}`);
169
+ return config;
170
+ });
171
+ var refreshHandler = null;
172
+ function setRefreshHandler(handler) {
173
+ refreshHandler = handler;
174
+ }
175
+ var refreshPromise = null;
176
+ function refreshAccessToken() {
177
+ if (!refreshHandler) return Promise.resolve(false);
178
+ if (!refreshPromise) {
179
+ refreshPromise = refreshHandler().catch(() => false).finally(() => {
180
+ refreshPromise = null;
181
+ });
182
+ }
183
+ return refreshPromise;
184
+ }
185
+ var ApiError = class extends Error {
186
+ constructor(message, status, code) {
187
+ super(message);
188
+ this.name = "ApiError";
189
+ this.status = status;
190
+ this.code = code;
191
+ }
192
+ };
193
+ var onUnauthorized = null;
194
+ function setUnauthorizedHandler(handler) {
195
+ onUnauthorized = handler;
196
+ }
197
+ apiClient.interceptors.response.use(
198
+ (response) => response,
199
+ (error) => __async(null, null, function* () {
200
+ var _a5, _b, _c;
201
+ const status = (_a5 = error.response) == null ? void 0 : _a5.status;
202
+ const originalRequest = error.config;
203
+ if (status === 401 && refreshHandler && originalRequest && !originalRequest._retry) {
204
+ originalRequest._retry = true;
205
+ const refreshed = yield refreshAccessToken();
206
+ if (refreshed) return apiClient(originalRequest);
207
+ }
208
+ if (status === 401) onUnauthorized == null ? void 0 : onUnauthorized();
209
+ const body = (_b = error.response) == null ? void 0 : _b.data;
210
+ return Promise.reject(new ApiError((_c = body == null ? void 0 : body.message) != null ? _c : error.message, status, body == null ? void 0 : body.code));
211
+ })
212
+ );
151
213
  export {
214
+ API_BASE_URL,
215
+ ApiError,
152
216
  COMPANY_NAME,
153
217
  LOGO_ALT,
154
218
  LOGO_SRC,
219
+ apiClient,
155
220
  filterAndSort,
156
221
  fixJosa,
157
222
  getFinalSound,
158
223
  hasFinalConsonant,
159
224
  josa,
160
225
  runFinalizers,
226
+ setAccessTokenGetter,
227
+ setRefreshHandler,
228
+ setUnauthorizedHandler,
161
229
  t,
162
230
  withJosa
163
231
  };
package/package.json CHANGED
@@ -1,49 +1,29 @@
1
1
  {
2
2
  "name": "@yeongseoksong/framework",
3
- "version": "1.6.1",
3
+ "version": "1.7.0",
4
4
  "files": [
5
5
  "dist"
6
6
  ],
7
7
  "exports": {
8
8
  "./ui": {
9
- "import": {
10
- "types": "./dist/ui/index.d.mts",
11
- "default": "./dist/ui/index.mjs"
12
- },
13
- "require": {
14
- "types": "./dist/ui/index.d.ts",
15
- "default": "./dist/ui/index.cjs"
16
- }
9
+ "types": "./dist/ui/index.d.mts",
10
+ "default": "./dist/ui/index.mjs"
17
11
  },
18
12
  "./store": {
19
- "import": {
20
- "types": "./dist/store/index.d.mts",
21
- "default": "./dist/store/index.mjs"
22
- },
23
- "require": {
24
- "types": "./dist/store/index.d.ts",
25
- "default": "./dist/store/index.cjs"
26
- }
13
+ "types": "./dist/store/index.d.mts",
14
+ "default": "./dist/store/index.mjs"
27
15
  },
28
16
  "./util": {
29
- "import": {
30
- "types": "./dist/util/index.d.mts",
31
- "default": "./dist/util/index.mjs"
32
- },
33
- "require": {
34
- "types": "./dist/util/index.d.ts",
35
- "default": "./dist/util/index.cjs"
36
- }
17
+ "types": "./dist/util/index.d.mts",
18
+ "default": "./dist/util/index.mjs"
37
19
  },
38
20
  "./types": {
39
- "import": {
40
- "types": "./dist/types/index.d.mts",
41
- "default": "./dist/types/index.mjs"
42
- },
43
- "require": {
44
- "types": "./dist/types/index.d.ts",
45
- "default": "./dist/types/index.cjs"
46
- }
21
+ "types": "./dist/types/index.d.mts",
22
+ "default": "./dist/types/index.mjs"
23
+ },
24
+ "./platform": {
25
+ "types": "./dist/platform/index.d.mts",
26
+ "default": "./dist/platform/index.mjs"
47
27
  }
48
28
  },
49
29
  "publishConfig": {
@@ -60,6 +40,7 @@
60
40
  "react": "19.2.4",
61
41
  "react-dom": "19.2.4",
62
42
  "@tabler/icons-react": "^3.44.0",
43
+ "axios": "^1.18.1",
63
44
  "zustand": "^5.0.14"
64
45
  },
65
46
  "devDependencies": {
@@ -1,393 +0,0 @@
1
- "use client";
2
- "use strict";
3
- var __defProp = Object.defineProperty;
4
- var __defProps = Object.defineProperties;
5
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
- var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
7
- var __getOwnPropNames = Object.getOwnPropertyNames;
8
- var __getOwnPropSymbols = Object.getOwnPropertySymbols;
9
- var __hasOwnProp = Object.prototype.hasOwnProperty;
10
- var __propIsEnum = Object.prototype.propertyIsEnumerable;
11
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
12
- var __spreadValues = (a, b) => {
13
- for (var prop in b || (b = {}))
14
- if (__hasOwnProp.call(b, prop))
15
- __defNormalProp(a, prop, b[prop]);
16
- if (__getOwnPropSymbols)
17
- for (var prop of __getOwnPropSymbols(b)) {
18
- if (__propIsEnum.call(b, prop))
19
- __defNormalProp(a, prop, b[prop]);
20
- }
21
- return a;
22
- };
23
- var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
24
- var __restKey = (key) => typeof key === "symbol" ? key : key + "";
25
- var __objRest = (source, exclude) => {
26
- var target = {};
27
- for (var prop in source)
28
- if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
29
- target[prop] = source[prop];
30
- if (source != null && __getOwnPropSymbols)
31
- for (var prop of __getOwnPropSymbols(source)) {
32
- if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
33
- target[prop] = source[prop];
34
- }
35
- return target;
36
- };
37
- var __export = (target, all) => {
38
- for (var name in all)
39
- __defProp(target, name, { get: all[name], enumerable: true });
40
- };
41
- var __copyProps = (to, from, except, desc) => {
42
- if (from && typeof from === "object" || typeof from === "function") {
43
- for (let key of __getOwnPropNames(from))
44
- if (!__hasOwnProp.call(to, key) && key !== except)
45
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
46
- }
47
- return to;
48
- };
49
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
50
- var __async = (__this, __arguments, generator) => {
51
- return new Promise((resolve, reject) => {
52
- var fulfilled = (value) => {
53
- try {
54
- step(generator.next(value));
55
- } catch (e) {
56
- reject(e);
57
- }
58
- };
59
- var rejected = (value) => {
60
- try {
61
- step(generator.throw(value));
62
- } catch (e) {
63
- reject(e);
64
- }
65
- };
66
- var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
67
- step((generator = generator.apply(__this, __arguments)).next());
68
- });
69
- };
70
-
71
- // store/index.ts
72
- var store_exports = {};
73
- __export(store_exports, {
74
- formRules: () => formRules,
75
- useAuthHydrated: () => useAuthHydrated,
76
- useAuthStore: () => useAuthStore,
77
- useFormStore: () => useFormStore,
78
- useNavStore: () => useNavStore,
79
- useSdForm: () => useSdForm,
80
- useUiStore: () => useUiStore
81
- });
82
- module.exports = __toCommonJS(store_exports);
83
-
84
- // store/auth.store.ts
85
- var import_react = require("react");
86
- var import_zustand = require("zustand");
87
- var import_middleware = require("zustand/middleware");
88
- var useAuthStore = (0, import_zustand.create)()(
89
- (0, import_middleware.persist)(
90
- (set) => ({
91
- user: null,
92
- isAuthenticated: false,
93
- hasHydrated: false,
94
- login: (user) => set({ user, isAuthenticated: true }),
95
- logout: () => set({ user: null, isAuthenticated: false }),
96
- setHasHydrated: (value) => set({ hasHydrated: value })
97
- }),
98
- {
99
- name: "sd-auth",
100
- storage: (0, import_middleware.createJSONStorage)(() => localStorage),
101
- partialize: (state) => ({ user: state.user, isAuthenticated: state.isAuthenticated }),
102
- skipHydration: true,
103
- onRehydrateStorage: () => (state) => state == null ? void 0 : state.setHasHydrated(true)
104
- }
105
- )
106
- );
107
- var rehydrateStarted = false;
108
- function useAuthHydrated() {
109
- const hasHydrated = useAuthStore((state) => state.hasHydrated);
110
- (0, import_react.useEffect)(() => {
111
- if (rehydrateStarted) return;
112
- rehydrateStarted = true;
113
- void useAuthStore.persist.rehydrate();
114
- }, []);
115
- return hasHydrated;
116
- }
117
-
118
- // store/ui.store.ts
119
- var import_zustand2 = require("zustand");
120
- var useUiStore = (0, import_zustand2.create)()((set) => ({
121
- globalLoading: false,
122
- setGlobalLoading: (value) => set({ globalLoading: value }),
123
- sideNavOpened: false,
124
- openSideNav: () => set({ sideNavOpened: true }),
125
- closeSideNav: () => set({ sideNavOpened: false }),
126
- toggleSideNav: () => set((state) => ({ sideNavOpened: !state.sideNavOpened }))
127
- }));
128
-
129
- // store/nav.store.ts
130
- var import_zustand3 = require("zustand");
131
- var useNavStore = (0, import_zustand3.create)()((set) => ({
132
- navItems: [],
133
- setNavItems: (navItems) => set({ navItems })
134
- }));
135
-
136
- // store/form.state.ts
137
- var import_react2 = require("react");
138
- var import_zustand4 = require("zustand");
139
-
140
- // ui/atom/Toast.tsx
141
- var import_notifications = require("@mantine/notifications");
142
- var import_icons_react = require("@tabler/icons-react");
143
- var import_jsx_runtime = require("react/jsx-runtime");
144
- var toastStyles = {
145
- /** 성공 — 저장·전송 완료 */
146
- Success: { color: "green", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icons_react.IconCheck, { size: 18 }), title: "\uC644\uB8CC" },
147
- /** 실패 — 요청 오류·검증 실패 */
148
- Error: { color: "red", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icons_react.IconX, { size: 18 }), title: "\uC624\uB958" },
149
- /** 주의 — 되돌릴 수 없는 동작 안내 등 */
150
- Warning: { color: "amber", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icons_react.IconAlertTriangle, { size: 18 }), title: "\uC8FC\uC758" },
151
- /** 안내 — 중립 정보 */
152
- Info: { color: "primary", icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icons_react.IconInfoCircle, { size: 18 }), title: "\uC548\uB0B4" },
153
- /** 진행 중 — 스피너 + 자동 닫힘 없음. Update로 결과 변형으로 바꾼다. */
154
- Loading: { loading: true, autoClose: false, withCloseButton: false, title: "\uCC98\uB9AC \uC911" }
155
- };
156
- function createToast(defaults) {
157
- return function SdToast2(message, options) {
158
- return import_notifications.notifications.show(__spreadValues(__spreadProps(__spreadValues({}, defaults), { message }), options));
159
- };
160
- }
161
- var SdToast = {
162
- Success: createToast(toastStyles.Success),
163
- Error: createToast(toastStyles.Error),
164
- Warning: createToast(toastStyles.Warning),
165
- Info: createToast(toastStyles.Info),
166
- Loading: createToast(toastStyles.Loading),
167
- /**
168
- * 이미 떠 있는 알림을 다른 변형으로 교체한다 — `Loading` → `Success`/`Error` 전환용.
169
- * `loading: false`·`autoClose`를 먼저 깔아 두므로 스피너가 그대로 남지 않는다.
170
- */
171
- Update: (id, variant, message, options) => import_notifications.notifications.update(__spreadValues(__spreadProps(__spreadValues({
172
- id,
173
- loading: false,
174
- autoClose: 4e3,
175
- withCloseButton: true
176
- }, toastStyles[variant]), {
177
- message
178
- }), options)),
179
- /** 특정 알림 닫기 */
180
- Hide: (id) => import_notifications.notifications.hide(id),
181
- /** 떠 있는 알림 전부 닫기 */
182
- Clean: () => import_notifications.notifications.clean()
183
- };
184
- var SdToastSuccess = SdToast.Success;
185
- var SdToastError = SdToast.Error;
186
- var SdToastWarning = SdToast.Warning;
187
- var SdToastInfo = SdToast.Info;
188
- var SdToastLoading = SdToast.Loading;
189
- var SdToastUpdate = SdToast.Update;
190
- var SdToastHide = SdToast.Hide;
191
- var SdToastClean = SdToast.Clean;
192
-
193
- // util/finalize.util.ts
194
- function runFinalizers(finalize, label = "finalize") {
195
- return __async(this, null, function* () {
196
- if (!finalize) return;
197
- const list = Array.isArray(finalize) ? finalize : [finalize];
198
- for (const fn of list) {
199
- try {
200
- yield fn();
201
- } catch (caught) {
202
- console.error(`[${label}] finalize\uC5D0\uC11C \uC608\uC678\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.`, caught);
203
- }
204
- }
205
- });
206
- }
207
-
208
- // store/form.state.ts
209
- var EMPTY_ERRORS = {};
210
- function readChangePayload(payload) {
211
- if (payload && typeof payload === "object" && "currentTarget" in payload) {
212
- const target = payload.currentTarget;
213
- if (target && typeof target === "object" && "value" in target) {
214
- return target.type === "checkbox" ? target.checked : target.value;
215
- }
216
- }
217
- return payload;
218
- }
219
- function blankEntry(values) {
220
- return { values, errors: {}, submitting: false, error: null };
221
- }
222
- function patch(state, formId, next) {
223
- const current = state.forms[formId];
224
- if (!current) return state;
225
- return __spreadProps(__spreadValues({}, state), { forms: __spreadProps(__spreadValues({}, state.forms), { [formId]: __spreadValues(__spreadValues({}, current), next) }) });
226
- }
227
- var useFormStore = (0, import_zustand4.create)()((set) => ({
228
- forms: {},
229
- ensureForm: (formId, initialValues) => set(
230
- (state) => state.forms[formId] ? state : __spreadProps(__spreadValues({}, state), { forms: __spreadProps(__spreadValues({}, state.forms), { [formId]: blankEntry(initialValues) }) })
231
- ),
232
- setValue: (formId, name, value) => set((state) => {
233
- const current = state.forms[formId];
234
- if (!current) return state;
235
- const _a = current.errors, { [name]: _removed } = _a, restErrors = __objRest(_a, [__restKey(name)]);
236
- return patch(state, formId, {
237
- values: __spreadProps(__spreadValues({}, current.values), { [name]: value }),
238
- errors: restErrors
239
- });
240
- }),
241
- setValues: (formId, values) => set((state) => {
242
- const current = state.forms[formId];
243
- if (!current) return state;
244
- return patch(state, formId, { values: __spreadValues(__spreadValues({}, current.values), values) });
245
- }),
246
- setErrors: (formId, errors) => set((state) => patch(state, formId, { errors })),
247
- setSubmitting: (formId, submitting) => set((state) => patch(state, formId, { submitting })),
248
- setError: (formId, error) => set((state) => patch(state, formId, { error })),
249
- resetForm: (formId, initialValues) => set((state) => __spreadProps(__spreadValues({}, state), { forms: __spreadProps(__spreadValues({}, state.forms), { [formId]: blankEntry(initialValues) }) })),
250
- removeForm: (formId) => set((state) => {
251
- const _a = state.forms, { [formId]: _removed } = _a, rest = __objRest(_a, [__restKey(formId)]);
252
- return __spreadProps(__spreadValues({}, state), { forms: rest });
253
- })
254
- }));
255
- function useSdForm({
256
- id,
257
- initialValues,
258
- rules,
259
- onSubmit,
260
- successMessage,
261
- errorMessage,
262
- resetOnSuccess,
263
- onSuccess,
264
- onError,
265
- finalize
266
- }) {
267
- var _a, _b, _c, _d;
268
- const entry = useFormStore((state) => state.forms[id]);
269
- const store = useFormStore;
270
- (0, import_react2.useEffect)(() => {
271
- store.getState().ensureForm(id, initialValues);
272
- }, [id]);
273
- const values = (_a = entry == null ? void 0 : entry.values) != null ? _a : initialValues;
274
- const errors = (_b = entry == null ? void 0 : entry.errors) != null ? _b : EMPTY_ERRORS;
275
- const submitting = (_c = entry == null ? void 0 : entry.submitting) != null ? _c : false;
276
- const error = (_d = entry == null ? void 0 : entry.error) != null ? _d : null;
277
- const setValue = (0, import_react2.useCallback)(
278
- (name, value) => store.getState().setValue(id, String(name), value),
279
- [id, store]
280
- );
281
- const setValues = (0, import_react2.useCallback)(
282
- (next) => store.getState().setValues(id, next),
283
- [id, store]
284
- );
285
- const reset = (0, import_react2.useCallback)(
286
- () => store.getState().resetForm(id, initialValues),
287
- // initialValues 참조가 바뀌어도 reset의 의미는 같다.
288
- // eslint-disable-next-line react-hooks/exhaustive-deps
289
- [id, store]
290
- );
291
- const getInputProps = (0, import_react2.useCallback)(
292
- (name, options) => {
293
- const key = String(name);
294
- const fieldError = errors[name];
295
- const current = values[name];
296
- const onChange = (payload) => store.getState().setValue(id, key, readChangePayload(payload));
297
- if ((options == null ? void 0 : options.type) === "checkbox") {
298
- return { checked: Boolean(current), error: fieldError, onChange };
299
- }
300
- return {
301
- // undefined일 때만 ''로 메운다 — null은 그대로 둬야 Select·Date가 "값 없음"으로 읽는다.
302
- value: current === void 0 ? "" : current,
303
- error: fieldError,
304
- onChange
305
- };
306
- },
307
- [errors, id, store, values]
308
- );
309
- const handleSubmit = (0, import_react2.useCallback)(
310
- (event) => __async(null, null, function* () {
311
- var _a2, _b2;
312
- event == null ? void 0 : event.preventDefault();
313
- const state = store.getState();
314
- const current = state.forms[id];
315
- if (current == null ? void 0 : current.submitting) return;
316
- const currentValues = (_a2 = current == null ? void 0 : current.values) != null ? _a2 : initialValues;
317
- if (rules) {
318
- const found = {};
319
- for (const [name, rule] of Object.entries(rules)) {
320
- const message = rule == null ? void 0 : rule(currentValues[name], currentValues);
321
- if (message) found[name] = message;
322
- }
323
- if (Object.keys(found).length > 0) {
324
- state.setErrors(id, found);
325
- return;
326
- }
327
- }
328
- state.setErrors(id, {});
329
- state.setError(id, null);
330
- state.setSubmitting(id, true);
331
- try {
332
- yield onSubmit(currentValues);
333
- if (successMessage !== false) SdToast.Success(successMessage != null ? successMessage : "\uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4.");
334
- if (resetOnSuccess) state.resetForm(id, initialValues);
335
- onSuccess == null ? void 0 : onSuccess(currentValues);
336
- } catch (caught) {
337
- const message = (_b2 = errorMessage == null ? void 0 : errorMessage(caught)) != null ? _b2 : caught instanceof Error ? caught.message : "\uC694\uCCAD\uC744 \uCC98\uB9AC\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.";
338
- state.setError(id, message);
339
- SdToast.Error(message);
340
- onError == null ? void 0 : onError(caught);
341
- } finally {
342
- store.getState().setSubmitting(id, false);
343
- yield runFinalizers(finalize, "useSdForm");
344
- }
345
- }),
346
- // eslint-disable-next-line react-hooks/exhaustive-deps
347
- [
348
- id,
349
- store,
350
- rules,
351
- onSubmit,
352
- successMessage,
353
- errorMessage,
354
- resetOnSuccess,
355
- onSuccess,
356
- onError,
357
- finalize
358
- ]
359
- );
360
- return (0, import_react2.useMemo)(
361
- () => ({
362
- values,
363
- errors,
364
- submitting,
365
- error,
366
- setValue,
367
- setValues,
368
- reset,
369
- getInputProps,
370
- onSubmit: handleSubmit
371
- }),
372
- [values, errors, submitting, error, setValue, setValues, reset, getInputProps, handleSubmit]
373
- );
374
- }
375
- var formRules = {
376
- required: (message = "\uD544\uC218 \uC785\uB825 \uD56D\uBAA9\uC785\uB2C8\uB2E4.") => (value) => value === void 0 || value === null || String(value).trim() === "" ? message : null,
377
- email: (message = "\uC774\uBA54\uC77C \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.") => (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(value != null ? value : "")) ? null : message,
378
- minLength: (length, message) => (value) => String(value != null ? value : "").length >= length ? null : message != null ? message : `${length}\uC790 \uC774\uC0C1 \uC785\uB825\uD558\uC138\uC694.`,
379
- /** 체크박스 동의 — 반드시 true여야 한다. */
380
- checked: (message = "\uB3D9\uC758\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.") => (value) => value === true ? null : message,
381
- /** 다른 필드와 같은 값인지 — 비밀번호 확인 등. */
382
- sameAs: (other, message = "\uAC12\uC774 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.") => (value, values) => value === values[other] ? null : message
383
- };
384
- // Annotate the CommonJS export names for ESM import in node:
385
- 0 && (module.exports = {
386
- formRules,
387
- useAuthHydrated,
388
- useAuthStore,
389
- useFormStore,
390
- useNavStore,
391
- useSdForm,
392
- useUiStore
393
- });