@wipco/sdui 1.5.0 → 1.6.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/dist/{DatePicker-C_TT_SOw.d.ts → DatePicker-DX9juElE.d.ts} +10 -1
- package/dist/{chunk-GZBY7I4Y.js → chunk-2KQL6DYG.js} +3 -3
- package/dist/{chunk-RYBBPFI7.js → chunk-2QXA52DG.js} +4 -4
- package/dist/{chunk-DMLDKW7K.js → chunk-37BNG4DT.js} +1 -1
- package/dist/{chunk-5WW4MBRO.js → chunk-3JTBOABA.js} +6 -2
- package/dist/{chunk-UV6HHFEW.js → chunk-4OABUPIU.js} +129 -68
- package/dist/{chunk-ZORQPB6B.js → chunk-53KS4E5N.js} +318 -359
- package/dist/{chunk-I5SD7XRA.js → chunk-6TI4WF5Y.js} +3 -3
- package/dist/{chunk-44TDRCKR.js → chunk-6XI2VF2O.js} +5 -5
- package/dist/{chunk-HBIIEYKW.js → chunk-7C54CKFZ.js} +4 -4
- package/dist/{chunk-IZ3JA2BR.js → chunk-7JP52QQO.js} +3 -3
- package/dist/{chunk-I45EUZC6.js → chunk-AY4GM6KQ.js} +4 -2
- package/dist/{chunk-SAPBDJJQ.js → chunk-AZSLSAJB.js} +2 -2
- package/dist/chunk-BITS7UJY.js +333 -0
- package/dist/{chunk-RNOUXUBO.js → chunk-BLFJMMBW.js} +306 -57
- package/dist/{chunk-YOZN35HD.js → chunk-CEVZAFPO.js} +3 -3
- package/dist/chunk-CI5OZ2QT.js +106 -0
- package/dist/{chunk-FNMS7OB7.js → chunk-CSE3PZOZ.js} +5 -5
- package/dist/chunk-CSZCWBOE.js +256 -0
- package/dist/{chunk-JIV4YHCZ.js → chunk-EJIWOD7W.js} +1 -1
- package/dist/{chunk-JZXU22PE.js → chunk-FEKKOBPX.js} +208 -117
- package/dist/chunk-GSM664TX.js +38 -0
- package/dist/{chunk-JYAR7D5U.js → chunk-HRHKQOOF.js} +2 -2
- package/dist/{chunk-CXW6KT6X.js → chunk-HXLQSO66.js} +3 -3
- package/dist/{chunk-O4IISNE7.js → chunk-IB2ZPUTF.js} +51 -12
- package/dist/{chunk-MPT7WJG2.js → chunk-IQXLNICY.js} +5 -5
- package/dist/{chunk-OQ52XMGG.js → chunk-K6RE23XX.js} +4 -4
- package/dist/{chunk-VY7MVBSE.js → chunk-KZ6U6FKB.js} +2 -2
- package/dist/{chunk-WPSK2CB5.js → chunk-LIZTK3HO.js} +16 -5
- package/dist/{chunk-Q4SWUINA.js → chunk-LKQJJSVJ.js} +1 -1
- package/dist/{chunk-E6ISNQK3.js → chunk-M6H3SHIS.js} +31 -8
- package/dist/chunk-MN4RZ2WP.js +380 -0
- package/dist/{chunk-GTGS2QRL.js → chunk-NA232CBX.js} +3 -3
- package/dist/{chunk-ZFNFPXPR.js → chunk-NAIWQQPW.js} +2 -2
- package/dist/{chunk-YVTV4ZV6.js → chunk-NWYYNE3Q.js} +1 -1
- package/dist/{chunk-DKVSM5HX.js → chunk-OUEJWC2Z.js} +6 -6
- package/dist/{chunk-DOL57OOP.js → chunk-OYDJHQNZ.js} +3 -3
- package/dist/{chunk-YWUDBLO6.js → chunk-QVS6K4FP.js} +2 -2
- package/dist/{chunk-N5RKM6IC.js → chunk-RU5U2YMT.js} +4 -4
- package/dist/{chunk-A6LAIGUS.js → chunk-V7BWMOHO.js} +7 -4
- package/dist/{chunk-2HLXCKK7.js → chunk-VPOOIEKT.js} +2 -2
- package/dist/{chunk-2YP5N4VV.js → chunk-VZOQRQAR.js} +1 -1
- package/dist/{chunk-GNA6SX2H.js → chunk-W4VA76QO.js} +2 -2
- package/dist/{chunk-BEM2YS7U.js → chunk-WEG3V7ZB.js} +3 -3
- package/dist/{chunk-XGFDY43I.js → chunk-WTIYILY3.js} +88 -18
- package/dist/{chunk-RWSH2MBU.js → chunk-X2NIRVLN.js} +73 -19
- package/dist/{chunk-AXGF4M57.js → chunk-XAXROQKB.js} +1 -1
- package/dist/{chunk-NIJUVTEW.js → chunk-XG22XKKD.js} +3 -3
- package/dist/{chunk-A5XQOEVH.js → chunk-YN7X7BEO.js} +4 -4
- package/dist/{chunk-KO6OEY3G.js → chunk-YNEEJCSZ.js} +3 -13
- package/dist/{chunk-7P7N43JV.js → chunk-YZTTFEL4.js} +2 -2
- package/dist/index.d.ts +418 -90
- package/dist/index.js +997 -123
- package/dist/{layout-C3nRXMwb.d.ts → layout-DmRikwZb.d.ts} +5 -4
- package/dist/meta.d.ts +1 -1
- package/dist/meta.js +236 -22
- package/dist/rich/ExcelUploadCard.d.ts +1 -1
- package/dist/rich/ExcelUploadCard.js +5 -4
- package/dist/rich.d.ts +21 -3
- package/dist/rich.js +277 -35
- package/dist/styles.css +102 -4
- package/dist/{types-COjGKgmd.d.ts → types-CYfIthux.d.ts} +57 -1
- package/dist/widgets/ApprovalLineEditor.d.ts +1 -1
- package/dist/widgets/ApprovalLineEditor.js +9 -8
- package/dist/widgets/ApprovalOverridesEditor.d.ts +1 -1
- package/dist/widgets/ApprovalOverridesEditor.js +11 -10
- package/dist/widgets/ApprovalStepper.d.ts +29 -4
- package/dist/widgets/ApprovalStepper.js +3 -3
- package/dist/widgets/AutocompleteField.d.ts +1 -1
- package/dist/widgets/AutocompleteField.js +9 -8
- package/dist/widgets/ColumnBoard.d.ts +1 -1
- package/dist/widgets/ColumnBoard.js +3 -3
- package/dist/widgets/DiffView.d.ts +1 -1
- package/dist/widgets/DiffView.js +3 -3
- package/dist/widgets/Disclosure.d.ts +1 -1
- package/dist/widgets/Disclosure.js +3 -3
- package/dist/widgets/EventCalendar.d.ts +5 -1
- package/dist/widgets/EventCalendar.js +5 -3
- package/dist/widgets/FileDropzone.d.ts +1 -1
- package/dist/widgets/FileDropzone.js +9 -7
- package/dist/widgets/ImageLightbox.d.ts +1 -1
- package/dist/widgets/ImageLightbox.js +7 -5
- package/dist/widgets/InlineEdit.d.ts +1 -1
- package/dist/widgets/InlineEdit.js +3 -3
- package/dist/widgets/JsonField.d.ts +1 -1
- package/dist/widgets/JsonField.js +3 -3
- package/dist/widgets/LiveClock.d.ts +1 -1
- package/dist/widgets/LiveClock.js +2 -2
- package/dist/widgets/OptionSchemaEditor.d.ts +1 -1
- package/dist/widgets/OptionSchemaEditor.js +4 -4
- package/dist/widgets/OptionValueFields.d.ts +1 -1
- package/dist/widgets/OptionValueFields.js +4 -4
- package/dist/widgets/OrgChart.d.ts +1 -1
- package/dist/widgets/OrgChart.js +4 -4
- package/dist/widgets/OrgChartBoard.d.ts +15 -33
- package/dist/widgets/OrgChartBoard.js +10 -8
- package/dist/widgets/PeriodFilter.d.ts +1 -1
- package/dist/widgets/PeriodFilter.js +6 -5
- package/dist/widgets/PermissionMatrixEditor.d.ts +34 -2
- package/dist/widgets/PermissionMatrixEditor.js +6 -4
- package/dist/widgets/PositionSelect.d.ts +1 -1
- package/dist/widgets/PositionSelect.js +5 -4
- package/dist/widgets/PrintSheet.d.ts +15 -2
- package/dist/widgets/PrintSheet.js +3 -3
- package/dist/widgets/ProfileEditDiffCard.d.ts +1 -1
- package/dist/widgets/ProfileEditDiffCard.js +5 -4
- package/dist/widgets/QRDisplay.d.ts +1 -1
- package/dist/widgets/QRDisplay.js +5 -4
- package/dist/widgets/RankBoard.d.ts +1 -1
- package/dist/widgets/RankBoard.js +8 -6
- package/dist/widgets/RecordSheet.d.ts +1 -1
- package/dist/widgets/RecordSheet.js +2 -2
- package/dist/widgets/RoleMemberPicker.d.ts +1 -1
- package/dist/widgets/RoleMemberPicker.js +6 -5
- package/dist/widgets/SearchableMultiPicker.d.ts +1 -1
- package/dist/widgets/SearchableMultiPicker.js +7 -6
- package/dist/widgets/SecureScreen.d.ts +1 -1
- package/dist/widgets/SecureScreen.js +3 -3
- package/dist/widgets/StackedMeter.d.ts +1 -1
- package/dist/widgets/StackedMeter.js +2 -2
- package/dist/widgets/StagePeriodEditor.d.ts +1 -1
- package/dist/widgets/StagePeriodEditor.js +6 -5
- package/dist/widgets/Strip.d.ts +1 -1
- package/dist/widgets/Strip.js +2 -2
- package/dist/widgets/SummaryTable.d.ts +1 -1
- package/dist/widgets/SummaryTable.js +2 -2
- package/dist/widgets/TagInput.d.ts +1 -1
- package/dist/widgets/TagInput.js +3 -3
- package/dist/widgets/TagMultiSelect.d.ts +1 -1
- package/dist/widgets/TagMultiSelect.js +5 -4
- package/dist/widgets/Timeline.d.ts +1 -1
- package/dist/widgets/Timeline.js +4 -4
- package/dist/widgets/TreePicker.d.ts +1 -1
- package/dist/widgets/TreePicker.js +8 -7
- package/dist/widgets/TreeView.d.ts +1 -1
- package/dist/widgets/TreeView.js +7 -6
- package/dist/widgets/index.d.ts +3 -3
- package/dist/widgets/index.js +84 -83
- package/package.json +1 -1
- package/dist/chunk-LFEJLOFN.js +0 -352
- package/dist/chunk-MMIGOPO5.js +0 -111
- package/dist/chunk-TFGNKFL2.js +0 -39
- package/dist/chunk-XQPTSEJC.js +0 -135
package/dist/index.js
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
|
-
export { loadOptionalModule } from './chunk-
|
|
2
|
-
import { useSduiDispatch, SduiDialog, useSduiCan, SduiDispatchContext, SduiPermissionProvider } from './chunk-
|
|
3
|
-
export { SduiDialog, SduiPermissionProvider, useSduiCan, useSduiDispatch } from './chunk-
|
|
4
|
-
import { Text, Spacer, Show, Section, Row, PageHead, KeyValueList, Grid, Divider, Col, Actions } from './chunk-
|
|
5
|
-
export { Actions, Col, Divider, Grid, KeyValueList, PageHead, Row, Section, Show, Spacer, Text } from './chunk-
|
|
6
|
-
import { ImageViewer } from './chunk-
|
|
7
|
-
export { ImageViewer } from './chunk-
|
|
8
|
-
import { normalizeSearchFields, searchValuesFromParams, splitMultiValue, includesLooseSearch, cascadingValuePaths, SearchToolbar, searchFieldKeys, Popover } from './chunk-
|
|
9
|
-
export { SearchToolbar, cascadingValuePaths, cleanSearchValues, includesLooseSearch, joinMultiValue, normalizeSearchText, searchFieldKeys, searchValuesFromParams, splitMultiValue } from './chunk-
|
|
10
|
-
import { parseYmd, YMD_RE, TimePicker, DateRangePicker, DatePicker } from './chunk-
|
|
11
|
-
export { DatePicker, DateRangePicker, TimePicker } from './chunk-
|
|
1
|
+
export { loadOptionalModule } from './chunk-NWYYNE3Q.js';
|
|
2
|
+
import { useSduiDispatch, SduiDialog, useSduiCan, useSduiRuntime, SduiDirtyRegistryContext, SduiLeaveRegistryContext, SduiFormScopeContext, SduiDispatchContext, SduiRuntimeContext, SduiPermissionProvider } from './chunk-CI5OZ2QT.js';
|
|
3
|
+
export { SduiDialog, SduiPermissionProvider, useSduiCan, useSduiDispatch, useSduiRuntime } from './chunk-CI5OZ2QT.js';
|
|
4
|
+
import { Text, Spacer, Show, Section, Row, PageHead, KeyValueList, Grid, Divider, Col, Actions } from './chunk-LIZTK3HO.js';
|
|
5
|
+
export { Actions, Col, Divider, Grid, KeyValueList, PageHead, Row, Section, Show, Spacer, Text } from './chunk-LIZTK3HO.js';
|
|
6
|
+
import { ImageViewer } from './chunk-AZSLSAJB.js';
|
|
7
|
+
export { ImageViewer } from './chunk-AZSLSAJB.js';
|
|
8
|
+
import { normalizeSearchFields, searchValuesFromParams, splitMultiValue, includesLooseSearch, cascadingValuePaths, SearchToolbar, searchFieldKeys, Popover } from './chunk-4OABUPIU.js';
|
|
9
|
+
export { SearchToolbar, cascadingValuePaths, cleanSearchValues, includesLooseSearch, joinMultiValue, normalizeSearchText, searchFieldKeys, searchValuesFromParams, splitMultiValue } from './chunk-4OABUPIU.js';
|
|
10
|
+
import { parseYmd, YMD_RE, TimePicker, DateRangePicker, DatePicker } from './chunk-X2NIRVLN.js';
|
|
11
|
+
export { DatePicker, DateRangePicker, TimePicker } from './chunk-X2NIRVLN.js';
|
|
12
|
+
import { OverlayContainerContext, OverlayLayer, useAnyModalOpen } from './chunk-BITS7UJY.js';
|
|
13
|
+
export { OverlayContainerContext, OverlayLayer, getOverlayRoot, useAnyModalOpen, useOverlayLayer } from './chunk-BITS7UJY.js';
|
|
12
14
|
import { HoistedStyle } from './chunk-RIZRM7OO.js';
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
import { useSduiAdapter, hostFetchOf, isSafeNavigationTarget, useSduiSandbox, resolveAdapter, assetResolverOf, SduiAdapterContext } from './chunk-YNEEJCSZ.js';
|
|
16
|
+
export { defaultAdapter, useSduiAssetResolver, useSduiSandbox } from './chunk-YNEEJCSZ.js';
|
|
17
|
+
import { DEV, useIsMobile } from './chunk-GSM664TX.js';
|
|
18
|
+
export { MOBILE_MEDIA_QUERY, useIsMobile } from './chunk-GSM664TX.js';
|
|
19
|
+
import { EmptyState, Button, Checkbox, Select, Tabs, SegmentedControl, Spinner, Pagination, fieldOf, StatCard, isZonedIsoDateTime, formatDateValue, Icon, Card, Badge, ProgressBar, Tag, Toast, Textarea, Table, Switch, Input, IconButton, Field, Alert, warnOnce, isPlainObject, CALC_OPS, evaluateCalc, Dialog, ICON_NODES, toNumber } from './chunk-BLFJMMBW.js';
|
|
20
|
+
export { Alert, Avatar, Badge, Breadcrumb, Button, CALC_OPS, Card, Checkbox, Dialog, EmptyState, Field, Icon, IconButton, Input, Pagination, ProgressBar, Radio, SegmentedControl, Select, SidebarNav, Spinner, StatCard, Switch, Table, Tabs, Tag, Textarea, Toast, Tooltip, formatDateValue, formatDisplayValue, formatNumberValue, isZonedIsoDateTime, parseNumeric } from './chunk-BLFJMMBW.js';
|
|
21
|
+
import { boxed, LAYOUT_CSS, childLayoutFrom, CHILD_LAYOUT_PROPS } from './chunk-3JTBOABA.js';
|
|
22
|
+
export { CHILD_LAYOUT_PROPS, LAYOUT_CSS, boxed, childLayoutFrom, columnsClass, containerLayoutStyle, joinClass, mergeStyle, typographyStyle } from './chunk-3JTBOABA.js';
|
|
23
|
+
import React9, { createContext, useState, useMemo, useEffect, useRef, useContext, useCallback, useSyncExternalStore } from 'react';
|
|
21
24
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
22
25
|
|
|
23
26
|
// src/asset-url.ts
|
|
@@ -114,6 +117,119 @@ function resolveAssetsIn(propName, value, resolve, component) {
|
|
|
114
117
|
return out ?? record;
|
|
115
118
|
}
|
|
116
119
|
}
|
|
120
|
+
|
|
121
|
+
// src/josa.ts
|
|
122
|
+
var HANGUL_FIRST = 44032;
|
|
123
|
+
var HANGUL_LAST = 55203;
|
|
124
|
+
var JONG_RIEUL = 8;
|
|
125
|
+
var DIGIT_JONG = {
|
|
126
|
+
"0": 21,
|
|
127
|
+
"1": JONG_RIEUL,
|
|
128
|
+
"2": 0,
|
|
129
|
+
"3": 16,
|
|
130
|
+
"4": 0,
|
|
131
|
+
"5": 0,
|
|
132
|
+
"6": 1,
|
|
133
|
+
"7": JONG_RIEUL,
|
|
134
|
+
"8": JONG_RIEUL,
|
|
135
|
+
"9": 0
|
|
136
|
+
};
|
|
137
|
+
function finalConsonant(word) {
|
|
138
|
+
let text = word.trim();
|
|
139
|
+
for (; ; ) {
|
|
140
|
+
const next = text.replace(/\s*[([([][^()[\]()[]]*[)\])]]$/u, "").replace(/["'”’」』]+$/u, "").trim();
|
|
141
|
+
if (next === text) break;
|
|
142
|
+
text = next;
|
|
143
|
+
}
|
|
144
|
+
const last = text.slice(-1);
|
|
145
|
+
if (!last) return null;
|
|
146
|
+
const code = last.charCodeAt(0);
|
|
147
|
+
if (code >= HANGUL_FIRST && code <= HANGUL_LAST) return (code - HANGUL_FIRST) % 28;
|
|
148
|
+
if (last in DIGIT_JONG) return DIGIT_JONG[last];
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
function josaOf(word, pair) {
|
|
152
|
+
const [withFinal, withoutFinal] = pair.split("/");
|
|
153
|
+
const jong = finalConsonant(word);
|
|
154
|
+
if (jong === null) return `${withFinal}(${withoutFinal})`;
|
|
155
|
+
if (pair === "\uC73C\uB85C/\uB85C") return jong === 0 || jong === JONG_RIEUL ? withoutFinal : withFinal;
|
|
156
|
+
return jong === 0 ? withoutFinal : withFinal;
|
|
157
|
+
}
|
|
158
|
+
function josa(word, pair) {
|
|
159
|
+
return `${word}${josaOf(word, pair)}`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/toast-store.ts
|
|
163
|
+
var TOAST_LIFETIME_MS = 4e3;
|
|
164
|
+
var TOAST_UNSHOWN_TTL_MS = 1e4;
|
|
165
|
+
var entries = [];
|
|
166
|
+
var hosts = [];
|
|
167
|
+
var nextToastId = 0;
|
|
168
|
+
var nextHostId = 0;
|
|
169
|
+
var version = { entries, hosts };
|
|
170
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
171
|
+
function later(fn, ms) {
|
|
172
|
+
const timer = setTimeout(fn, ms);
|
|
173
|
+
timer.unref?.();
|
|
174
|
+
}
|
|
175
|
+
function emit() {
|
|
176
|
+
version = { entries, hosts };
|
|
177
|
+
listeners.forEach((fn) => fn());
|
|
178
|
+
}
|
|
179
|
+
function subscribeToasts(fn) {
|
|
180
|
+
listeners.add(fn);
|
|
181
|
+
return () => {
|
|
182
|
+
listeners.delete(fn);
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function toastSnapshot() {
|
|
186
|
+
return version;
|
|
187
|
+
}
|
|
188
|
+
var EMPTY_SNAPSHOT = { entries: [], hosts: [] };
|
|
189
|
+
function toastServerSnapshot() {
|
|
190
|
+
return EMPTY_SNAPSHOT;
|
|
191
|
+
}
|
|
192
|
+
function allocateToastHostId() {
|
|
193
|
+
nextHostId += 1;
|
|
194
|
+
return nextHostId;
|
|
195
|
+
}
|
|
196
|
+
function registerToastHost(id, channel) {
|
|
197
|
+
hosts = [...hosts.filter((h) => h.id !== id), { id, channel }];
|
|
198
|
+
emit();
|
|
199
|
+
return () => {
|
|
200
|
+
hosts = hosts.filter((h) => h.id !== id);
|
|
201
|
+
emit();
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function pushToastEntry(owner, channel, toast) {
|
|
205
|
+
nextToastId += 1;
|
|
206
|
+
const id = nextToastId;
|
|
207
|
+
entries = [...entries, { ...toast, id, owner, channel, shown: false }];
|
|
208
|
+
emit();
|
|
209
|
+
later(() => {
|
|
210
|
+
const entry = entries.find((e) => e.id === id);
|
|
211
|
+
if (entry && !entry.shown) closeToastEntry(id);
|
|
212
|
+
}, TOAST_UNSHOWN_TTL_MS);
|
|
213
|
+
return id;
|
|
214
|
+
}
|
|
215
|
+
function closeToastEntry(id) {
|
|
216
|
+
if (!entries.some((e) => e.id === id)) return;
|
|
217
|
+
entries = entries.filter((e) => e.id !== id);
|
|
218
|
+
emit();
|
|
219
|
+
}
|
|
220
|
+
function markToastsShown(ids) {
|
|
221
|
+
const fresh = ids.filter((id) => entries.some((e) => e.id === id && !e.shown));
|
|
222
|
+
if (fresh.length === 0) return;
|
|
223
|
+
entries = entries.map((e) => fresh.includes(e.id) ? { ...e, shown: true } : e);
|
|
224
|
+
for (const id of fresh) later(() => closeToastEntry(id), TOAST_LIFETIME_MS);
|
|
225
|
+
}
|
|
226
|
+
function toastHostOf(entry, liveHosts) {
|
|
227
|
+
if (liveHosts.some((h) => h.id === entry.owner)) return entry.owner;
|
|
228
|
+
for (let i = liveHosts.length - 1; i >= 0; i -= 1) {
|
|
229
|
+
if (liveHosts[i].channel === entry.channel) return liveHosts[i].id;
|
|
230
|
+
}
|
|
231
|
+
return void 0;
|
|
232
|
+
}
|
|
117
233
|
function SpecErrorCard({ children }) {
|
|
118
234
|
return /* @__PURE__ */ jsx(
|
|
119
235
|
"div",
|
|
@@ -361,13 +477,85 @@ function cellRenderer(cell, key, dispatch) {
|
|
|
361
477
|
return void 0;
|
|
362
478
|
}
|
|
363
479
|
}
|
|
480
|
+
|
|
481
|
+
// src/search/sort.ts
|
|
482
|
+
var SORT_PARAM = "sort";
|
|
483
|
+
var ORDER_PARAM = "order";
|
|
484
|
+
function sortFromParams(params) {
|
|
485
|
+
const key = params.get(SORT_PARAM);
|
|
486
|
+
if (!key) return null;
|
|
487
|
+
return { key, dir: params.get(ORDER_PARAM) === "desc" ? "desc" : "asc" };
|
|
488
|
+
}
|
|
489
|
+
function nextSort(current, key) {
|
|
490
|
+
if (!current || current.key !== key) return { key, dir: "asc" };
|
|
491
|
+
return current.dir === "asc" ? { key, dir: "desc" } : null;
|
|
492
|
+
}
|
|
493
|
+
function sortEchoOf(body) {
|
|
494
|
+
if (!body || typeof body !== "object") return null;
|
|
495
|
+
const sort = body.sort;
|
|
496
|
+
if (!sort || typeof sort !== "object") return null;
|
|
497
|
+
const { field, order } = sort;
|
|
498
|
+
if (typeof field !== "string" || !field) return null;
|
|
499
|
+
return { field, order: order === "desc" ? "desc" : "asc" };
|
|
500
|
+
}
|
|
501
|
+
var NONE = Object.freeze([]);
|
|
502
|
+
function endpointPath(endpoint) {
|
|
503
|
+
const cut = endpoint.indexOf("?");
|
|
504
|
+
return cut >= 0 ? endpoint.slice(0, cut) : endpoint;
|
|
505
|
+
}
|
|
506
|
+
function createServerSortMemory() {
|
|
507
|
+
const entries2 = /* @__PURE__ */ new Map();
|
|
508
|
+
const entry = (endpoint) => {
|
|
509
|
+
const path = endpointPath(endpoint);
|
|
510
|
+
let found = entries2.get(path);
|
|
511
|
+
if (!found) {
|
|
512
|
+
found = { supported: /* @__PURE__ */ new Set(), unsupported: [], all: false };
|
|
513
|
+
entries2.set(path, found);
|
|
514
|
+
}
|
|
515
|
+
return found;
|
|
516
|
+
};
|
|
517
|
+
const unsortable = (endpoint) => {
|
|
518
|
+
const e = entries2.get(endpointPath(endpoint));
|
|
519
|
+
if (!e) return NONE;
|
|
520
|
+
return e.all ? "all" : e.unsupported.length > 0 ? e.unsupported : NONE;
|
|
521
|
+
};
|
|
522
|
+
return {
|
|
523
|
+
record(endpoint, requested, echo) {
|
|
524
|
+
const e = entry(endpoint);
|
|
525
|
+
if (echo) {
|
|
526
|
+
e.supported.add(echo.field);
|
|
527
|
+
e.all = false;
|
|
528
|
+
if (echo.field === requested.key) return true;
|
|
529
|
+
}
|
|
530
|
+
if (e.supported.size === 0) e.all = true;
|
|
531
|
+
else if (!e.unsupported.includes(requested.key)) e.unsupported = [...e.unsupported, requested.key];
|
|
532
|
+
return false;
|
|
533
|
+
},
|
|
534
|
+
unsortable,
|
|
535
|
+
allows(endpoint, key) {
|
|
536
|
+
const verdict = unsortable(endpoint);
|
|
537
|
+
return verdict !== "all" && !verdict.includes(key);
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
var serverSortMemory = createServerSortMemory();
|
|
542
|
+
function withoutSort(params) {
|
|
543
|
+
const next = new URLSearchParams(params);
|
|
544
|
+
next.delete(SORT_PARAM);
|
|
545
|
+
next.delete(ORDER_PARAM);
|
|
546
|
+
return next;
|
|
547
|
+
}
|
|
364
548
|
function searchUrl(endpoint, params) {
|
|
365
549
|
const query = params.toString();
|
|
366
550
|
return `${endpoint}${query ? endpoint.includes("?") ? "&" : "?" : ""}${query}`;
|
|
367
551
|
}
|
|
368
552
|
function toPage(body) {
|
|
369
553
|
const rows = Array.isArray(body?.data) ? body.data : [];
|
|
370
|
-
return {
|
|
554
|
+
return {
|
|
555
|
+
data: rows,
|
|
556
|
+
total: typeof body?.total === "number" ? body.total : rows.length,
|
|
557
|
+
sort: sortEchoOf(body)
|
|
558
|
+
};
|
|
371
559
|
}
|
|
372
560
|
var defaultFetcher = async (endpoint, params, signal) => {
|
|
373
561
|
const response = await fetch(searchUrl(endpoint, params), {
|
|
@@ -398,19 +586,19 @@ function SduiSearchDataProvider({
|
|
|
398
586
|
return /* @__PURE__ */ jsx(SearchDataContext.Provider, { value: fetcher, children });
|
|
399
587
|
}
|
|
400
588
|
function createSearchInvalidator() {
|
|
401
|
-
const
|
|
589
|
+
const listeners2 = /* @__PURE__ */ new Set();
|
|
402
590
|
return {
|
|
403
591
|
invalidate(prefix = "", exact = false) {
|
|
404
|
-
for (const entry of [...
|
|
592
|
+
for (const entry of [...listeners2]) {
|
|
405
593
|
const hit = prefix === "" ? true : exact ? entry.endpoint === prefix : entry.endpoint.startsWith(prefix);
|
|
406
594
|
if (hit) entry.reload();
|
|
407
595
|
}
|
|
408
596
|
},
|
|
409
597
|
subscribe(endpoint, reload) {
|
|
410
598
|
const entry = { endpoint, reload };
|
|
411
|
-
|
|
599
|
+
listeners2.add(entry);
|
|
412
600
|
return () => {
|
|
413
|
-
|
|
601
|
+
listeners2.delete(entry);
|
|
414
602
|
};
|
|
415
603
|
}
|
|
416
604
|
};
|
|
@@ -436,6 +624,7 @@ function useRemoteSearchPage(endpoint, params) {
|
|
|
436
624
|
const fetcher = useSearchFetcher();
|
|
437
625
|
const invalidator = useContext(SearchInvalidationContext);
|
|
438
626
|
const [page, setPage] = useState(EMPTY_PAGE);
|
|
627
|
+
const [pageQuery, setPageQuery] = useState(null);
|
|
439
628
|
const [isLoading, setIsLoading] = useState(true);
|
|
440
629
|
const [error, setError] = useState(null);
|
|
441
630
|
const [reloadKey, setReloadKey] = useState(0);
|
|
@@ -454,6 +643,7 @@ function useRemoteSearchPage(endpoint, params) {
|
|
|
454
643
|
(next) => {
|
|
455
644
|
if (!alive) return;
|
|
456
645
|
setPage(next);
|
|
646
|
+
setPageQuery(query);
|
|
457
647
|
setError(null);
|
|
458
648
|
setIsLoading(false);
|
|
459
649
|
},
|
|
@@ -469,7 +659,7 @@ function useRemoteSearchPage(endpoint, params) {
|
|
|
469
659
|
};
|
|
470
660
|
}, [endpoint, query, reloadKey]);
|
|
471
661
|
const reload = useCallback(() => setReloadKey((value) => value + 1), []);
|
|
472
|
-
return { page, isLoading, error, reload };
|
|
662
|
+
return { page, pageQuery, isLoading, error, reload };
|
|
473
663
|
}
|
|
474
664
|
function useRemoteSearchSummary(endpoint, params) {
|
|
475
665
|
const adapter = useSduiAdapter();
|
|
@@ -523,6 +713,8 @@ function summaryParams(params) {
|
|
|
523
713
|
const next = new URLSearchParams(params);
|
|
524
714
|
next.delete("page");
|
|
525
715
|
next.delete("limit");
|
|
716
|
+
next.delete("sort");
|
|
717
|
+
next.delete("order");
|
|
526
718
|
return next;
|
|
527
719
|
}
|
|
528
720
|
function plainValue(value) {
|
|
@@ -545,6 +737,9 @@ function SearchSummaryCards({ items, values, dispatch }) {
|
|
|
545
737
|
}
|
|
546
738
|
var OVERSCAN = 10;
|
|
547
739
|
var CLIP_CSS = `.wp-table--clip tbody td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}`;
|
|
740
|
+
function isBlankCell(value) {
|
|
741
|
+
return value === null || value === void 0 || value === "";
|
|
742
|
+
}
|
|
548
743
|
function ResultTable({
|
|
549
744
|
columns,
|
|
550
745
|
rows,
|
|
@@ -555,7 +750,8 @@ function ResultTable({
|
|
|
555
750
|
empty,
|
|
556
751
|
virtual = false,
|
|
557
752
|
height = 560,
|
|
558
|
-
rowHeight = 41
|
|
753
|
+
rowHeight = 41,
|
|
754
|
+
stackable = true
|
|
559
755
|
}) {
|
|
560
756
|
const scrollRef = useRef(null);
|
|
561
757
|
const firstRowRef = useRef(null);
|
|
@@ -579,13 +775,14 @@ function ResultTable({
|
|
|
579
775
|
const table = /* @__PURE__ */ jsxs(
|
|
580
776
|
"table",
|
|
581
777
|
{
|
|
582
|
-
className: `wp-table wp-table--hover${virtual ? " wp-table--sticky wp-table--clip" : ""}`,
|
|
778
|
+
className: `wp-table wp-table--hover${virtual ? " wp-table--sticky wp-table--clip" : stackable ? " wp-table--stackable" : ""}`,
|
|
583
779
|
style: virtual ? { tableLayout: "fixed" } : void 0,
|
|
584
780
|
children: [
|
|
585
781
|
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: columns.map((column) => /* @__PURE__ */ jsx(
|
|
586
782
|
"th",
|
|
587
783
|
{
|
|
588
784
|
style: { width: column.width, textAlign: column.align === "right" ? "right" : void 0 },
|
|
785
|
+
"data-mobile": column.mobile,
|
|
589
786
|
children: column.sortable && onSort ? (
|
|
590
787
|
// 인라인 style로는 :hover를 표현할 수 없다 — 색 피드백은 .wp-table__sort:hover가 낸다
|
|
591
788
|
/* @__PURE__ */ jsxs("button", { type: "button", className: "wp-table__sort", onClick: () => onSort(column.key), children: [
|
|
@@ -619,10 +816,20 @@ function ResultTable({
|
|
|
619
816
|
if (column.align === "right") cls.push("wp-table__num");
|
|
620
817
|
if (column.mono) cls.push("wp-table__mono");
|
|
621
818
|
if (column.strong) cls.push("wp-table__strong");
|
|
622
|
-
|
|
819
|
+
const value = column.render ? column.render(row, index) : (
|
|
623
820
|
// 평면 키 우선, 없으면 점 경로 — 셀 렌더러(cells.tsx)와 같은 조회 규칙이다
|
|
624
821
|
fieldOf(row, column.key)
|
|
625
|
-
)
|
|
822
|
+
);
|
|
823
|
+
return /* @__PURE__ */ jsx(
|
|
824
|
+
"td",
|
|
825
|
+
{
|
|
826
|
+
className: cls.join(" ") || void 0,
|
|
827
|
+
"data-label": labelOf(column),
|
|
828
|
+
"data-mobile": column.mobile,
|
|
829
|
+
children: isBlankCell(value) ? /* @__PURE__ */ jsx("span", { style: { color: "var(--wp-text-subtle)" }, children: "\u2014" }) : value
|
|
830
|
+
},
|
|
831
|
+
column.key
|
|
832
|
+
);
|
|
626
833
|
})
|
|
627
834
|
},
|
|
628
835
|
row.id ?? index
|
|
@@ -633,14 +840,33 @@ function ResultTable({
|
|
|
633
840
|
]
|
|
634
841
|
}
|
|
635
842
|
);
|
|
636
|
-
if (!virtual)
|
|
843
|
+
if (!virtual) {
|
|
844
|
+
return /* @__PURE__ */ jsx("div", { className: "wp-table-scroll", children: table });
|
|
845
|
+
}
|
|
637
846
|
return /* @__PURE__ */ jsxs("div", { ref: scrollRef, onScroll: handleScroll, style: { height, overflowY: "auto" }, children: [
|
|
638
847
|
/* @__PURE__ */ jsx(HoistedStyle, { id: "wp-table-clip", css: CLIP_CSS }),
|
|
639
848
|
table
|
|
640
849
|
] });
|
|
641
850
|
}
|
|
642
|
-
function
|
|
643
|
-
|
|
851
|
+
function labelOf(column) {
|
|
852
|
+
if (typeof column.label === "string") return column.label;
|
|
853
|
+
return typeof column.header === "string" ? column.header : "";
|
|
854
|
+
}
|
|
855
|
+
function ResultEmptyState({ title, filtered = false, emptyTitle, emptyText, scoped = false, onReset }) {
|
|
856
|
+
const name = String(title ?? "").trim() || "\uD56D\uBAA9";
|
|
857
|
+
if (filtered) {
|
|
858
|
+
return /* @__PURE__ */ jsx(
|
|
859
|
+
EmptyState,
|
|
860
|
+
{
|
|
861
|
+
icon: "scan-search",
|
|
862
|
+
title: `\uC870\uAC74\uC5D0 \uB9DE\uB294 ${name}${josaOf(name, "\uC774/\uAC00")} \uC5C6\uC2B5\uB2C8\uB2E4`,
|
|
863
|
+
tight: true,
|
|
864
|
+
actions: onReset ? /* @__PURE__ */ jsx(Button, { size: "sm", variant: "secondary", icon: "refresh-cw", onClick: onReset, children: "\uC870\uAC74 \uCD08\uAE30\uD654" }) : void 0,
|
|
865
|
+
children: "\uAC80\uC0C9\uC5B4\uB098 \uC870\uAC74\uC744 \uBC14\uAFD4 \uBCF4\uC138\uC694."
|
|
866
|
+
}
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
return /* @__PURE__ */ jsx(EmptyState, { title: emptyTitle || `${scoped ? "" : "\uC544\uC9C1 "}${name}${josaOf(name, "\uC774/\uAC00")} \uC5C6\uC2B5\uB2C8\uB2E4`, tight: true, children: emptyText || void 0 });
|
|
644
870
|
}
|
|
645
871
|
var EMPTY_FILTERS = [];
|
|
646
872
|
var EMPTY_FIELDS = [];
|
|
@@ -649,6 +875,29 @@ var DEFAULT_VIEWS = [
|
|
|
649
875
|
{ value: "list", icon: "list", label: "\uBAA9\uB85D" },
|
|
650
876
|
{ value: "grid", icon: "layout-grid", label: "\uCE74\uB4DC" }
|
|
651
877
|
];
|
|
878
|
+
var VIEW_STORAGE_PREFIX = "wp-sdui:view:";
|
|
879
|
+
var viewMemory = /* @__PURE__ */ new Map();
|
|
880
|
+
function readStoredView(title) {
|
|
881
|
+
const inMemory = viewMemory.get(title);
|
|
882
|
+
if (inMemory) return inMemory;
|
|
883
|
+
try {
|
|
884
|
+
if (typeof window === "undefined") return null;
|
|
885
|
+
const stored = window.sessionStorage.getItem(VIEW_STORAGE_PREFIX + title);
|
|
886
|
+
return stored === "grid" || stored === "list" ? stored : null;
|
|
887
|
+
} catch {
|
|
888
|
+
return null;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
function writeStoredView(title, view) {
|
|
892
|
+
viewMemory.set(title, view);
|
|
893
|
+
try {
|
|
894
|
+
if (typeof window !== "undefined") window.sessionStorage.setItem(VIEW_STORAGE_PREFIX + title, view);
|
|
895
|
+
} catch {
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
function looksLikeLocator(value) {
|
|
899
|
+
return /^(https?:)?\/\//.test(value) || value.startsWith("/") || value.startsWith("data:");
|
|
900
|
+
}
|
|
652
901
|
function normalizePageSizes(sizes) {
|
|
653
902
|
if (!Array.isArray(sizes)) return void 0;
|
|
654
903
|
const clean = [...new Set(sizes.map(Number).filter((n) => Number.isFinite(n) && n > 0).map(Math.floor))].sort(
|
|
@@ -697,9 +946,15 @@ function SearchResults({
|
|
|
697
946
|
hideHeader = false,
|
|
698
947
|
advancedSearchFields: rawAdvancedSearchFields = EMPTY_FIELDS,
|
|
699
948
|
searchPlaceholder,
|
|
700
|
-
virtual
|
|
949
|
+
virtual,
|
|
950
|
+
serverUnsortable,
|
|
951
|
+
primaryKey,
|
|
952
|
+
emptyTitle,
|
|
953
|
+
emptyText,
|
|
954
|
+
serverFiltered = false
|
|
701
955
|
}) {
|
|
702
956
|
const hasActions = actions.length > 0;
|
|
957
|
+
const isMobile = useIsMobile();
|
|
703
958
|
const advancedSearchFields = useMemo(
|
|
704
959
|
() => normalizeSearchFields(rawAdvancedSearchFields),
|
|
705
960
|
[rawAdvancedSearchFields]
|
|
@@ -715,12 +970,18 @@ function SearchResults({
|
|
|
715
970
|
);
|
|
716
971
|
const applyViewConstraints = (candidate) => viewItems.some((v) => v.value === candidate) ? candidate : viewItems[0].value;
|
|
717
972
|
const appliedSearch = searchParams.get("search") ?? "";
|
|
718
|
-
const [
|
|
973
|
+
const [chosenView, setChosenView] = useState(() => readStoredView(title));
|
|
719
974
|
const [search, setSearch] = useState(appliedSearch);
|
|
720
|
-
const [sort, setSort] = useState(null);
|
|
721
975
|
const [selected, setSelected] = useState(/* @__PURE__ */ new Set());
|
|
722
976
|
const selectedRowsRef = useRef(/* @__PURE__ */ new Map());
|
|
723
|
-
const
|
|
977
|
+
const hasGridView = viewItems.some((v) => v.value === "grid");
|
|
978
|
+
const defaultView = isMobile && hasGridView ? "grid" : initialView;
|
|
979
|
+
const activeView = applyViewConstraints(chosenView ?? defaultView);
|
|
980
|
+
const chooseView = (next) => {
|
|
981
|
+
const view = applyViewConstraints(next);
|
|
982
|
+
writeStoredView(title, view);
|
|
983
|
+
setChosenView(view);
|
|
984
|
+
};
|
|
724
985
|
useEffect(() => {
|
|
725
986
|
setSearch(appliedSearch);
|
|
726
987
|
}, [appliedSearch]);
|
|
@@ -737,33 +998,43 @@ function SearchResults({
|
|
|
737
998
|
onParamsChange(next);
|
|
738
999
|
};
|
|
739
1000
|
const activeFilters = useMemo(() => {
|
|
740
|
-
const
|
|
1001
|
+
const entries2 = [];
|
|
741
1002
|
for (const filter of [...leftFilters, ...rightFilters]) {
|
|
742
1003
|
const values = splitMultiValue(searchParams.get(filter.key) ?? "");
|
|
743
|
-
if (values.length > 0)
|
|
1004
|
+
if (values.length > 0) entries2.push({ key: filter.key, values });
|
|
744
1005
|
}
|
|
745
1006
|
if (tabs && typeof tabs.key === "string") {
|
|
746
1007
|
const values = splitMultiValue(searchParams.get(tabs.key) ?? "");
|
|
747
|
-
if (values.length > 0)
|
|
1008
|
+
if (values.length > 0) entries2.push({ key: tabs.key, values });
|
|
748
1009
|
}
|
|
749
|
-
return
|
|
1010
|
+
return entries2;
|
|
750
1011
|
}, [leftFilters, rightFilters, tabs, searchParams]);
|
|
751
1012
|
const hasAllRows = item.data.length >= item.total;
|
|
1013
|
+
const sortAllowed = (key) => hasAllRows || !(serverUnsortable === "all" || Array.isArray(serverUnsortable) && serverUnsortable.includes(key));
|
|
1014
|
+
const requestedSort = sortFromParams(searchParams);
|
|
1015
|
+
const sortKey = requestedSort && sortAllowed(requestedSort.key) ? requestedSort.key : null;
|
|
1016
|
+
const sortDir = requestedSort?.dir ?? "asc";
|
|
1017
|
+
const sort = useMemo(() => sortKey ? { key: sortKey, dir: sortDir } : null, [sortKey, sortDir]);
|
|
752
1018
|
const processed = useMemo(() => {
|
|
753
1019
|
const cell = (row, key) => fieldOf(row, key);
|
|
754
1020
|
let rows = item.data;
|
|
755
|
-
if (hasAllRows) {
|
|
1021
|
+
if (hasAllRows && !serverFiltered) {
|
|
1022
|
+
const source = item.data;
|
|
1023
|
+
const known = (key) => source.some((row) => cell(row, key) !== void 0);
|
|
756
1024
|
if (appliedSearch) {
|
|
757
1025
|
rows = rows.filter((row) => includesLooseSearch(localSearchText(row), appliedSearch));
|
|
758
1026
|
}
|
|
759
1027
|
for (const { key, values } of activeFilters) {
|
|
1028
|
+
if (!known(key)) continue;
|
|
760
1029
|
rows = rows.filter((row) => values.includes(String(cell(row, key))));
|
|
761
1030
|
}
|
|
762
1031
|
for (const field of advancedSearchFields) {
|
|
763
1032
|
if (field.type === "select") {
|
|
1033
|
+
if (!known(field.key)) continue;
|
|
764
1034
|
const wanted = splitMultiValue(advancedValues[field.key] ?? "");
|
|
765
1035
|
if (wanted.length > 0) rows = rows.filter((row) => wanted.includes(String(cell(row, field.key))));
|
|
766
1036
|
} else if (field.type === "cascading") {
|
|
1037
|
+
if (!known(field.key)) continue;
|
|
767
1038
|
const wanted = splitMultiValue(advancedValues[field.key] ?? "");
|
|
768
1039
|
if (wanted.length > 0) {
|
|
769
1040
|
const lineages = cascadingValuePaths(field.options);
|
|
@@ -774,12 +1045,14 @@ function SearchResults({
|
|
|
774
1045
|
});
|
|
775
1046
|
}
|
|
776
1047
|
} else if (field.type === "dateRange") {
|
|
1048
|
+
if (!known(field.key)) continue;
|
|
777
1049
|
const from = advancedValues[field.fromKey ?? `${field.key}From`];
|
|
778
1050
|
const to = advancedValues[field.toKey ?? `${field.key}To`];
|
|
779
|
-
const datePart = (v) => String(v ?? "").slice(0, 10);
|
|
1051
|
+
const datePart = (v) => isZonedIsoDateTime(v) ? formatDateValue(v, "date") : String(v ?? "").slice(0, 10);
|
|
780
1052
|
if (from) rows = rows.filter((row) => datePart(cell(row, field.key)) >= from.slice(0, 10));
|
|
781
1053
|
if (to) rows = rows.filter((row) => datePart(cell(row, field.key)) <= to.slice(0, 10));
|
|
782
1054
|
} else if (field.type === "numberRange") {
|
|
1055
|
+
if (!known(field.key)) continue;
|
|
783
1056
|
const min = numericValue(advancedValues[field.fromKey ?? `${field.key}Min`]);
|
|
784
1057
|
const max = numericValue(advancedValues[field.toKey ?? `${field.key}Max`]);
|
|
785
1058
|
if (min !== null) rows = rows.filter((row) => (numericValue(cell(row, field.key)) ?? -Infinity) >= min);
|
|
@@ -788,11 +1061,13 @@ function SearchResults({
|
|
|
788
1061
|
const keyword = advancedValues[field.key];
|
|
789
1062
|
const suffix = ["_contains", "Contains"].find((s) => field.key.endsWith(s));
|
|
790
1063
|
const baseKey = suffix ? field.key.slice(0, -suffix.length) : field.key;
|
|
791
|
-
if (keyword
|
|
1064
|
+
if (keyword && known(baseKey)) {
|
|
1065
|
+
rows = rows.filter((row) => includesLooseSearch(String(cell(row, baseKey) ?? ""), keyword));
|
|
1066
|
+
}
|
|
792
1067
|
}
|
|
793
1068
|
}
|
|
794
1069
|
}
|
|
795
|
-
if (sort) {
|
|
1070
|
+
if (sort && hasAllRows) {
|
|
796
1071
|
const column = columns.find((c) => c.key === sort.key);
|
|
797
1072
|
const dirFactor = sort.dir === "asc" ? 1 : -1;
|
|
798
1073
|
rows = [...rows].sort((a, b) => {
|
|
@@ -807,7 +1082,7 @@ function SearchResults({
|
|
|
807
1082
|
});
|
|
808
1083
|
}
|
|
809
1084
|
return rows;
|
|
810
|
-
}, [item.data, hasAllRows, appliedSearch, activeFilters, advancedSearchFields, advancedValues, sort, columns]);
|
|
1085
|
+
}, [item.data, hasAllRows, serverFiltered, appliedSearch, activeFilters, advancedSearchFields, advancedValues, sort, columns]);
|
|
811
1086
|
const clientPaginated = hasAllRows;
|
|
812
1087
|
const totalCount = clientPaginated ? processed.length : item.total;
|
|
813
1088
|
const pageCount = Math.max(1, Math.ceil(totalCount / pageSize));
|
|
@@ -839,7 +1114,50 @@ function SearchResults({
|
|
|
839
1114
|
});
|
|
840
1115
|
};
|
|
841
1116
|
const selectedItems = Array.from(selected).map((id) => selectedRowsRef.current.get(id)).filter((row) => row !== void 0);
|
|
1117
|
+
const paramsKey = searchParams.toString();
|
|
1118
|
+
const seenRef = useRef(null);
|
|
1119
|
+
const answersRef = useRef(/* @__PURE__ */ new Map());
|
|
1120
|
+
useEffect(() => {
|
|
1121
|
+
const settle = (key, data) => {
|
|
1122
|
+
const answers = answersRef.current;
|
|
1123
|
+
const nextIds = new Set(data.map((row) => row.id));
|
|
1124
|
+
const prev = answers.get(key);
|
|
1125
|
+
answers.delete(key);
|
|
1126
|
+
answers.set(key, nextIds);
|
|
1127
|
+
if (answers.size > 64) answers.delete(answers.keys().next().value);
|
|
1128
|
+
if (!prev) return;
|
|
1129
|
+
const vanished = [...prev].filter((id) => !nextIds.has(id));
|
|
1130
|
+
if (vanished.length === 0) return;
|
|
1131
|
+
vanished.forEach((id) => selectedRowsRef.current.delete(id));
|
|
1132
|
+
setSelected((current) => {
|
|
1133
|
+
let changed = false;
|
|
1134
|
+
const next = new Set(current);
|
|
1135
|
+
for (const id of vanished) if (next.delete(id)) changed = true;
|
|
1136
|
+
return changed ? next : current;
|
|
1137
|
+
});
|
|
1138
|
+
};
|
|
1139
|
+
const seen = seenRef.current;
|
|
1140
|
+
seenRef.current = { key: paramsKey, data: item.data };
|
|
1141
|
+
if (!seen) {
|
|
1142
|
+
settle(paramsKey, item.data);
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
if (seen.data === item.data) return;
|
|
1146
|
+
settle(paramsKey, item.data);
|
|
1147
|
+
}, [paramsKey, item.data]);
|
|
842
1148
|
const ratioSum = columns.reduce((sum, c) => sum + (c.ratio ?? 0), 0);
|
|
1149
|
+
const titleColumnKey = columns.find((c) => c.mobile === "title")?.key ?? (primaryKey && columns.some((c) => c.key === primaryKey) ? primaryKey : void 0) ?? columns.find((c) => c.strong && c.mobile !== "hide")?.key;
|
|
1150
|
+
const rowLabelOf = (row) => {
|
|
1151
|
+
const keys = [primaryKey, titleColumnKey, ...columns.map((c) => c.key)].filter(
|
|
1152
|
+
(key) => typeof key === "string" && key.length > 0
|
|
1153
|
+
);
|
|
1154
|
+
for (const key of keys) {
|
|
1155
|
+
const value = fieldOf(row, key);
|
|
1156
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
1157
|
+
if (typeof value === "string" && value.trim() && !looksLikeLocator(value.trim())) return value.trim();
|
|
1158
|
+
}
|
|
1159
|
+
return "\uD589";
|
|
1160
|
+
};
|
|
843
1161
|
const tableColumns = [
|
|
844
1162
|
...hasActions ? [
|
|
845
1163
|
{
|
|
@@ -854,6 +1172,7 @@ function SearchResults({
|
|
|
854
1172
|
}
|
|
855
1173
|
),
|
|
856
1174
|
width: "36px",
|
|
1175
|
+
label: "",
|
|
857
1176
|
render: (row) => (
|
|
858
1177
|
// 셀 어디를 눌러도 행 클릭(onRowClick)으로 번지지 않게 래퍼에서 전파를 끊는다
|
|
859
1178
|
/* @__PURE__ */ jsx("span", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx(
|
|
@@ -861,7 +1180,7 @@ function SearchResults({
|
|
|
861
1180
|
{
|
|
862
1181
|
checked: selected.has(row.id),
|
|
863
1182
|
onChange: (e) => toggleSelect(row, e.target.checked),
|
|
864
|
-
"aria-label": `${row
|
|
1183
|
+
"aria-label": `${rowLabelOf(row)} \uC120\uD0DD`
|
|
865
1184
|
}
|
|
866
1185
|
) })
|
|
867
1186
|
)
|
|
@@ -870,7 +1189,9 @@ function SearchResults({
|
|
|
870
1189
|
...columns.map((column) => ({
|
|
871
1190
|
key: column.key,
|
|
872
1191
|
header: column.title,
|
|
873
|
-
|
|
1192
|
+
label: column.title,
|
|
1193
|
+
mobile: column.mobile ?? (column.key === titleColumnKey ? "title" : void 0),
|
|
1194
|
+
sortable: column.sortable && sortAllowed(column.key),
|
|
874
1195
|
align: column.textAlign === "right" ? "right" : "left",
|
|
875
1196
|
mono: column.mono,
|
|
876
1197
|
strong: column.strong,
|
|
@@ -879,9 +1200,51 @@ function SearchResults({
|
|
|
879
1200
|
}))
|
|
880
1201
|
];
|
|
881
1202
|
const handleSort = (key) => {
|
|
882
|
-
|
|
1203
|
+
const next = nextSort(sort, key);
|
|
1204
|
+
updateParams({ [SORT_PARAM]: next?.key ?? null, [ORDER_PARAM]: next?.dir ?? null, page: null });
|
|
883
1205
|
};
|
|
884
|
-
const
|
|
1206
|
+
const sortChoices = columns.filter((column) => column.sortable && sortAllowed(column.key));
|
|
1207
|
+
const mobileSort = isMobile && sortChoices.length > 0 ? /* @__PURE__ */ jsx(
|
|
1208
|
+
Select,
|
|
1209
|
+
{
|
|
1210
|
+
size: "sm",
|
|
1211
|
+
"aria-label": `${title} \uC815\uB82C`,
|
|
1212
|
+
value: sort ? `${sort.key}:${sort.dir}` : "",
|
|
1213
|
+
options: [
|
|
1214
|
+
{ value: "", label: "\uAE30\uBCF8 \uC815\uB82C" },
|
|
1215
|
+
...sortChoices.flatMap((column) => [
|
|
1216
|
+
{ value: `${column.key}:asc`, label: `${column.title} \uC624\uB984\uCC28\uC21C` },
|
|
1217
|
+
{ value: `${column.key}:desc`, label: `${column.title} \uB0B4\uB9BC\uCC28\uC21C` }
|
|
1218
|
+
])
|
|
1219
|
+
],
|
|
1220
|
+
onChange: (e) => {
|
|
1221
|
+
const cut = e.target.value.lastIndexOf(":");
|
|
1222
|
+
const key = cut > 0 ? e.target.value.slice(0, cut) : "";
|
|
1223
|
+
const dir = e.target.value.slice(cut + 1) === "desc" ? "desc" : "asc";
|
|
1224
|
+
updateParams({ [SORT_PARAM]: key || null, [ORDER_PARAM]: key ? dir : null, page: null });
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
) : null;
|
|
1228
|
+
const chipKeys = [...leftFilters, ...rightFilters].map((filter) => filter.key);
|
|
1229
|
+
const filtered = Boolean(appliedSearch) || Object.values(advancedValues).some((value) => Boolean(value)) || chipKeys.some((key) => Boolean(searchParams.get(key)));
|
|
1230
|
+
const scoped = Boolean(tabs && typeof tabs.key === "string" && searchParams.get(tabs.key));
|
|
1231
|
+
const resetConditions = () => {
|
|
1232
|
+
const patch = { search: null, page: null };
|
|
1233
|
+
for (const key of searchFieldKeys(advancedSearchFields)) patch[key] = null;
|
|
1234
|
+
for (const key of chipKeys) patch[key] = null;
|
|
1235
|
+
updateParams(patch);
|
|
1236
|
+
};
|
|
1237
|
+
const empty = /* @__PURE__ */ jsx(
|
|
1238
|
+
ResultEmptyState,
|
|
1239
|
+
{
|
|
1240
|
+
title,
|
|
1241
|
+
filtered,
|
|
1242
|
+
scoped,
|
|
1243
|
+
emptyTitle,
|
|
1244
|
+
emptyText,
|
|
1245
|
+
onReset: resetConditions
|
|
1246
|
+
}
|
|
1247
|
+
);
|
|
885
1248
|
return /* @__PURE__ */ jsxs("div", { className: ["wp-col", className].filter(Boolean).join(" "), style, "aria-label": title, children: [
|
|
886
1249
|
summary ?? null,
|
|
887
1250
|
tabs && Array.isArray(tabs.items) && tabs.items.length > 0 ? /* @__PURE__ */ jsx(
|
|
@@ -921,9 +1284,10 @@ function SearchResults({
|
|
|
921
1284
|
{
|
|
922
1285
|
items: viewItems,
|
|
923
1286
|
value: activeView,
|
|
924
|
-
onChange: (next) =>
|
|
1287
|
+
onChange: (next) => chooseView(next)
|
|
925
1288
|
}
|
|
926
1289
|
) : null,
|
|
1290
|
+
mobileSort,
|
|
927
1291
|
headerActions
|
|
928
1292
|
] })
|
|
929
1293
|
] }) : null,
|
|
@@ -1004,7 +1368,7 @@ function SearchResults({
|
|
|
1004
1368
|
virtual: virtualListActive
|
|
1005
1369
|
}
|
|
1006
1370
|
) : visibleItems.length === 0 ? empty : /* @__PURE__ */ jsx("div", { className: "wp-grid wp-grid--3", children: visibleItems.map((row) => /* @__PURE__ */ jsx(GridCard, { item: row, config: gridConfig, onClick: onRowClick }, row.id)) }),
|
|
1007
|
-
virtualListActive && clientPaginated ? null : /* @__PURE__ */ jsxs("div", { className: "wp-row wp-row--between", children: [
|
|
1371
|
+
virtualListActive && clientPaginated || totalCount === 0 && !isLoading ? null : /* @__PURE__ */ jsxs("div", { className: "wp-row wp-row--between", children: [
|
|
1008
1372
|
/* @__PURE__ */ jsxs("div", { className: "wp-row", style: { flex: "none" }, children: [
|
|
1009
1373
|
/* @__PURE__ */ jsx("span", { className: "wp-caption", style: { whiteSpace: "nowrap" }, children: "\uD398\uC774\uC9C0\uB2F9" }),
|
|
1010
1374
|
/* @__PURE__ */ jsx(
|
|
@@ -1046,6 +1410,9 @@ function formatCell(value) {
|
|
|
1046
1410
|
if (value instanceof Date) return formatDateCellValue(value);
|
|
1047
1411
|
return value;
|
|
1048
1412
|
}
|
|
1413
|
+
function blankAsDash(value) {
|
|
1414
|
+
return value === null || value === void 0 || value === "" ? /* @__PURE__ */ jsx("span", { style: { color: "var(--wp-text-subtle)" }, children: "\u2014" }) : value;
|
|
1415
|
+
}
|
|
1049
1416
|
function resolveRowPart(part, item) {
|
|
1050
1417
|
return typeof part === "function" ? part(item) : part;
|
|
1051
1418
|
}
|
|
@@ -1061,7 +1428,7 @@ function GridCard({
|
|
|
1061
1428
|
return /* @__PURE__ */ jsxs("div", { className: "wp-row", children: [
|
|
1062
1429
|
resolveRowPart(row.icon, item),
|
|
1063
1430
|
row.label != null ? /* @__PURE__ */ jsx("span", { className: "wp-caption", children: resolveRowPart(row.label, item) }) : null,
|
|
1064
|
-
/* @__PURE__ */ jsx("span", { className: "wp-body-sm", children: resolveRowPart(row.value, item) })
|
|
1431
|
+
/* @__PURE__ */ jsx("span", { className: "wp-body-sm", children: blankAsDash(resolveRowPart(row.value, item)) })
|
|
1065
1432
|
] }, idx);
|
|
1066
1433
|
}) });
|
|
1067
1434
|
const note = config.note?.(item);
|
|
@@ -1130,6 +1497,22 @@ function FilterMultiSelect({
|
|
|
1130
1497
|
function toList(action) {
|
|
1131
1498
|
return Array.isArray(action) ? action : [action];
|
|
1132
1499
|
}
|
|
1500
|
+
function conditionParams(params) {
|
|
1501
|
+
const next = new URLSearchParams(params);
|
|
1502
|
+
next.delete("page");
|
|
1503
|
+
next.delete("limit");
|
|
1504
|
+
return next.toString();
|
|
1505
|
+
}
|
|
1506
|
+
function useQueryStateMirror(dispatch, key, params) {
|
|
1507
|
+
const value = conditionParams(params);
|
|
1508
|
+
const lastRef = useRef(null);
|
|
1509
|
+
useEffect(() => {
|
|
1510
|
+
if (typeof key !== "string" || key === "") return;
|
|
1511
|
+
if (lastRef.current === value) return;
|
|
1512
|
+
lastRef.current = value;
|
|
1513
|
+
dispatch({ action: "setState", key, value });
|
|
1514
|
+
}, [dispatch, key, value]);
|
|
1515
|
+
}
|
|
1133
1516
|
function mapColumns(columns, dispatch) {
|
|
1134
1517
|
return (columns ?? []).map((column) => ({
|
|
1135
1518
|
key: column.key,
|
|
@@ -1139,6 +1522,7 @@ function mapColumns(columns, dispatch) {
|
|
|
1139
1522
|
ratio: column.ratio,
|
|
1140
1523
|
mono: column.mono,
|
|
1141
1524
|
strong: column.strong,
|
|
1525
|
+
mobile: column.mobile === "title" || column.mobile === "hide" ? column.mobile : void 0,
|
|
1142
1526
|
render: cellRenderer(column.cell, column.key, dispatch)
|
|
1143
1527
|
}));
|
|
1144
1528
|
}
|
|
@@ -1240,11 +1624,15 @@ function SduiSearchResultsImpl({
|
|
|
1240
1624
|
searchPlaceholder,
|
|
1241
1625
|
rowClick,
|
|
1242
1626
|
headerActions,
|
|
1243
|
-
virtual
|
|
1627
|
+
virtual,
|
|
1628
|
+
emptyTitle,
|
|
1629
|
+
emptyText,
|
|
1630
|
+
queryStateKey
|
|
1244
1631
|
}) {
|
|
1245
1632
|
const dispatch = useSduiDispatch();
|
|
1246
1633
|
const [params, setParams] = useState(() => new URLSearchParams());
|
|
1247
1634
|
const rows = Array.isArray(data) ? data : [];
|
|
1635
|
+
useQueryStateMirror(dispatch, queryStateKey, params);
|
|
1248
1636
|
return /* @__PURE__ */ jsx(
|
|
1249
1637
|
SearchResults,
|
|
1250
1638
|
{
|
|
@@ -1265,6 +1653,9 @@ function SduiSearchResultsImpl({
|
|
|
1265
1653
|
searchPlaceholder,
|
|
1266
1654
|
headerActions,
|
|
1267
1655
|
virtual,
|
|
1656
|
+
primaryKey: gridCard?.titleKey,
|
|
1657
|
+
emptyTitle,
|
|
1658
|
+
emptyText,
|
|
1268
1659
|
onRowClick: rowClick ? (row) => toList(rowClick).forEach((a) => dispatch(a, row)) : void 0,
|
|
1269
1660
|
searchParams: params,
|
|
1270
1661
|
onParamsChange: setParams
|
|
@@ -1293,7 +1684,10 @@ function RemoteSearchResultsImpl({
|
|
|
1293
1684
|
pageSize,
|
|
1294
1685
|
searchParams: controlledParams,
|
|
1295
1686
|
onParamsChange,
|
|
1296
|
-
summary
|
|
1687
|
+
summary,
|
|
1688
|
+
emptyTitle,
|
|
1689
|
+
emptyText,
|
|
1690
|
+
queryStateKey
|
|
1297
1691
|
}) {
|
|
1298
1692
|
const dispatch = useSduiDispatch();
|
|
1299
1693
|
const defaultPageSize = normalizePageSizes(pageSizes)?.[0] ?? 10;
|
|
@@ -1313,8 +1707,18 @@ function RemoteSearchResultsImpl({
|
|
|
1313
1707
|
if (!next.has("limit")) next.set("limit", String(defaultPageSize));
|
|
1314
1708
|
return next;
|
|
1315
1709
|
}, [params, defaultPageSize]);
|
|
1316
|
-
const { page, isLoading, error, reload } = useRemoteSearchPage(endpoint, query);
|
|
1710
|
+
const { page, pageQuery, isLoading, error, reload } = useRemoteSearchPage(endpoint, query);
|
|
1711
|
+
const requestedSort = sortFromParams(params);
|
|
1712
|
+
const queryString = query.toString();
|
|
1713
|
+
const settledForQuery = pageQuery === queryString;
|
|
1714
|
+
useEffect(() => {
|
|
1715
|
+
if (!requestedSort || !settledForQuery || page.data.length >= page.total) return;
|
|
1716
|
+
if (!serverSortMemory.record(endpoint, requestedSort, page.sort ?? null)) {
|
|
1717
|
+
handleParamsChange(withoutSort(params));
|
|
1718
|
+
}
|
|
1719
|
+
}, [page, settledForQuery, endpoint, requestedSort?.key, requestedSort?.dir]);
|
|
1317
1720
|
const summaryQuery = useMemo(() => summaryParams(params), [params]);
|
|
1721
|
+
useQueryStateMirror(dispatch, queryStateKey, params);
|
|
1318
1722
|
const summaryState = useRemoteSearchSummary(
|
|
1319
1723
|
summary && typeof summary.endpoint === "string" ? summary.endpoint : null,
|
|
1320
1724
|
summaryQuery
|
|
@@ -1351,6 +1755,11 @@ function RemoteSearchResultsImpl({
|
|
|
1351
1755
|
headerActions,
|
|
1352
1756
|
virtual,
|
|
1353
1757
|
isLoading,
|
|
1758
|
+
serverUnsortable: serverSortMemory.unsortable(endpoint),
|
|
1759
|
+
serverFiltered: true,
|
|
1760
|
+
primaryKey: gridCard?.titleKey,
|
|
1761
|
+
emptyTitle,
|
|
1762
|
+
emptyText,
|
|
1354
1763
|
summary: summary ? /* @__PURE__ */ jsx(SearchSummaryCards, { items: summary.items, values: summaryState.values, dispatch }) : void 0,
|
|
1355
1764
|
onRowClick: rowClick ? (row) => toList(rowClick).forEach((a) => dispatch(a, row)) : void 0,
|
|
1356
1765
|
searchParams: params,
|
|
@@ -1360,12 +1769,6 @@ function RemoteSearchResultsImpl({
|
|
|
1360
1769
|
}
|
|
1361
1770
|
var SduiSearchResults = boxed(SduiSearchResultsImpl, "SduiSearchResults");
|
|
1362
1771
|
var RemoteSearchResults = boxed(RemoteSearchResultsImpl, "RemoteSearchResults");
|
|
1363
|
-
var SduiRuntimeContext = createContext(null);
|
|
1364
|
-
function useSduiRuntime() {
|
|
1365
|
-
const runtime = useContext(SduiRuntimeContext);
|
|
1366
|
-
if (!runtime) throw new Error("useSduiRuntime\uC740 SduiRenderer \uB0B4\uBD80\uC5D0\uC11C\uB9CC \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
|
|
1367
|
-
return runtime;
|
|
1368
|
-
}
|
|
1369
1772
|
function prefillParams(search, hash) {
|
|
1370
1773
|
const params = new URLSearchParams(search);
|
|
1371
1774
|
const q = hash.indexOf("?");
|
|
@@ -1402,21 +1805,21 @@ function SduiForm({
|
|
|
1402
1805
|
return JSON.stringify(keys.map((key) => [key, state[key]]));
|
|
1403
1806
|
};
|
|
1404
1807
|
useEffect(() => {
|
|
1405
|
-
const
|
|
1808
|
+
const entries2 = {};
|
|
1406
1809
|
for (const [field, value] of Object.entries(initial ?? {})) {
|
|
1407
|
-
if (runtime.state[`${id}.${field}`] === void 0)
|
|
1810
|
+
if (runtime.state[`${id}.${field}`] === void 0) entries2[`${id}.${field}`] = value;
|
|
1408
1811
|
}
|
|
1409
1812
|
if (initialFromUrl && typeof window !== "undefined") {
|
|
1410
1813
|
const params = prefillParams(window.location.search, window.location.hash);
|
|
1411
1814
|
for (const [field, param] of Object.entries(initialFromUrl)) {
|
|
1412
1815
|
const raw = params.get(param);
|
|
1413
1816
|
if (raw !== null && runtime.state[`${id}.${field}`] === void 0) {
|
|
1414
|
-
|
|
1817
|
+
entries2[`${id}.${field}`] = raw;
|
|
1415
1818
|
}
|
|
1416
1819
|
}
|
|
1417
1820
|
}
|
|
1418
|
-
if (Object.keys(
|
|
1419
|
-
initialSnapshot.current = sliceOf({ ...runtime.state, ...
|
|
1821
|
+
if (Object.keys(entries2).length > 0) runtime.setEntries(entries2);
|
|
1822
|
+
initialSnapshot.current = sliceOf({ ...runtime.state, ...entries2 });
|
|
1420
1823
|
}, [id]);
|
|
1421
1824
|
useEffect(() => {
|
|
1422
1825
|
return () => {
|
|
@@ -1437,6 +1840,25 @@ function SduiForm({
|
|
|
1437
1840
|
}
|
|
1438
1841
|
}, [slice, id, runtime]);
|
|
1439
1842
|
const dirty = Boolean(runtime.state[`${id}.$dirty`]);
|
|
1843
|
+
const latestState = useRef(runtime.state);
|
|
1844
|
+
latestState.current = runtime.state;
|
|
1845
|
+
const dirtyRegistry = useContext(SduiDirtyRegistryContext);
|
|
1846
|
+
useEffect(
|
|
1847
|
+
() => dirtyRegistry?.register(
|
|
1848
|
+
() => initialSnapshot.current !== null && sliceOf(latestState.current) !== initialSnapshot.current
|
|
1849
|
+
),
|
|
1850
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1851
|
+
[dirtyRegistry, id]
|
|
1852
|
+
);
|
|
1853
|
+
const leaveRegistry = useContext(SduiLeaveRegistryContext);
|
|
1854
|
+
useEffect(() => {
|
|
1855
|
+
if (!guard || !leaveRegistry) return;
|
|
1856
|
+
return leaveRegistry.register(
|
|
1857
|
+
id,
|
|
1858
|
+
() => initialSnapshot.current !== null && sliceOf(latestState.current) !== initialSnapshot.current
|
|
1859
|
+
);
|
|
1860
|
+
}, [guard, leaveRegistry, id]);
|
|
1861
|
+
const scope = useMemo(() => ({ id, rules: JSON.parse(rulesJson) }), [id, rulesJson]);
|
|
1440
1862
|
useEffect(() => {
|
|
1441
1863
|
if (!guard || !dirty) return;
|
|
1442
1864
|
const onBeforeUnload = (event) => {
|
|
@@ -1451,12 +1873,13 @@ function SduiForm({
|
|
|
1451
1873
|
{
|
|
1452
1874
|
className: ["wp-col", className].filter(Boolean).join(" "),
|
|
1453
1875
|
style,
|
|
1876
|
+
"data-sdui-form": id,
|
|
1454
1877
|
onSubmit: (event) => {
|
|
1455
1878
|
event.preventDefault();
|
|
1456
1879
|
onSubmit?.(event);
|
|
1457
1880
|
},
|
|
1458
1881
|
noValidate: true,
|
|
1459
|
-
children
|
|
1882
|
+
children: /* @__PURE__ */ jsx(SduiFormScopeContext.Provider, { value: scope, children })
|
|
1460
1883
|
}
|
|
1461
1884
|
);
|
|
1462
1885
|
}
|
|
@@ -1798,6 +2221,9 @@ function withProps(Component, defaults) {
|
|
|
1798
2221
|
function deferChainEntry(container, key) {
|
|
1799
2222
|
return typeof container.action === "string" && (key === "onSuccess" || key === "onError");
|
|
1800
2223
|
}
|
|
2224
|
+
function deferEachTarget(container, key) {
|
|
2225
|
+
return container.action === "request" && container.each !== void 0 && (key === "endpoint" || key === "body");
|
|
2226
|
+
}
|
|
1801
2227
|
function failureDetailOf(error) {
|
|
1802
2228
|
const message = error instanceof Error ? error.message : "\uC694\uCCAD\uC774 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4";
|
|
1803
2229
|
const carrier = error;
|
|
@@ -1817,12 +2243,14 @@ function runSuccessChain(kind, id, run) {
|
|
|
1817
2243
|
);
|
|
1818
2244
|
}
|
|
1819
2245
|
}
|
|
2246
|
+
var EMPTY_ENDPOINT_MESSAGE = "request \uC561\uC158\uC758 endpoint\uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4 \u2014 $state \uCC38\uC870\uC5D0 \uC544\uC9C1 \uAC12\uC774 \uC5C6\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694";
|
|
1820
2247
|
async function runRequestAction(action, deps) {
|
|
2248
|
+
if (action.each !== void 0) return runRequestEach(action, deps);
|
|
1821
2249
|
const pendingKey = typeof action.pendingKey === "string" && action.pendingKey !== "" ? action.pendingKey : null;
|
|
1822
2250
|
let pendingOn = false;
|
|
1823
2251
|
try {
|
|
1824
2252
|
if (typeof action.endpoint !== "string" || action.endpoint === "") {
|
|
1825
|
-
throw new Error(
|
|
2253
|
+
throw new Error(EMPTY_ENDPOINT_MESSAGE);
|
|
1826
2254
|
}
|
|
1827
2255
|
if (pendingKey) {
|
|
1828
2256
|
pendingOn = true;
|
|
@@ -1856,6 +2284,91 @@ async function runRequestAction(action, deps) {
|
|
|
1856
2284
|
if (pendingKey && pendingOn) deps.dispatch({ action: "setState", key: pendingKey, value: false });
|
|
1857
2285
|
}
|
|
1858
2286
|
}
|
|
2287
|
+
var REQUEST_EACH_CONCURRENCY = 3;
|
|
2288
|
+
async function mapWithLimit(items, limit, fn) {
|
|
2289
|
+
const results = new Array(items.length);
|
|
2290
|
+
let next = 0;
|
|
2291
|
+
const worker = async () => {
|
|
2292
|
+
while (next < items.length) {
|
|
2293
|
+
const index = next;
|
|
2294
|
+
next += 1;
|
|
2295
|
+
results[index] = await fn(items[index], index);
|
|
2296
|
+
}
|
|
2297
|
+
};
|
|
2298
|
+
const width = Math.max(1, Math.min(limit, items.length));
|
|
2299
|
+
await Promise.all(Array.from({ length: width }, () => worker()));
|
|
2300
|
+
return results;
|
|
2301
|
+
}
|
|
2302
|
+
function eachFailureMessage(total, failed, first) {
|
|
2303
|
+
const head = `${total}\uAC74 \uC911 ${failed}\uAC74 \uC2E4\uD328`;
|
|
2304
|
+
return first ? `${head} \u2014 ${first}` : head;
|
|
2305
|
+
}
|
|
2306
|
+
async function runRequestEach(action, deps) {
|
|
2307
|
+
const list = action.each;
|
|
2308
|
+
if (!Array.isArray(list) || list.length === 0) {
|
|
2309
|
+
warnOnce(
|
|
2310
|
+
"request:each:empty",
|
|
2311
|
+
"[sdui] request \uC561\uC158\uC758 each \uAC00 \uBC30\uC5F4\uC774 \uC544\uB2C8\uAC70\uB098 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4 \u2014 \uC694\uCCAD\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 (\uBC84\uD2BC\uC744 \uC120\uD0DD \uC218\uB85C \uC7A0\uADF8\uC138\uC694)",
|
|
2312
|
+
"warn"
|
|
2313
|
+
);
|
|
2314
|
+
return;
|
|
2315
|
+
}
|
|
2316
|
+
const pendingKey = typeof action.pendingKey === "string" && action.pendingKey !== "" ? action.pendingKey : null;
|
|
2317
|
+
const resolveFor = deps.resolveFor ?? ((value) => value);
|
|
2318
|
+
const method = action.method ?? "POST";
|
|
2319
|
+
if (pendingKey) deps.dispatch({ action: "setState", key: pendingKey, value: true });
|
|
2320
|
+
try {
|
|
2321
|
+
const outcomes = await mapWithLimit(list, REQUEST_EACH_CONCURRENCY, async (item, index) => {
|
|
2322
|
+
try {
|
|
2323
|
+
const endpoint = resolveFor(action.endpoint, item, index);
|
|
2324
|
+
if (typeof endpoint !== "string" || endpoint === "") throw new Error(EMPTY_ENDPOINT_MESSAGE);
|
|
2325
|
+
const body = action.body === void 0 ? void 0 : resolveFor(action.body, item, index);
|
|
2326
|
+
const value = await deps.fetch(endpoint, { method, ...body === void 0 ? {} : { body } });
|
|
2327
|
+
return { ok: true, value };
|
|
2328
|
+
} catch (error) {
|
|
2329
|
+
return { ok: false, error };
|
|
2330
|
+
}
|
|
2331
|
+
});
|
|
2332
|
+
const errors = [];
|
|
2333
|
+
outcomes.forEach((outcome, index) => {
|
|
2334
|
+
if (!outcome.ok) errors.push({ index, item: list[index], ...failureDetailOf(outcome.error) });
|
|
2335
|
+
});
|
|
2336
|
+
const succeeded = outcomes.length - errors.length;
|
|
2337
|
+
const invalidate = typeof action.invalidate === "string" && action.invalidate !== "" ? action.invalidate : null;
|
|
2338
|
+
if (errors.length === 0) {
|
|
2339
|
+
runSuccessChain("request", `each:${list.length}`, () => {
|
|
2340
|
+
if (invalidate) deps.dispatch({ action: "invalidate", prefix: invalidate });
|
|
2341
|
+
const responses = outcomes.map((outcome) => outcome.ok ? outcome.value : null);
|
|
2342
|
+
(action.onSuccess ?? []).forEach((a) => deps.dispatch(a, responses));
|
|
2343
|
+
});
|
|
2344
|
+
return;
|
|
2345
|
+
}
|
|
2346
|
+
if (succeeded > 0 && invalidate) {
|
|
2347
|
+
runSuccessChain("request", `each:${list.length}`, () => deps.dispatch({ action: "invalidate", prefix: invalidate }));
|
|
2348
|
+
}
|
|
2349
|
+
const failure = {
|
|
2350
|
+
failed: errors.length,
|
|
2351
|
+
succeeded,
|
|
2352
|
+
total: list.length,
|
|
2353
|
+
message: eachFailureMessage(list.length, errors.length, errors[0]?.message),
|
|
2354
|
+
errors
|
|
2355
|
+
};
|
|
2356
|
+
if (typeof action.errorKey === "string" && action.errorKey !== "") {
|
|
2357
|
+
deps.dispatch({ action: "setState", key: action.errorKey, value: failure });
|
|
2358
|
+
}
|
|
2359
|
+
if (action.onError?.length) {
|
|
2360
|
+
action.onError.forEach((a) => deps.dispatch(a, failure));
|
|
2361
|
+
} else {
|
|
2362
|
+
deps.toast({
|
|
2363
|
+
tone: "danger",
|
|
2364
|
+
title: `${list.length}\uAC74 \uC911 ${errors.length}\uAC74 \uC2E4\uD328`,
|
|
2365
|
+
message: errors[0]?.message
|
|
2366
|
+
});
|
|
2367
|
+
}
|
|
2368
|
+
} finally {
|
|
2369
|
+
if (pendingKey) deps.dispatch({ action: "setState", key: pendingKey, value: false });
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
1859
2372
|
|
|
1860
2373
|
// src/state-path.ts
|
|
1861
2374
|
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
@@ -2292,6 +2805,33 @@ function nearestArgsFrame(scope) {
|
|
|
2292
2805
|
}
|
|
2293
2806
|
return void 0;
|
|
2294
2807
|
}
|
|
2808
|
+
function toastText(value) {
|
|
2809
|
+
if (typeof value === "string") return value;
|
|
2810
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
2811
|
+
if (isPlainObject(value) && typeof value.message === "string") return value.message;
|
|
2812
|
+
return void 0;
|
|
2813
|
+
}
|
|
2814
|
+
var DEFAULT_LEAVE_MESSAGE = "\uC800\uC7A5\uD558\uC9C0 \uC54A\uC740 \uBCC0\uACBD \uC0AC\uD56D\uC774 \uC788\uC2B5\uB2C8\uB2E4. \uC774 \uD654\uBA74\uC744 \uB5A0\uB098\uBA74 \uC0AC\uB77C\uC9D1\uB2C8\uB2E4.";
|
|
2815
|
+
function leaveMessageOf(value) {
|
|
2816
|
+
if (typeof value === "string") return value.trim() === "" ? null : value;
|
|
2817
|
+
return value ? DEFAULT_LEAVE_MESSAGE : null;
|
|
2818
|
+
}
|
|
2819
|
+
function titleText(value) {
|
|
2820
|
+
if (typeof value === "string") return value.trim() === "" ? null : value;
|
|
2821
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
2822
|
+
return null;
|
|
2823
|
+
}
|
|
2824
|
+
function collectFormValues(state, formId) {
|
|
2825
|
+
const prefix = `${formId}.`;
|
|
2826
|
+
const values = {};
|
|
2827
|
+
for (const [key, value] of Object.entries(state)) {
|
|
2828
|
+
if (key.startsWith(prefix) && !key.slice(prefix.length).startsWith("$") && value !== void 0) {
|
|
2829
|
+
values[key.slice(prefix.length)] = value;
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
return values;
|
|
2833
|
+
}
|
|
2834
|
+
var TOAST_MODAL_CSS = ".wp-toast-region.wp-toast-region--modal{top:16px;bottom:auto}@media screen and (max-width:767.98px){.wp-toast-region.wp-toast-region--modal{top:calc(12px + env(safe-area-inset-top));left:12px;right:12px}}";
|
|
2295
2835
|
function hasExplicitPageHead(body) {
|
|
2296
2836
|
return Array.isArray(body) && body.some((child) => isPlainObject(child) && child.component === "PageHead");
|
|
2297
2837
|
}
|
|
@@ -2369,45 +2909,76 @@ function toIndex(v) {
|
|
|
2369
2909
|
function inRange(index, length) {
|
|
2370
2910
|
return index !== null && index >= 0 && index < length;
|
|
2371
2911
|
}
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2912
|
+
var CHOICE_KINDS = /* @__PURE__ */ new Set([
|
|
2913
|
+
"Select",
|
|
2914
|
+
"AutocompleteField",
|
|
2915
|
+
"PositionSelect",
|
|
2916
|
+
"TagMultiSelect",
|
|
2917
|
+
"TreePicker",
|
|
2918
|
+
"SearchableMultiPicker",
|
|
2919
|
+
"RoleMemberPicker",
|
|
2920
|
+
"SegmentedControl",
|
|
2921
|
+
"Radio",
|
|
2922
|
+
"DatePicker",
|
|
2923
|
+
"DateRangePicker",
|
|
2924
|
+
"TimePicker"
|
|
2925
|
+
]);
|
|
2926
|
+
function requiredVerb(kind) {
|
|
2927
|
+
if (kind && CHOICE_KINDS.has(kind)) return "\uC120\uD0DD\uD558\uC138\uC694";
|
|
2928
|
+
if (kind === "FileDropzone") return "\uCCA8\uBD80\uD558\uC138\uC694";
|
|
2929
|
+
if (kind === "Checkbox" || kind === "Switch") return "\uD655\uC778\uD558\uC138\uC694";
|
|
2930
|
+
return "\uC785\uB825\uD558\uC138\uC694";
|
|
2931
|
+
}
|
|
2932
|
+
function validateForm(values, config, customValidators, fieldInfo) {
|
|
2933
|
+
const out = { errors: {}, failed: {} };
|
|
2934
|
+
if (!config) return out;
|
|
2935
|
+
const labelOf2 = (field) => config.labels[field] ?? fieldInfo?.get(field)?.label;
|
|
2375
2936
|
for (const [field, rules] of Object.entries(config.rules)) {
|
|
2376
2937
|
const value = values[field];
|
|
2377
|
-
const
|
|
2938
|
+
const context = {
|
|
2939
|
+
label: labelOf2(field),
|
|
2940
|
+
kind: fieldInfo?.get(field)?.kind,
|
|
2941
|
+
labelOf: labelOf2
|
|
2942
|
+
};
|
|
2378
2943
|
for (const rule of rules) {
|
|
2379
|
-
const message = ruleError(rule, value, values,
|
|
2944
|
+
const message = ruleError(rule, value, values, context, customValidators);
|
|
2380
2945
|
if (message) {
|
|
2381
|
-
errors[field] = message;
|
|
2946
|
+
out.errors[field] = message;
|
|
2947
|
+
out.failed[field] = rule.rule;
|
|
2382
2948
|
break;
|
|
2383
2949
|
}
|
|
2384
2950
|
}
|
|
2385
2951
|
}
|
|
2386
|
-
return
|
|
2952
|
+
return out;
|
|
2387
2953
|
}
|
|
2388
|
-
function ruleError(rule, value, values, label, customValidators) {
|
|
2954
|
+
function ruleError(rule, value, values, { label, kind, labelOf: labelOf2 }, customValidators) {
|
|
2389
2955
|
const empty = value === void 0 || value === null || value === "" || Array.isArray(value) && value.length === 0 || // DateRangePicker의 {} 같은 "값 없는 객체"도 미입력으로 본다
|
|
2390
2956
|
typeof value === "object" && !Array.isArray(value) && Object.values(value).every((v) => v === void 0 || v === null || v === "");
|
|
2957
|
+
const subject = (pair) => label ? `${josa(label, pair)} ` : "";
|
|
2391
2958
|
switch (rule.rule) {
|
|
2392
2959
|
case "required":
|
|
2393
|
-
return empty || value === false ? rule.message ?? `${label
|
|
2960
|
+
return empty || value === false ? rule.message ?? (label ? `${josa(label, "\uC744/\uB97C")} ${requiredVerb(kind)}` : "\uD544\uC218 \uD56D\uBAA9\uC785\uB2C8\uB2E4") : null;
|
|
2394
2961
|
case "min":
|
|
2395
|
-
return !empty && toNumber(value) < rule.value ? rule.message ?? `${
|
|
2962
|
+
return !empty && toNumber(value) < rule.value ? rule.message ?? `${subject("\uC740/\uB294")}${rule.value} \uC774\uC0C1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4` : null;
|
|
2396
2963
|
case "max":
|
|
2397
|
-
return !empty && toNumber(value) > rule.value ? rule.message ?? `${
|
|
2964
|
+
return !empty && toNumber(value) > rule.value ? rule.message ?? `${subject("\uC740/\uB294")}${rule.value} \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4` : null;
|
|
2398
2965
|
case "minLength":
|
|
2399
|
-
return !empty && String(value).length < rule.value ? rule.message ?? `${
|
|
2966
|
+
return !empty && String(value).length < rule.value ? rule.message ?? `${subject("\uC740/\uB294")}${rule.value}\uC790 \uC774\uC0C1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4` : null;
|
|
2400
2967
|
case "maxLength":
|
|
2401
|
-
return !empty && String(value).length > rule.value ? rule.message ?? `${
|
|
2968
|
+
return !empty && String(value).length > rule.value ? rule.message ?? `${subject("\uC740/\uB294")}${rule.value}\uC790 \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4` : null;
|
|
2402
2969
|
case "pattern":
|
|
2403
|
-
return !empty && !new RegExp(rule.value).test(String(value)) ? rule.message ?? `${label} \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4` : null;
|
|
2970
|
+
return !empty && !new RegExp(rule.value).test(String(value)) ? rule.message ?? (label ? `${label} \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4` : "\uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4") : null;
|
|
2404
2971
|
case "notBeforeField": {
|
|
2405
2972
|
const other = values[rule.field];
|
|
2406
2973
|
if (empty || other === void 0 || other === null || other === "") return null;
|
|
2407
2974
|
const a = Number(String(value).replace(/,/g, ""));
|
|
2408
2975
|
const b = Number(String(other).replace(/,/g, ""));
|
|
2409
2976
|
const before = Number.isFinite(a) && Number.isFinite(b) && String(value).trim() !== "" ? a < b : String(value) < String(other);
|
|
2410
|
-
|
|
2977
|
+
if (!before) return null;
|
|
2978
|
+
if (rule.message) return rule.message;
|
|
2979
|
+
const otherLabel = labelOf2(rule.field);
|
|
2980
|
+
if (label && otherLabel) return `${josa(label, "\uC740/\uB294")} ${otherLabel}\uBCF4\uB2E4 \uC55E\uC124 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4`;
|
|
2981
|
+
return `${subject("\uC774/\uAC00")}\uC21C\uC11C\uC5D0 \uB9DE\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4`;
|
|
2411
2982
|
}
|
|
2412
2983
|
case "custom": {
|
|
2413
2984
|
const validator = customValidators?.[rule.name];
|
|
@@ -2420,6 +2991,122 @@ function ruleError(rule, value, values, label, customValidators) {
|
|
|
2420
2991
|
}
|
|
2421
2992
|
}
|
|
2422
2993
|
}
|
|
2994
|
+
function invalidSummary(result, labelOf2, order = []) {
|
|
2995
|
+
const rank = (field) => {
|
|
2996
|
+
const index = order.indexOf(field);
|
|
2997
|
+
return index === -1 ? Number.MAX_SAFE_INTEGER : index;
|
|
2998
|
+
};
|
|
2999
|
+
const fields = Object.keys(result.errors).sort((a, b) => rank(a) - rank(b));
|
|
3000
|
+
const count = fields.length;
|
|
3001
|
+
const allRequired = fields.every((field) => result.failed[field] === "required");
|
|
3002
|
+
const labels = fields.map(labelOf2).filter((l) => typeof l === "string" && l !== "");
|
|
3003
|
+
if (count === 1 && labels.length === 1) return { title: result.errors[fields[0]] };
|
|
3004
|
+
const title = allRequired ? `\uD544\uC218 \uD56D\uBAA9 ${count}\uAC1C\uB97C \uCC44\uC6CC \uC8FC\uC138\uC694` : `\uC785\uB825\uAC12 ${count}\uAC1C\uB97C \uD655\uC778\uD574 \uC8FC\uC138\uC694`;
|
|
3005
|
+
if (labels.length === 0) return { title, message: count === 1 ? void 0 : result.errors[fields[0]] };
|
|
3006
|
+
const shown = labels.slice(0, 4).join(", ");
|
|
3007
|
+
const rest = labels.length - 4;
|
|
3008
|
+
return { title, message: rest > 0 ? `${shown} \uC678 ${rest}\uAC1C` : shown };
|
|
3009
|
+
}
|
|
3010
|
+
var FOCUSABLE_IN_FIELD = 'input:not([type="hidden"]):not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
3011
|
+
function formElementOf(formId) {
|
|
3012
|
+
if (typeof document === "undefined") return void 0;
|
|
3013
|
+
return Array.from(document.querySelectorAll("form[data-sdui-form]")).find(
|
|
3014
|
+
(el) => el.getAttribute("data-sdui-form") === formId && el.isConnected
|
|
3015
|
+
);
|
|
3016
|
+
}
|
|
3017
|
+
function fieldOrderOf(formId) {
|
|
3018
|
+
const form = formElementOf(formId);
|
|
3019
|
+
if (!form) return [];
|
|
3020
|
+
return Array.from(form.querySelectorAll("[data-sdui-field]")).map(
|
|
3021
|
+
(el) => el.getAttribute("data-sdui-field") ?? ""
|
|
3022
|
+
);
|
|
3023
|
+
}
|
|
3024
|
+
function focusFirstInvalid(formId, fields) {
|
|
3025
|
+
const form = formElementOf(formId);
|
|
3026
|
+
if (!form) return;
|
|
3027
|
+
const wanted = new Set(fields);
|
|
3028
|
+
const candidates = form.querySelectorAll('[data-sdui-field], [aria-invalid]:not([aria-invalid="false"])');
|
|
3029
|
+
const hit = Array.from(candidates).find((el) => {
|
|
3030
|
+
const field = el.getAttribute("data-sdui-field");
|
|
3031
|
+
return field !== null ? wanted.has(field) : true;
|
|
3032
|
+
});
|
|
3033
|
+
if (!hit) return;
|
|
3034
|
+
const target = hit.matches(FOCUSABLE_IN_FIELD) ? hit : hit.querySelector(FOCUSABLE_IN_FIELD);
|
|
3035
|
+
hit.scrollIntoView?.({ block: "center", inline: "nearest" });
|
|
3036
|
+
target?.focus({ preventScroll: true });
|
|
3037
|
+
}
|
|
3038
|
+
var INVALID_PROP_COMPONENTS = /* @__PURE__ */ new Set([
|
|
3039
|
+
"Input",
|
|
3040
|
+
"Textarea",
|
|
3041
|
+
"Select",
|
|
3042
|
+
"DatePicker",
|
|
3043
|
+
"DateRangePicker",
|
|
3044
|
+
"TimePicker",
|
|
3045
|
+
"AutocompleteField",
|
|
3046
|
+
"JsonField",
|
|
3047
|
+
"PositionSelect"
|
|
3048
|
+
]);
|
|
3049
|
+
var BINDING_PROPS = ["value", "checked", "values"];
|
|
3050
|
+
function formKeyOf(value, formId) {
|
|
3051
|
+
if (!isStateRef(value)) return null;
|
|
3052
|
+
const prefix = `${formId}.`;
|
|
3053
|
+
if (!value.$state.startsWith(prefix)) return null;
|
|
3054
|
+
const key = value.$state.slice(prefix.length);
|
|
3055
|
+
return key === "" || key.startsWith("$") ? null : key;
|
|
3056
|
+
}
|
|
3057
|
+
function boundInputOf(node, formId, depth = 0) {
|
|
3058
|
+
if (!Array.isArray(node.children)) return null;
|
|
3059
|
+
for (const child of node.children) {
|
|
3060
|
+
if (!isPlainObject(child)) continue;
|
|
3061
|
+
const childNode = child;
|
|
3062
|
+
const props = isPlainObject(childNode.props) ? childNode.props : {};
|
|
3063
|
+
for (const name of BINDING_PROPS) {
|
|
3064
|
+
const key = formKeyOf(props[name], formId);
|
|
3065
|
+
if (key) return { key, kind: String(childNode.component) };
|
|
3066
|
+
}
|
|
3067
|
+
const component = childNode.component;
|
|
3068
|
+
if (component === "Field" || component === "Form" || component === "ForEach" || component === "Fragment") continue;
|
|
3069
|
+
if (depth < 4) {
|
|
3070
|
+
const found = boundInputOf(childNode, formId, depth + 1);
|
|
3071
|
+
if (found) return found;
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
return null;
|
|
3075
|
+
}
|
|
3076
|
+
function hasRequiredRule(rules) {
|
|
3077
|
+
return Array.isArray(rules) && rules.some((rule) => isPlainObject(rule) && rule.rule === "required");
|
|
3078
|
+
}
|
|
3079
|
+
function applyFormBinding(node, props, form, engine) {
|
|
3080
|
+
const raw = isPlainObject(node.props) ? node.props : {};
|
|
3081
|
+
const errorOf = (key) => {
|
|
3082
|
+
const errors = engine.resolveValue({ $state: `${form.id}.$errors` }, EMPTY_SCOPE);
|
|
3083
|
+
const message = isPlainObject(errors) ? errors[key] : void 0;
|
|
3084
|
+
return typeof message === "string" && message !== "" ? message : void 0;
|
|
3085
|
+
};
|
|
3086
|
+
if (node.component === "Field") {
|
|
3087
|
+
const bound = boundInputOf(node, form.id);
|
|
3088
|
+
if (!bound) return;
|
|
3089
|
+
if (!Object.hasOwn(raw, "required") && hasRequiredRule(form.rules[bound.key])) props.required = true;
|
|
3090
|
+
if (!Object.hasOwn(raw, "error")) {
|
|
3091
|
+
const message = errorOf(bound.key);
|
|
3092
|
+
if (message) props.error = message;
|
|
3093
|
+
}
|
|
3094
|
+
props["data-sdui-field"] = bound.key;
|
|
3095
|
+
engine.noteField(form.id, bound.key, {
|
|
3096
|
+
label: typeof props.label === "string" && props.label !== "" ? props.label : void 0,
|
|
3097
|
+
kind: bound.kind
|
|
3098
|
+
});
|
|
3099
|
+
return;
|
|
3100
|
+
}
|
|
3101
|
+
if (typeof node.component !== "string" || !INVALID_PROP_COMPONENTS.has(node.component)) return;
|
|
3102
|
+
if (Object.hasOwn(raw, "invalid")) return;
|
|
3103
|
+
for (const name of BINDING_PROPS) {
|
|
3104
|
+
const key = formKeyOf(raw[name], form.id);
|
|
3105
|
+
if (!key) continue;
|
|
3106
|
+
if (errorOf(key)) props.invalid = true;
|
|
3107
|
+
return;
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
2423
3110
|
var EMPTY_SCOPE = [];
|
|
2424
3111
|
var DENIED_PROPS2 = /* @__PURE__ */ new Set([
|
|
2425
3112
|
"dangerouslySetInnerHTML",
|
|
@@ -2452,6 +3139,7 @@ function SduiNodeRenderer({
|
|
|
2452
3139
|
engine
|
|
2453
3140
|
}) {
|
|
2454
3141
|
const { registry, resolveValue, dispatch, resolveAsset } = engine;
|
|
3142
|
+
const formScope = React9.useContext(SduiFormScopeContext);
|
|
2455
3143
|
const slots = isPlainObject(node.slots) ? node.slots : void 0;
|
|
2456
3144
|
const childList = childrenOf(node);
|
|
2457
3145
|
if (node.component === "ForEach") {
|
|
@@ -2516,6 +3204,10 @@ function SduiNodeRenderer({
|
|
|
2516
3204
|
props[name] = resolved;
|
|
2517
3205
|
}
|
|
2518
3206
|
}
|
|
3207
|
+
if (formScope) applyFormBinding(node, props, formScope, engine);
|
|
3208
|
+
if (typeof node.component === "string" && INVALID_PROP_COMPONENTS.has(node.component) && props.invalid !== void 0 && typeof props.invalid !== "boolean") {
|
|
3209
|
+
props.invalid = Boolean(props.invalid);
|
|
3210
|
+
}
|
|
2519
3211
|
const childLayout = childLayoutFrom(props);
|
|
2520
3212
|
if (childLayout) {
|
|
2521
3213
|
for (const name of CHILD_LAYOUT_PROPS) delete props[name];
|
|
@@ -2612,14 +3304,30 @@ function SduiFragmentBody({
|
|
|
2612
3304
|
return null;
|
|
2613
3305
|
}
|
|
2614
3306
|
const page = spec;
|
|
2615
|
-
if (page.data || page.urlState || page.subscribe) {
|
|
3307
|
+
if (page.data || page.urlState || page.subscribe || page.leaveGuard !== void 0 || page.titleFrom !== void 0) {
|
|
2616
3308
|
warnOnce(
|
|
2617
3309
|
`fragment:page-features:${id}`,
|
|
2618
|
-
`[sdui] Fragment \uC2A4\uD399 ${id}\uC758 data/urlState/subscribe\
|
|
3310
|
+
`[sdui] Fragment \uC2A4\uD399 ${id}\uC758 data/urlState/subscribe/leaveGuard/titleFrom\uC740 \uBB34\uC2DC\uB429\uB2C8\uB2E4 \u2014 \uC870\uAC01\uC740 body\uB9CC \uB80C\uB354\uD569\uB2C8\uB2E4`
|
|
2619
3311
|
);
|
|
2620
3312
|
}
|
|
2621
3313
|
return /* @__PURE__ */ jsx(SduiFragmentChainContext.Provider, { value: nextChain, children: page.body.map((child, i) => renderChild(child, i, bodyScope, engine)) });
|
|
2622
3314
|
}
|
|
3315
|
+
function SduiToastRegion({ hostId }) {
|
|
3316
|
+
const board = useSyncExternalStore(subscribeToasts, toastSnapshot, toastServerSnapshot);
|
|
3317
|
+
const toasts = useMemo(
|
|
3318
|
+
() => board.entries.filter((entry) => toastHostOf(entry, board.hosts) === hostId),
|
|
3319
|
+
[board, hostId]
|
|
3320
|
+
);
|
|
3321
|
+
useEffect(() => {
|
|
3322
|
+
if (toasts.length > 0) markToastsShown(toasts.map((t) => t.id));
|
|
3323
|
+
}, [toasts]);
|
|
3324
|
+
const modalOpen = useAnyModalOpen();
|
|
3325
|
+
if (toasts.length === 0) return null;
|
|
3326
|
+
return /* @__PURE__ */ jsxs("div", { className: modalOpen ? "wp-toast-region wp-toast-region--modal" : "wp-toast-region", children: [
|
|
3327
|
+
/* @__PURE__ */ jsx(HoistedStyle, { id: "wp-sdui-toast-modal", css: TOAST_MODAL_CSS }),
|
|
3328
|
+
toasts.map((t) => /* @__PURE__ */ jsx(Toast, { tone: t.tone, title: t.title, onClose: () => closeToastEntry(t.id), children: t.message }, t.id))
|
|
3329
|
+
] });
|
|
3330
|
+
}
|
|
2623
3331
|
function urlSeedSettled(state, seeded, baseline) {
|
|
2624
3332
|
return Object.keys(seeded).every(
|
|
2625
3333
|
(key) => Object.is(state[key], seeded[key]) || !Object.is(state[key], baseline[key])
|
|
@@ -2644,24 +3352,43 @@ function SduiRenderer({
|
|
|
2644
3352
|
const urlSeeded = useRef(false);
|
|
2645
3353
|
const urlSeedPending = useRef(null);
|
|
2646
3354
|
const urlHomePath = useRef(typeof window !== "undefined" ? window.location.pathname : "");
|
|
2647
|
-
const [toasts, setToasts] = useState([]);
|
|
2648
|
-
const toastSeq = useRef(0);
|
|
2649
|
-
const toastTimers = useRef([]);
|
|
2650
3355
|
const stateRef = useRef(state);
|
|
2651
3356
|
stateRef.current = state;
|
|
2652
3357
|
const formsRef = useRef(/* @__PURE__ */ new Map());
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
3358
|
+
const fieldInfoRef = useRef(/* @__PURE__ */ new Map());
|
|
3359
|
+
const noteField = useCallback((formId, key, info) => {
|
|
3360
|
+
let fields = fieldInfoRef.current.get(formId);
|
|
3361
|
+
if (!fields) {
|
|
3362
|
+
fields = /* @__PURE__ */ new Map();
|
|
3363
|
+
fieldInfoRef.current.set(formId, fields);
|
|
3364
|
+
}
|
|
3365
|
+
fields.set(key, info);
|
|
2656
3366
|
}, []);
|
|
3367
|
+
const [toastHostId] = useState(allocateToastHostId);
|
|
3368
|
+
const toastChannel = React9.useContext(OverlayContainerContext);
|
|
3369
|
+
useEffect(() => registerToastHost(toastHostId, toastChannel), [toastHostId, toastChannel]);
|
|
2657
3370
|
useEffect(() => {
|
|
2658
3371
|
if (!DEV) return;
|
|
2659
3372
|
const issues = validateSpec(page, new Set(Object.keys(registry)));
|
|
2660
3373
|
issues.forEach((issue) => console.warn(`[sdui:spec] ${issue.path} \u2014 ${issue.message}`));
|
|
2661
3374
|
}, [page, registry]);
|
|
2662
|
-
const
|
|
2663
|
-
|
|
2664
|
-
|
|
3375
|
+
const leaveFormsRef = useRef(/* @__PURE__ */ new Map());
|
|
3376
|
+
const leaveRegistry = useMemo(
|
|
3377
|
+
() => ({
|
|
3378
|
+
register: (formId, isDirty) => {
|
|
3379
|
+
leaveFormsRef.current.set(formId, isDirty);
|
|
3380
|
+
return () => {
|
|
3381
|
+
if (leaveFormsRef.current.get(formId) === isDirty) leaveFormsRef.current.delete(formId);
|
|
3382
|
+
};
|
|
3383
|
+
}
|
|
3384
|
+
}),
|
|
3385
|
+
[]
|
|
3386
|
+
);
|
|
3387
|
+
const leavingRef = useRef(false);
|
|
3388
|
+
const setEntries = useCallback((entries2) => {
|
|
3389
|
+
stateRef.current = { ...stateRef.current, ...entries2 };
|
|
3390
|
+
leavingRef.current = false;
|
|
3391
|
+
setState((prev) => ({ ...prev, ...entries2 }));
|
|
2665
3392
|
}, []);
|
|
2666
3393
|
useEffect(() => {
|
|
2667
3394
|
if (!syncUrlState || urlSeeded.current || !page.urlState?.length || typeof window === "undefined") return;
|
|
@@ -2685,6 +3412,16 @@ function SduiRenderer({
|
|
|
2685
3412
|
setEntries(seeded);
|
|
2686
3413
|
}
|
|
2687
3414
|
}, [page.urlState, setEntries, syncUrlState]);
|
|
3415
|
+
const [urlResync, setUrlResync] = useState(0);
|
|
3416
|
+
const hasUrlState = Boolean(page.urlState?.length);
|
|
3417
|
+
useEffect(() => {
|
|
3418
|
+
if (!syncUrlState || !hasUrlState || typeof window === "undefined") return;
|
|
3419
|
+
const onPop = () => {
|
|
3420
|
+
if (window.location.pathname === urlHomePath.current) setUrlResync((n) => n + 1);
|
|
3421
|
+
};
|
|
3422
|
+
window.addEventListener("popstate", onPop);
|
|
3423
|
+
return () => window.removeEventListener("popstate", onPop);
|
|
3424
|
+
}, [syncUrlState, hasUrlState]);
|
|
2688
3425
|
useEffect(() => {
|
|
2689
3426
|
if (!syncUrlState || !page.urlState?.length || !urlSeeded.current || typeof window === "undefined") return;
|
|
2690
3427
|
if (window.location.pathname !== urlHomePath.current) return;
|
|
@@ -2720,7 +3457,7 @@ function SduiRenderer({
|
|
|
2720
3457
|
);
|
|
2721
3458
|
}
|
|
2722
3459
|
}
|
|
2723
|
-
}, [state, page.urlState, syncUrlState]);
|
|
3460
|
+
}, [state, page.urlState, syncUrlState, urlResync]);
|
|
2724
3461
|
const subscribeChannels = page.subscribe?.join(",") ?? "";
|
|
2725
3462
|
const hasSubscribe = typeof host.subscribe === "function";
|
|
2726
3463
|
useEffect(() => {
|
|
@@ -2741,16 +3478,11 @@ function SduiRenderer({
|
|
|
2741
3478
|
unsubscribe();
|
|
2742
3479
|
};
|
|
2743
3480
|
}, [subscribeChannels, hasSubscribe]);
|
|
2744
|
-
const closeToast = useCallback((id) => {
|
|
2745
|
-
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
2746
|
-
}, []);
|
|
2747
3481
|
const pushToast = useCallback(
|
|
2748
3482
|
(toast) => {
|
|
2749
|
-
|
|
2750
|
-
setToasts((prev) => [...prev, { ...toast, id }]);
|
|
2751
|
-
toastTimers.current.push(setTimeout(() => closeToast(id), 4e3));
|
|
3483
|
+
pushToastEntry(toastHostId, toastChannel, toast);
|
|
2752
3484
|
},
|
|
2753
|
-
[
|
|
3485
|
+
[toastHostId, toastChannel]
|
|
2754
3486
|
);
|
|
2755
3487
|
const lookupState = useCallback((key) => {
|
|
2756
3488
|
const current = stateRef.current;
|
|
@@ -2811,7 +3543,7 @@ function SduiRenderer({
|
|
|
2811
3543
|
if (isPlainObject(value)) {
|
|
2812
3544
|
const out = {};
|
|
2813
3545
|
for (const [k, v] of Object.entries(value)) {
|
|
2814
|
-
out[k] = deferChainEntry(value, k) || deferRunEntry(k, v) ? v : resolveValue(v, scope);
|
|
3546
|
+
out[k] = deferChainEntry(value, k) || deferRunEntry(k, v) || deferEachTarget(value, k) ? v : resolveValue(v, scope);
|
|
2815
3547
|
}
|
|
2816
3548
|
return out;
|
|
2817
3549
|
}
|
|
@@ -2819,6 +3551,76 @@ function SduiRenderer({
|
|
|
2819
3551
|
},
|
|
2820
3552
|
[lookupState]
|
|
2821
3553
|
);
|
|
3554
|
+
const leaveMessageNow = useCallback(() => {
|
|
3555
|
+
if (leavingRef.current) return null;
|
|
3556
|
+
if (page.leaveGuard !== void 0) {
|
|
3557
|
+
const message = leaveMessageOf(resolveValue(page.leaveGuard, EMPTY_SCOPE));
|
|
3558
|
+
if (message) return message;
|
|
3559
|
+
}
|
|
3560
|
+
for (const [formId, isDirty] of leaveFormsRef.current) {
|
|
3561
|
+
if (stateRef.current[`${formId}.$resetDirty`] === true) continue;
|
|
3562
|
+
if (isDirty()) return DEFAULT_LEAVE_MESSAGE;
|
|
3563
|
+
}
|
|
3564
|
+
return null;
|
|
3565
|
+
}, [page.leaveGuard, resolveValue]);
|
|
3566
|
+
const leaveSentRef = useRef(null);
|
|
3567
|
+
const unloadHandlerRef = useRef(null);
|
|
3568
|
+
const syncLeaveGuard = useCallback(() => {
|
|
3569
|
+
const message = leaveMessageNow();
|
|
3570
|
+
if (message === leaveSentRef.current) return message;
|
|
3571
|
+
leaveSentRef.current = message;
|
|
3572
|
+
hostRef.current.setLeaveGuard?.(message);
|
|
3573
|
+
if (typeof window === "undefined") return message;
|
|
3574
|
+
if (message !== null && !unloadHandlerRef.current) {
|
|
3575
|
+
const handler = (event) => {
|
|
3576
|
+
event.preventDefault();
|
|
3577
|
+
event.returnValue = "";
|
|
3578
|
+
};
|
|
3579
|
+
window.addEventListener("beforeunload", handler);
|
|
3580
|
+
unloadHandlerRef.current = handler;
|
|
3581
|
+
} else if (message === null && unloadHandlerRef.current) {
|
|
3582
|
+
window.removeEventListener("beforeunload", unloadHandlerRef.current);
|
|
3583
|
+
unloadHandlerRef.current = null;
|
|
3584
|
+
}
|
|
3585
|
+
return message;
|
|
3586
|
+
}, [leaveMessageNow]);
|
|
3587
|
+
useEffect(() => {
|
|
3588
|
+
syncLeaveGuard();
|
|
3589
|
+
});
|
|
3590
|
+
useEffect(
|
|
3591
|
+
() => () => {
|
|
3592
|
+
if (leaveSentRef.current !== null) {
|
|
3593
|
+
leaveSentRef.current = null;
|
|
3594
|
+
hostRef.current.setLeaveGuard?.(null);
|
|
3595
|
+
}
|
|
3596
|
+
if (unloadHandlerRef.current && typeof window !== "undefined") {
|
|
3597
|
+
window.removeEventListener("beforeunload", unloadHandlerRef.current);
|
|
3598
|
+
unloadHandlerRef.current = null;
|
|
3599
|
+
}
|
|
3600
|
+
},
|
|
3601
|
+
[]
|
|
3602
|
+
);
|
|
3603
|
+
const [pendingLeave, setPendingLeave] = useState(null);
|
|
3604
|
+
const titleFrom = page.titleFrom;
|
|
3605
|
+
const resolvedTitle = useMemo(
|
|
3606
|
+
() => titleFrom === void 0 ? null : titleText(resolveValue(titleFrom, EMPTY_SCOPE)),
|
|
3607
|
+
// state 가 바뀌어야 다시 푼다 — resolveValue 는 ref 를 읽어 정체성이 안 바뀐다.
|
|
3608
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
3609
|
+
[titleFrom, resolveValue, state]
|
|
3610
|
+
);
|
|
3611
|
+
const effectiveTitle = resolvedTitle ?? page.title;
|
|
3612
|
+
useEffect(() => {
|
|
3613
|
+
if (titleFrom === void 0) return;
|
|
3614
|
+
hostRef.current.setTitle?.(effectiveTitle ?? null);
|
|
3615
|
+
}, [titleFrom, effectiveTitle]);
|
|
3616
|
+
useEffect(
|
|
3617
|
+
() => () => {
|
|
3618
|
+
if (titleFrom !== void 0) hostRef.current.setTitle?.(null);
|
|
3619
|
+
},
|
|
3620
|
+
// 마운트 1회 계약 — 라우트가 바뀌면 호스트가 key 로 갈아 끼운다.
|
|
3621
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
3622
|
+
[]
|
|
3623
|
+
);
|
|
2822
3624
|
const dataPlan = useMemo(
|
|
2823
3625
|
() => planPageData(page.data, (value) => resolveValue(value, EMPTY_SCOPE)),
|
|
2824
3626
|
// state를 의존성에 두어야 $state를 참조하는 경로가 값 변화를 따라간다.
|
|
@@ -2908,6 +3710,34 @@ function SduiRenderer({
|
|
|
2908
3710
|
revertUnsettledStamps(fetchedRef.current, runnable, epochs, settled);
|
|
2909
3711
|
};
|
|
2910
3712
|
}, [dataPlanKey, dataEpochKey, setEntries]);
|
|
3713
|
+
const revalidateField = useCallback(
|
|
3714
|
+
(key) => {
|
|
3715
|
+
for (const [formId, config] of formsRef.current) {
|
|
3716
|
+
const prefix = `${formId}.`;
|
|
3717
|
+
if (!key.startsWith(prefix)) continue;
|
|
3718
|
+
const field = key.slice(prefix.length);
|
|
3719
|
+
if (field === "" || field.startsWith("$")) continue;
|
|
3720
|
+
const errors = stateRef.current[`${formId}.$errors`];
|
|
3721
|
+
if (!isPlainObject(errors) || !Object.hasOwn(errors, field)) continue;
|
|
3722
|
+
const next = { ...errors };
|
|
3723
|
+
try {
|
|
3724
|
+
const result = validateForm(
|
|
3725
|
+
collectFormValues(stateRef.current, formId),
|
|
3726
|
+
config,
|
|
3727
|
+
customValidators,
|
|
3728
|
+
fieldInfoRef.current.get(formId)
|
|
3729
|
+
);
|
|
3730
|
+
const message = result.errors[field];
|
|
3731
|
+
if (message) next[field] = message;
|
|
3732
|
+
else delete next[field];
|
|
3733
|
+
} catch {
|
|
3734
|
+
delete next[field];
|
|
3735
|
+
}
|
|
3736
|
+
setEntries({ [`${formId}.$errors`]: next });
|
|
3737
|
+
}
|
|
3738
|
+
},
|
|
3739
|
+
[customValidators, setEntries]
|
|
3740
|
+
);
|
|
2911
3741
|
const dispatchRef = useRef(() => {
|
|
2912
3742
|
});
|
|
2913
3743
|
const dispatch = useCallback(
|
|
@@ -2924,17 +3754,24 @@ function SduiRenderer({
|
|
|
2924
3754
|
eventScope
|
|
2925
3755
|
);
|
|
2926
3756
|
switch (action.action) {
|
|
2927
|
-
case "navigate":
|
|
3757
|
+
case "navigate": {
|
|
2928
3758
|
if (!isSafeNavigationTarget(action.to)) {
|
|
2929
3759
|
warnOnce(`navigate:${String(action.to)}`, `[sdui] \uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 navigate \uB300\uC0C1: ${String(action.to)}`);
|
|
2930
3760
|
break;
|
|
2931
3761
|
}
|
|
3762
|
+
const leaveMessage = syncLeaveGuard();
|
|
3763
|
+
if (leaveMessage !== null && typeof hostRef.current.setLeaveGuard !== "function") {
|
|
3764
|
+
setPendingLeave({ to: action.to, message: leaveMessage });
|
|
3765
|
+
break;
|
|
3766
|
+
}
|
|
2932
3767
|
hostRef.current.navigate(action.to);
|
|
2933
3768
|
break;
|
|
3769
|
+
}
|
|
2934
3770
|
case "setState": {
|
|
2935
3771
|
const nextValue = action.fromEvent ? eventValue(arg) : action.value;
|
|
2936
3772
|
if (!Array.isArray(action.path) || action.path.length === 0) {
|
|
2937
3773
|
setEntries({ [action.key]: nextValue });
|
|
3774
|
+
revalidateField(action.key);
|
|
2938
3775
|
break;
|
|
2939
3776
|
}
|
|
2940
3777
|
const written = setAtPath(
|
|
@@ -2950,6 +3787,7 @@ function SduiRenderer({
|
|
|
2950
3787
|
break;
|
|
2951
3788
|
}
|
|
2952
3789
|
setEntries({ [action.key]: written.next });
|
|
3790
|
+
revalidateField(action.key);
|
|
2953
3791
|
break;
|
|
2954
3792
|
}
|
|
2955
3793
|
case "open":
|
|
@@ -2959,7 +3797,7 @@ function SduiRenderer({
|
|
|
2959
3797
|
setEntries({ [action.key]: false });
|
|
2960
3798
|
break;
|
|
2961
3799
|
case "toast":
|
|
2962
|
-
pushToast({ tone: action.tone ?? "info", title: action.title, message: action.message });
|
|
3800
|
+
pushToast({ tone: action.tone ?? "info", title: toastText(action.title), message: toastText(action.message) });
|
|
2963
3801
|
break;
|
|
2964
3802
|
case "arrayAppend": {
|
|
2965
3803
|
const current = stateRef.current[action.key];
|
|
@@ -3026,16 +3864,21 @@ function SduiRenderer({
|
|
|
3026
3864
|
let phase = "validate";
|
|
3027
3865
|
let pendingOn = false;
|
|
3028
3866
|
try {
|
|
3029
|
-
const
|
|
3030
|
-
const
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
}
|
|
3035
|
-
}
|
|
3036
|
-
const errors = validateForm(values, formsRef.current.get(formId), customValidators);
|
|
3867
|
+
const values = collectFormValues(stateRef.current, formId);
|
|
3868
|
+
const config = formsRef.current.get(formId);
|
|
3869
|
+
const fieldInfo = fieldInfoRef.current.get(formId);
|
|
3870
|
+
const result = validateForm(values, config, customValidators, fieldInfo);
|
|
3871
|
+
const errors = result.errors;
|
|
3037
3872
|
if (Object.keys(errors).length > 0) {
|
|
3038
3873
|
setEntries({ [`${formId}.$errors`]: errors, [`${formId}.$submitting`]: false });
|
|
3874
|
+
const summary = invalidSummary(
|
|
3875
|
+
result,
|
|
3876
|
+
(field) => config?.labels[field] ?? fieldInfo?.get(field)?.label,
|
|
3877
|
+
fieldOrderOf(formId)
|
|
3878
|
+
);
|
|
3879
|
+
pushToast({ tone: "warning", title: summary.title, message: summary.message });
|
|
3880
|
+
const fields = Object.keys(errors);
|
|
3881
|
+
setTimeout(() => focusFirstInvalid(formId, fields), 0);
|
|
3039
3882
|
return;
|
|
3040
3883
|
}
|
|
3041
3884
|
setEntries({ [`${formId}.$errors`]: {}, [`${formId}.$submitting`]: true });
|
|
@@ -3080,7 +3923,10 @@ function SduiRenderer({
|
|
|
3080
3923
|
// scope 를 잇는다 — request 의 onSuccess/onError 도 같은 자리의 텍스트다.
|
|
3081
3924
|
// {"$arg"} 체인 항목은 dispatch 진입부가 이 스코프로 펼친다(submitForm과 동일).
|
|
3082
3925
|
dispatch: (a, value) => dispatchRef.current(a, value, scope),
|
|
3083
|
-
toast: pushToast
|
|
3926
|
+
toast: pushToast,
|
|
3927
|
+
// each: 원소를 행 프레임으로 얹어 endpoint·body 를 원소마다 푼다 — 같은 자리의
|
|
3928
|
+
// $state·$arg·$event 는 그대로 보이고, $item/$index 만 이 원소를 가리킨다.
|
|
3929
|
+
resolveFor: (value, item, index) => resolveValue(value, [...eventScope, { item, index }])
|
|
3084
3930
|
});
|
|
3085
3931
|
break;
|
|
3086
3932
|
case "invalidate": {
|
|
@@ -3127,12 +3973,12 @@ function SduiRenderer({
|
|
|
3127
3973
|
break;
|
|
3128
3974
|
}
|
|
3129
3975
|
},
|
|
3130
|
-
[pushToast, onCustomAction, customValidators, setEntries, resolveValue]
|
|
3976
|
+
[pushToast, onCustomAction, customValidators, setEntries, resolveValue, revalidateField, syncLeaveGuard]
|
|
3131
3977
|
);
|
|
3132
3978
|
dispatchRef.current = dispatch;
|
|
3133
3979
|
const engine = useMemo(
|
|
3134
|
-
() => ({ registry, resolveValue, dispatch, resolveAsset: assetResolverOf(host) }),
|
|
3135
|
-
[registry, resolveValue, dispatch, host]
|
|
3980
|
+
() => ({ registry, resolveValue, dispatch, resolveAsset: assetResolverOf(host), noteField }),
|
|
3981
|
+
[registry, resolveValue, dispatch, host, noteField]
|
|
3136
3982
|
);
|
|
3137
3983
|
const runtime = useMemo(
|
|
3138
3984
|
() => ({
|
|
@@ -3150,15 +3996,43 @@ function SduiRenderer({
|
|
|
3150
3996
|
return (
|
|
3151
3997
|
// 해석된 어댑터를 트리에 흘려보낸다 — dispatch를 거치지 않고 스스로 요청을 내는
|
|
3152
3998
|
// 컴포넌트(목록 조회 등)도 호스트가 건 잠금 안에서 움직이게 하기 위해서다(adapter.ts 주석).
|
|
3153
|
-
/* @__PURE__ */ jsx(SduiAdapterContext.Provider, { value: host, children: /* @__PURE__ */ jsx(SduiDispatchContext.Provider, { value: dispatch, children: /* @__PURE__ */ jsx(SduiRuntimeContext.Provider, { value: runtime, children: /* @__PURE__ */ jsx(SduiPermissionProvider, { can, children: /* @__PURE__ */ jsxs("div", { className: "sdui-root", children: [
|
|
3999
|
+
/* @__PURE__ */ jsx(SduiAdapterContext.Provider, { value: host, children: /* @__PURE__ */ jsx(SduiDispatchContext.Provider, { value: dispatch, children: /* @__PURE__ */ jsx(SduiRuntimeContext.Provider, { value: runtime, children: /* @__PURE__ */ jsx(SduiPermissionProvider, { can, children: /* @__PURE__ */ jsx(SduiLeaveRegistryContext.Provider, { value: leaveRegistry, children: /* @__PURE__ */ jsxs("div", { className: "sdui-root", children: [
|
|
3154
4000
|
/* @__PURE__ */ jsx(HoistedStyle, { id: "wp-sdui-layout", css: LAYOUT_CSS }),
|
|
3155
|
-
!hasExplicitPageHead(page.body) && (
|
|
3156
|
-
|
|
4001
|
+
!hasExplicitPageHead(page.body) && (effectiveTitle || page.subtitle) ? /* @__PURE__ */ jsx("div", { className: "wp-page-head", children: /* @__PURE__ */ jsxs("div", { className: "wp-page-head__text", children: [
|
|
4002
|
+
effectiveTitle ? /* @__PURE__ */ jsx("h1", { className: "wp-page-head__title", children: effectiveTitle }) : null,
|
|
3157
4003
|
page.subtitle ? /* @__PURE__ */ jsx("div", { className: "wp-page-head__sub", children: page.subtitle }) : null
|
|
3158
4004
|
] }) }) : null,
|
|
3159
4005
|
/* @__PURE__ */ jsx("div", { className: "wp-col", children: Array.isArray(page.body) ? page.body.map((child, i) => renderChild(child, i, EMPTY_SCOPE, engine)) : /* @__PURE__ */ jsx(SpecErrorCard, { children: "[sdui] page.body\uB294 \uBC30\uC5F4\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4" }) }),
|
|
3160
|
-
|
|
3161
|
-
|
|
4006
|
+
/* @__PURE__ */ jsx(SduiToastRegion, { hostId: toastHostId }),
|
|
4007
|
+
pendingLeave ? /* @__PURE__ */ jsx(OverlayLayer, { onClose: () => setPendingLeave(null), children: /* @__PURE__ */ jsx(
|
|
4008
|
+
Dialog,
|
|
4009
|
+
{
|
|
4010
|
+
size: "sm",
|
|
4011
|
+
icon: "triangle-alert",
|
|
4012
|
+
tone: "danger",
|
|
4013
|
+
title: "\uC800\uC7A5\uD558\uC9C0 \uC54A\uACE0 \uB098\uAC08\uAE4C\uC694?",
|
|
4014
|
+
onClose: () => setPendingLeave(null),
|
|
4015
|
+
footer: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4016
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", "data-autofocus": "", onClick: () => setPendingLeave(null), children: "\uACC4\uC18D \uC791\uC131" }),
|
|
4017
|
+
/* @__PURE__ */ jsx(
|
|
4018
|
+
Button,
|
|
4019
|
+
{
|
|
4020
|
+
variant: "danger",
|
|
4021
|
+
onClick: () => {
|
|
4022
|
+
const { to } = pendingLeave;
|
|
4023
|
+
setPendingLeave(null);
|
|
4024
|
+
leavingRef.current = true;
|
|
4025
|
+
syncLeaveGuard();
|
|
4026
|
+
hostRef.current.navigate(to);
|
|
4027
|
+
},
|
|
4028
|
+
children: "\uB098\uAC00\uAE30"
|
|
4029
|
+
}
|
|
4030
|
+
)
|
|
4031
|
+
] }),
|
|
4032
|
+
children: pendingLeave.message
|
|
4033
|
+
}
|
|
4034
|
+
) }) : null
|
|
4035
|
+
] }) }) }) }) }) })
|
|
3162
4036
|
);
|
|
3163
4037
|
}
|
|
3164
4038
|
|
|
@@ -3219,4 +4093,4 @@ async function resolvePatternSpec(id, load) {
|
|
|
3219
4093
|
// src/icon-names.ts
|
|
3220
4094
|
var ICON_NAMES = Object.keys(ICON_NODES);
|
|
3221
4095
|
|
|
3222
|
-
export { Can, ICON_NAMES, NodeErrorBoundary, RemoteSearchResults, ResultTable, SduiForm, SduiRenderer, SduiSearchDataProvider, SduiSearchInvalidationProvider, SduiSearchResults, SearchResults, SearchSummaryCards, SortHandle, SortableList, cellRenderer, createRegistry, createSearchInvalidator, defaultRegistry, imageRenderer, isValidSpecId, mapActions, mapCardRows, mapColumns, mapGridCard, mapViews, matchesCondition, normalizePageSizes, patternSpecCandidates, resolvePatternSpec, sortableDropIndex, sortableMove, summaryParams, useRemoteSearchPage, useRemoteSearchSummary,
|
|
4096
|
+
export { Can, ICON_NAMES, NodeErrorBoundary, ORDER_PARAM, RemoteSearchResults, ResultEmptyState, ResultTable, SORT_PARAM, SduiForm, SduiRenderer, SduiSearchDataProvider, SduiSearchInvalidationProvider, SduiSearchResults, SearchResults, SearchSummaryCards, SortHandle, SortableList, cellRenderer, conditionParams, createRegistry, createSearchInvalidator, createServerSortMemory, defaultRegistry, imageRenderer, isValidSpecId, mapActions, mapCardRows, mapColumns, mapGridCard, mapViews, matchesCondition, nextSort, normalizePageSizes, patternSpecCandidates, resolvePatternSpec, serverSortMemory, sortEchoOf, sortFromParams, sortableDropIndex, sortableMove, summaryParams, useRemoteSearchPage, useRemoteSearchSummary, useSearchFetcher, validateSpec, withProps, withoutSort };
|