@rchemist/listgrid 0.2.12 → 0.2.14
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/listgrid/components/fields/InlineMapField.d.ts +15 -0
- package/dist/listgrid/components/fields/InlineMapField.js +35 -0
- package/dist/listgrid/components/form/ui/ViewEntityFormButtons.js +16 -4
- package/dist/listgrid/config/EntityFormButton.d.ts +8 -1
- package/dist/listgrid/config/RuntimeConfig.js +1 -1
- package/dist/listgrid/index.d.ts +1 -1
- package/package.json +1 -1
|
@@ -9,6 +9,13 @@ import { RenderType } from '../../config/Config';
|
|
|
9
9
|
interface InlineMapFieldProps extends FormFieldProps {
|
|
10
10
|
config?: InlineMapConfig | undefined;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* InlineMap 의 pendingRef.value 가 비어있는지 판정.
|
|
14
|
+
* - undefined / null → blank
|
|
15
|
+
* - Map / 객체 → entries 가 0개면 blank
|
|
16
|
+
* - 배열 (KeyValue[]) → 길이 0 이면 blank
|
|
17
|
+
*/
|
|
18
|
+
export declare function isInlineMapValueBlank(value: unknown): boolean;
|
|
12
19
|
export declare class InlineMapField extends FormField<InlineMapField> {
|
|
13
20
|
config?: InlineMapConfig | undefined;
|
|
14
21
|
pendingRef: {
|
|
@@ -17,6 +24,14 @@ export declare class InlineMapField extends FormField<InlineMapField> {
|
|
|
17
24
|
constructor(name: string, order: number, config?: InlineMapConfig);
|
|
18
25
|
isDirty(): boolean;
|
|
19
26
|
getSaveValue(entityForm: EntityForm, renderType?: RenderType): Promise<any>;
|
|
27
|
+
/**
|
|
28
|
+
* 사용자가 InlineMap UI 에서 입력한 값은 `pendingRef.current.value` 에 누적되며,
|
|
29
|
+
* `getSaveValue`/`isDirty` 시점에서야 form value 로 반영됩니다.
|
|
30
|
+
* 따라서 검증 단계의 `isBlank` 도 pendingRef 가 modified 된 경우에는
|
|
31
|
+
* pendingRef 값을 우선해서 봐야 합니다. 그렇지 않으면 사용자가 값을 입력했음에도
|
|
32
|
+
* "필수 값입니다" 검증에 막혀 저장이 차단됩니다.
|
|
33
|
+
*/
|
|
34
|
+
isBlank(renderType?: RenderType): Promise<boolean>;
|
|
20
35
|
/**
|
|
21
36
|
* InlineMapField 핵심 렌더링 로직
|
|
22
37
|
*/
|
|
@@ -2,6 +2,27 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { FormField } from './abstract';
|
|
3
3
|
import { InlineMap } from '../../ui';
|
|
4
4
|
import { getInputRendererParameters } from '../helper/FieldRendererHelper';
|
|
5
|
+
/**
|
|
6
|
+
* InlineMap 의 pendingRef.value 가 비어있는지 판정.
|
|
7
|
+
* - undefined / null → blank
|
|
8
|
+
* - Map / 객체 → entries 가 0개면 blank
|
|
9
|
+
* - 배열 (KeyValue[]) → 길이 0 이면 blank
|
|
10
|
+
*/
|
|
11
|
+
export function isInlineMapValueBlank(value) {
|
|
12
|
+
if (value === undefined || value === null) {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
if (Array.isArray(value)) {
|
|
16
|
+
return value.length === 0;
|
|
17
|
+
}
|
|
18
|
+
if (value instanceof Map) {
|
|
19
|
+
return value.size === 0;
|
|
20
|
+
}
|
|
21
|
+
if (typeof value === 'object') {
|
|
22
|
+
return Object.keys(value).length === 0;
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
5
26
|
export class InlineMapField extends FormField {
|
|
6
27
|
constructor(name, order, config) {
|
|
7
28
|
super(name, order, 'inlineMap');
|
|
@@ -20,6 +41,20 @@ export class InlineMapField extends FormField {
|
|
|
20
41
|
}
|
|
21
42
|
return super.getSaveValue(entityForm, renderType);
|
|
22
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* 사용자가 InlineMap UI 에서 입력한 값은 `pendingRef.current.value` 에 누적되며,
|
|
46
|
+
* `getSaveValue`/`isDirty` 시점에서야 form value 로 반영됩니다.
|
|
47
|
+
* 따라서 검증 단계의 `isBlank` 도 pendingRef 가 modified 된 경우에는
|
|
48
|
+
* pendingRef 값을 우선해서 봐야 합니다. 그렇지 않으면 사용자가 값을 입력했음에도
|
|
49
|
+
* "필수 값입니다" 검증에 막혀 저장이 차단됩니다.
|
|
50
|
+
*/
|
|
51
|
+
async isBlank(renderType = 'create') {
|
|
52
|
+
if (this.pendingRef.current.modified) {
|
|
53
|
+
const pendingValue = this.pendingRef.current.value;
|
|
54
|
+
return isInlineMapValueBlank(pendingValue);
|
|
55
|
+
}
|
|
56
|
+
return super.isBlank(renderType);
|
|
57
|
+
}
|
|
23
58
|
/**
|
|
24
59
|
* InlineMapField 핵심 렌더링 로직
|
|
25
60
|
*/
|
|
@@ -27,7 +27,7 @@ export function getOverwriteButton(buttons, id) {
|
|
|
27
27
|
* @param props
|
|
28
28
|
*/
|
|
29
29
|
export async function getEntityFormButtons(props) {
|
|
30
|
-
const { router, pathname, entityForm, setErrors, setNotifications, postSave, useCreateStep, showModal, closeModal, closeTopModal, getModalData, updateModalData, } = props;
|
|
30
|
+
const { router, pathname, entityForm, setEntityForm, setErrors, setNotifications, postSave, useCreateStep, showModal, closeModal, closeTopModal, getModalData, updateModalData, } = props;
|
|
31
31
|
if (!entityForm)
|
|
32
32
|
return [];
|
|
33
33
|
const readonly = isTrue(props.readonly) || isTrue(entityForm?.readonly);
|
|
@@ -146,6 +146,7 @@ export async function getEntityFormButtons(props) {
|
|
|
146
146
|
pathname,
|
|
147
147
|
setErrors,
|
|
148
148
|
setNotifications,
|
|
149
|
+
...(setEntityForm !== undefined ? { setEntityForm } : {}),
|
|
149
150
|
...(useCreateStep && {
|
|
150
151
|
step: {
|
|
151
152
|
useCreateStep: true,
|
|
@@ -172,9 +173,20 @@ export async function getEntityFormButtons(props) {
|
|
|
172
173
|
if (button.onClick !== undefined) {
|
|
173
174
|
const form = await button.onClick(buttonProps);
|
|
174
175
|
if (form.errors && form.errors.length > 0) {
|
|
175
|
-
//
|
|
176
|
-
|
|
177
|
-
|
|
176
|
+
// Propagate the returned form so ViewEntityFormErrors picks
|
|
177
|
+
// up field-level errors (entityForm.getErrorMap()), matching
|
|
178
|
+
// the behaviour of the built-in SaveButton.
|
|
179
|
+
setEntityForm?.(form);
|
|
180
|
+
// entityForm.getErrorMap() 이 채워지면 ShowNotifications 의 띠는
|
|
181
|
+
// 표시되지 않으므로(필드별로 표시), 여기에서는 매핑 가능한 필드가
|
|
182
|
+
// 하나도 없을 때만 string 메시지를 fallback 으로 노출한다.
|
|
183
|
+
if (form.getErrorMap().size === 0) {
|
|
184
|
+
const errorMessages = form.errors.flatMap((error) => error.errors);
|
|
185
|
+
setErrors(errorMessages);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
setErrors([]);
|
|
189
|
+
}
|
|
178
190
|
return;
|
|
179
191
|
}
|
|
180
192
|
else {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EntityForm } from '../config/EntityForm';
|
|
2
|
-
import { ReactNode } from 'react';
|
|
2
|
+
import { Dispatch, ReactNode, SetStateAction } from 'react';
|
|
3
3
|
import { LabelType } from '../config/Config';
|
|
4
4
|
import { ModalOptions } from '../store';
|
|
5
5
|
import type { RouterApi } from '../router/types';
|
|
@@ -15,6 +15,13 @@ export interface EntityFormButtonProps {
|
|
|
15
15
|
pathname: string | null;
|
|
16
16
|
setErrors: (errors: string[]) => void;
|
|
17
17
|
setNotifications: (notifications: string[]) => void;
|
|
18
|
+
/**
|
|
19
|
+
* Setter for the host EntityForm state. When a custom button's onClick
|
|
20
|
+
* mutates the form (e.g. via `entityForm.save()` returning a new instance
|
|
21
|
+
* with field errors), call this to propagate the new instance to
|
|
22
|
+
* ViewEntityForm so that ViewEntityFormErrors can render field-level errors.
|
|
23
|
+
*/
|
|
24
|
+
setEntityForm?: Dispatch<SetStateAction<EntityForm | undefined>>;
|
|
18
25
|
step?: EntityFormButtonStepInfo;
|
|
19
26
|
showModal?: (options: ModalOptions) => string;
|
|
20
27
|
closeModal?: (id: string) => Promise<void>;
|
|
@@ -17,7 +17,7 @@ const DEFAULT_ENDPOINTS = {
|
|
|
17
17
|
customOptionByAliases: '/option/by-aliases',
|
|
18
18
|
assetUpload: '/asset/upload-file',
|
|
19
19
|
staticResourcePrefix: '/static-resource/',
|
|
20
|
-
smsSenderList: '/
|
|
20
|
+
smsSenderList: '/sms-sender/list',
|
|
21
21
|
smsNotificationSend: '/notification/send',
|
|
22
22
|
revisionApi: '/revision',
|
|
23
23
|
noImageFallback: '/assets/images/no-image.png',
|
package/dist/listgrid/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export type { ModalOptions } from './store';
|
|
|
14
14
|
export { registerSmsHistoryField, createSmsHistoryField, registerPhoneNumberSmsHistoryInject, getPhoneNumberSmsHistoryInjectConfig, } from './extensions/FieldExtensions';
|
|
15
15
|
export type { SmsHistoryFieldConstructor, PhoneNumberSmsHistoryInjectConfig, } from './extensions/FieldExtensions';
|
|
16
16
|
export { configureRuntime, getRuntimeConfig, getEndpoint, getPermission, } from './config/RuntimeConfig';
|
|
17
|
-
export type { RuntimeConfig, ListGridEndpoints, ListGridPermissions
|
|
17
|
+
export type { RuntimeConfig, ListGridEndpoints, ListGridPermissions } from './config/RuntimeConfig';
|
|
18
18
|
export { configureTranslator, getTranslation } from './utils/i18n';
|
|
19
19
|
export type { Translator, TranslatorI18n, TranslatorFactory } from './utils/i18n';
|
|
20
20
|
export { registerMenuPermissionChecker, checkAdminMenuPermission, DEFAULT_MENU_ALIAS, } from './menu';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rchemist/listgrid",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.14",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Framework-free React CRUD UI engine — primitive-based design system, data-attr theming, and a full list/form renderer for RCM-framework-style entity backends.",
|
|
6
6
|
"keywords": [
|