@simplysm/solid 13.0.62 → 13.0.64
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/components/data/crud-detail/CrudDetail.d.ts.map +1 -1
- package/dist/components/data/crud-detail/CrudDetail.js +62 -41
- package/dist/components/data/crud-detail/CrudDetail.js.map +2 -2
- package/dist/components/data/crud-sheet/CrudSheet.d.ts.map +1 -1
- package/dist/components/data/crud-sheet/CrudSheet.js +164 -19
- package/dist/components/data/crud-sheet/CrudSheet.js.map +2 -2
- package/dist/components/data/crud-sheet/types.d.ts +9 -3
- package/dist/components/data/crud-sheet/types.d.ts.map +1 -1
- package/dist/components/data/sheet/DataSheet.d.ts.map +1 -1
- package/dist/components/data/sheet/DataSheet.js +3 -2
- package/dist/components/data/sheet/DataSheet.js.map +2 -2
- package/dist/components/form-control/checkbox/Checkbox.d.ts.map +1 -1
- package/dist/components/form-control/checkbox/Checkbox.js +10 -10
- package/dist/components/form-control/checkbox/Checkbox.js.map +2 -2
- package/dist/components/form-control/checkbox/Checkbox.styles.d.ts.map +1 -1
- package/dist/components/form-control/checkbox/Checkbox.styles.js +2 -2
- package/dist/components/form-control/checkbox/Checkbox.styles.js.map +1 -1
- package/dist/components/form-control/checkbox/Radio.d.ts.map +1 -1
- package/dist/components/form-control/checkbox/Radio.js +13 -13
- package/dist/components/form-control/checkbox/Radio.js.map +2 -2
- package/dist/components/form-control/data-select-button/DataSelectButton.d.ts +38 -0
- package/dist/components/form-control/data-select-button/DataSelectButton.d.ts.map +1 -0
- package/dist/components/form-control/data-select-button/DataSelectButton.js +184 -0
- package/dist/components/form-control/data-select-button/DataSelectButton.js.map +6 -0
- package/dist/components/form-control/select/Select.d.ts +7 -3
- package/dist/components/form-control/select/Select.d.ts.map +1 -1
- package/dist/components/form-control/select/Select.js +146 -45
- package/dist/components/form-control/select/Select.js.map +2 -2
- package/dist/components/form-control/select-list/SelectList.d.ts +54 -0
- package/dist/components/form-control/select-list/SelectList.d.ts.map +1 -0
- package/dist/components/form-control/select-list/SelectList.js +280 -0
- package/dist/components/form-control/select-list/SelectList.js.map +6 -0
- package/dist/components/form-control/select-list/SelectListContext.d.ts +13 -0
- package/dist/components/form-control/select-list/SelectListContext.d.ts.map +1 -0
- package/dist/components/form-control/select-list/SelectListContext.js +14 -0
- package/dist/components/form-control/select-list/SelectListContext.js.map +6 -0
- package/dist/components/form-control/shared-data/SharedDataSelect.d.ts +32 -0
- package/dist/components/form-control/shared-data/SharedDataSelect.d.ts.map +1 -0
- package/dist/components/form-control/shared-data/SharedDataSelect.js +74 -0
- package/dist/components/form-control/shared-data/SharedDataSelect.js.map +6 -0
- package/dist/components/form-control/shared-data/SharedDataSelectButton.d.ts +29 -0
- package/dist/components/form-control/shared-data/SharedDataSelectButton.d.ts.map +1 -0
- package/dist/components/form-control/shared-data/SharedDataSelectButton.js +17 -0
- package/dist/components/form-control/shared-data/SharedDataSelectButton.js.map +6 -0
- package/dist/components/form-control/shared-data/SharedDataSelectList.d.ts +29 -0
- package/dist/components/form-control/shared-data/SharedDataSelectList.d.ts.map +1 -0
- package/dist/components/form-control/shared-data/SharedDataSelectList.js +80 -0
- package/dist/components/form-control/shared-data/SharedDataSelectList.js.map +6 -0
- package/dist/features/address/AddressSearch.d.ts +8 -0
- package/dist/features/address/AddressSearch.d.ts.map +1 -0
- package/dist/features/address/AddressSearch.js +72 -0
- package/dist/features/address/AddressSearch.js.map +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/providers/shared-data/SharedDataContext.d.ts +14 -0
- package/dist/providers/shared-data/SharedDataContext.d.ts.map +1 -1
- package/dist/providers/shared-data/SharedDataContext.js.map +1 -1
- package/dist/providers/shared-data/SharedDataProvider.d.ts.map +1 -1
- package/dist/providers/shared-data/SharedDataProvider.js +5 -1
- package/dist/providers/shared-data/SharedDataProvider.js.map +2 -2
- package/docs/data-components.md +15 -4
- package/docs/form-controls.md +257 -0
- package/docs/hooks.md +30 -0
- package/docs/providers.md +7 -0
- package/package.json +3 -3
- package/src/components/data/crud-detail/CrudDetail.tsx +51 -26
- package/src/components/data/crud-sheet/CrudSheet.tsx +157 -20
- package/src/components/data/crud-sheet/types.ts +13 -3
- package/src/components/data/sheet/DataSheet.tsx +6 -7
- package/src/components/form-control/checkbox/Checkbox.styles.ts +2 -2
- package/src/components/form-control/checkbox/Checkbox.tsx +18 -20
- package/src/components/form-control/checkbox/Radio.tsx +18 -20
- package/src/components/form-control/data-select-button/DataSelectButton.tsx +279 -0
- package/src/components/form-control/select/Select.tsx +192 -36
- package/src/components/form-control/select-list/SelectList.tsx +385 -0
- package/src/components/form-control/select-list/SelectListContext.ts +23 -0
- package/src/components/form-control/shared-data/SharedDataSelect.tsx +101 -0
- package/src/components/form-control/shared-data/SharedDataSelectButton.tsx +47 -0
- package/src/components/form-control/shared-data/SharedDataSelectList.tsx +85 -0
- package/src/features/address/AddressSearch.tsx +75 -0
- package/src/index.ts +18 -0
- package/src/providers/shared-data/SharedDataContext.ts +14 -0
- package/src/providers/shared-data/SharedDataProvider.tsx +4 -0
|
@@ -11,7 +11,8 @@ import {
|
|
|
11
11
|
} from "solid-js";
|
|
12
12
|
import { createStore, produce, reconcile } from "solid-js/store";
|
|
13
13
|
import { createControllableStore } from "../../../hooks/createControllableStore";
|
|
14
|
-
import {
|
|
14
|
+
import type { DateTime } from "@simplysm/core-common";
|
|
15
|
+
import { objClone, objGetChainValue } from "@simplysm/core-common";
|
|
15
16
|
import "@simplysm/core-common"; // register extensions
|
|
16
17
|
import type { SortingDef } from "../sheet/types";
|
|
17
18
|
import { DataSheet } from "../sheet/DataSheet";
|
|
@@ -26,6 +27,7 @@ import { useDialogInstance } from "../../disclosure/DialogInstanceContext";
|
|
|
26
27
|
import { Dialog } from "../../disclosure/Dialog";
|
|
27
28
|
import { Link } from "../../display/Link";
|
|
28
29
|
import { createEventListener } from "@solid-primitives/event-listener";
|
|
30
|
+
import { useBeforeLeave } from "@solidjs/router";
|
|
29
31
|
import clsx from "clsx";
|
|
30
32
|
import {
|
|
31
33
|
IconDeviceFloppy,
|
|
@@ -67,11 +69,14 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
67
69
|
"search",
|
|
68
70
|
"getItemKey",
|
|
69
71
|
"persistKey",
|
|
70
|
-
"itemsPerPage",
|
|
71
72
|
"editable",
|
|
72
73
|
"itemEditable",
|
|
73
74
|
"itemDeletable",
|
|
74
75
|
"itemDeleted",
|
|
76
|
+
"isItemSelectable",
|
|
77
|
+
"lastModifiedAtProp",
|
|
78
|
+
"lastModifiedByProp",
|
|
79
|
+
"onSubmitted",
|
|
75
80
|
"filterInitial",
|
|
76
81
|
"items",
|
|
77
82
|
"onItemsChange",
|
|
@@ -123,6 +128,7 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
123
128
|
const [ready, setReady] = createSignal(false);
|
|
124
129
|
|
|
125
130
|
const [selectedItems, setSelectedItems] = createSignal<TItem[]>([]);
|
|
131
|
+
const [selectedKeys, setSelectedKeys] = createSignal<Set<string | number>>(new Set());
|
|
126
132
|
|
|
127
133
|
let formRef: HTMLFormElement | undefined;
|
|
128
134
|
|
|
@@ -130,6 +136,23 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
130
136
|
void doRefresh();
|
|
131
137
|
});
|
|
132
138
|
|
|
139
|
+
// -- Key-based selection: restore selectedItems when items change --
|
|
140
|
+
createEffect(() => {
|
|
141
|
+
const currentItems = items as unknown as TItem[];
|
|
142
|
+
const keys = selectedKeys();
|
|
143
|
+
if (keys.size === 0) {
|
|
144
|
+
if (selectedItems().length > 0) {
|
|
145
|
+
setSelectedItems([]);
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const restored = currentItems.filter((item) => {
|
|
150
|
+
const key = local.getItemKey(item);
|
|
151
|
+
return key != null && keys.has(key);
|
|
152
|
+
});
|
|
153
|
+
setSelectedItems(restored);
|
|
154
|
+
});
|
|
155
|
+
|
|
133
156
|
async function doRefresh() {
|
|
134
157
|
setBusyCount((c) => c + 1);
|
|
135
158
|
try {
|
|
@@ -142,12 +165,7 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
142
165
|
}
|
|
143
166
|
|
|
144
167
|
async function refresh() {
|
|
145
|
-
const
|
|
146
|
-
const result: SearchResult<TItem> = await local.search(
|
|
147
|
-
lastFilter(),
|
|
148
|
-
usePagination ? page() : 0,
|
|
149
|
-
sorts(),
|
|
150
|
-
);
|
|
168
|
+
const result: SearchResult<TItem> = await local.search(lastFilter(), page(), sorts());
|
|
151
169
|
setItems(reconcile(result.items));
|
|
152
170
|
originalItems = objClone(result.items);
|
|
153
171
|
setTotalPageCount(result.pageCount ?? 0);
|
|
@@ -155,20 +173,28 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
155
173
|
|
|
156
174
|
/* eslint-disable solid/reactivity -- 이벤트 핸들러에서만 호출, store 즉시 읽기 */
|
|
157
175
|
function getItemDiffs() {
|
|
158
|
-
return
|
|
159
|
-
|
|
160
|
-
})
|
|
176
|
+
return items.oneWayDiffs(originalItems, (item) => local.getItemKey(item), {
|
|
177
|
+
excludes: local.inlineEdit?.diffsExcludes,
|
|
178
|
+
});
|
|
161
179
|
}
|
|
162
180
|
/* eslint-enable solid/reactivity */
|
|
163
181
|
|
|
182
|
+
function checkIgnoreChanges(): boolean {
|
|
183
|
+
if (!local.inlineEdit) return true;
|
|
184
|
+
if (getItemDiffs().length === 0) return true;
|
|
185
|
+
return confirm("변경사항이 있습니다. 무시하시겠습니까?");
|
|
186
|
+
}
|
|
187
|
+
|
|
164
188
|
// -- Filter --
|
|
165
189
|
function handleFilterSubmit(e: Event) {
|
|
166
190
|
e.preventDefault();
|
|
191
|
+
if (!checkIgnoreChanges()) return;
|
|
167
192
|
setPage(1);
|
|
168
193
|
setLastFilter(() => objClone(filter));
|
|
169
194
|
}
|
|
170
195
|
|
|
171
196
|
async function handleRefresh() {
|
|
197
|
+
if (!checkIgnoreChanges()) return;
|
|
172
198
|
await doRefresh();
|
|
173
199
|
}
|
|
174
200
|
|
|
@@ -216,6 +242,7 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
216
242
|
await local.inlineEdit.submit(diffs);
|
|
217
243
|
noti.success("저장 완료", "저장되었습니다.");
|
|
218
244
|
await refresh();
|
|
245
|
+
local.onSubmitted?.();
|
|
219
246
|
} catch (err) {
|
|
220
247
|
noti.error(err, "저장 실패");
|
|
221
248
|
}
|
|
@@ -257,13 +284,28 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
257
284
|
setBusyCount((c) => c - 1);
|
|
258
285
|
}
|
|
259
286
|
|
|
287
|
+
async function handleRestoreItems() {
|
|
288
|
+
if (!local.modalEdit?.restoreItems) return;
|
|
289
|
+
const result = await local.modalEdit.restoreItems(selectedItems());
|
|
290
|
+
if (!result) return;
|
|
291
|
+
|
|
292
|
+
setBusyCount((c) => c + 1);
|
|
293
|
+
try {
|
|
294
|
+
await refresh();
|
|
295
|
+
noti.success("복구 완료", "복구되었습니다.");
|
|
296
|
+
} catch (err) {
|
|
297
|
+
noti.error(err, "복구 실패");
|
|
298
|
+
}
|
|
299
|
+
setBusyCount((c) => c - 1);
|
|
300
|
+
}
|
|
301
|
+
|
|
260
302
|
// -- Excel --
|
|
261
303
|
async function handleExcelDownload() {
|
|
262
304
|
if (!local.excel) return;
|
|
263
305
|
|
|
264
306
|
setBusyCount((c) => c + 1);
|
|
265
307
|
try {
|
|
266
|
-
const result = await local.search(lastFilter(),
|
|
308
|
+
const result = await local.search(lastFilter(), undefined, sorts());
|
|
267
309
|
await local.excel.download(result.items);
|
|
268
310
|
} catch (err) {
|
|
269
311
|
noti.error(err, "엑셀 다운로드 실패");
|
|
@@ -295,16 +337,51 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
295
337
|
}
|
|
296
338
|
|
|
297
339
|
// -- Select Mode --
|
|
340
|
+
function handleSelectedItemsChange(newSelectedItems: TItem[]) {
|
|
341
|
+
// 현재 페이지 아이템들의 key Set
|
|
342
|
+
const currentItems = items as unknown as TItem[];
|
|
343
|
+
const currentKeys = new Set<string | number>();
|
|
344
|
+
for (const item of currentItems) {
|
|
345
|
+
const key = local.getItemKey(item);
|
|
346
|
+
if (key != null) currentKeys.add(key);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// 새로 선택된 아이템들의 key
|
|
350
|
+
const newSelectedKeys = new Set<string | number>();
|
|
351
|
+
for (const item of newSelectedItems) {
|
|
352
|
+
const key = local.getItemKey(item);
|
|
353
|
+
if (key != null) newSelectedKeys.add(key);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// 다른 페이지 key 보존 + 현재 페이지 key 갱신
|
|
357
|
+
const merged = new Set<string | number>();
|
|
358
|
+
for (const key of selectedKeys()) {
|
|
359
|
+
if (!currentKeys.has(key)) {
|
|
360
|
+
merged.add(key); // 다른 페이지 key 보존
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
for (const key of newSelectedKeys) {
|
|
364
|
+
merged.add(key); // 현재 페이지 선택 추가
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
setSelectedKeys(merged);
|
|
368
|
+
setSelectedItems(newSelectedItems);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function clearSelection() {
|
|
372
|
+
setSelectedKeys(new Set<string | number>());
|
|
373
|
+
setSelectedItems([]);
|
|
374
|
+
}
|
|
375
|
+
|
|
298
376
|
function handleSelectConfirm() {
|
|
299
377
|
local.onSelect?.({
|
|
300
378
|
items: selectedItems(),
|
|
301
|
-
keys:
|
|
302
|
-
.map((item) => local.getItemKey(item))
|
|
303
|
-
.filter((k): k is string | number => k != null),
|
|
379
|
+
keys: [...selectedKeys()],
|
|
304
380
|
});
|
|
305
381
|
}
|
|
306
382
|
|
|
307
383
|
function handleSelectCancel() {
|
|
384
|
+
clearSelection();
|
|
308
385
|
local.onSelect?.({ items: [], keys: [] });
|
|
309
386
|
}
|
|
310
387
|
|
|
@@ -316,10 +393,25 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
316
393
|
}
|
|
317
394
|
if (e.ctrlKey && e.altKey && e.key === "l") {
|
|
318
395
|
e.preventDefault();
|
|
396
|
+
if (!checkIgnoreChanges()) return;
|
|
319
397
|
await doRefresh();
|
|
320
398
|
}
|
|
321
399
|
});
|
|
322
400
|
|
|
401
|
+
// -- Route Leave Guard --
|
|
402
|
+
// eslint-disable-next-line solid/reactivity -- inlineEdit는 초기 설정값으로만 사용
|
|
403
|
+
if (!isModal && local.inlineEdit) {
|
|
404
|
+
try {
|
|
405
|
+
useBeforeLeave((e) => {
|
|
406
|
+
if (!checkIgnoreChanges()) {
|
|
407
|
+
e.preventDefault();
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
} catch {
|
|
411
|
+
// Router context 없으면 skip
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
323
415
|
// -- Topbar Actions --
|
|
324
416
|
if (topbarCtx) {
|
|
325
417
|
createTopbarActions(() => (
|
|
@@ -361,6 +453,7 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
361
453
|
addItem: handleAddRow,
|
|
362
454
|
setPage,
|
|
363
455
|
setSorts,
|
|
456
|
+
clearSelection,
|
|
364
457
|
};
|
|
365
458
|
|
|
366
459
|
// -- Render --
|
|
@@ -458,13 +551,32 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
458
551
|
onClick={handleDeleteItems}
|
|
459
552
|
disabled={
|
|
460
553
|
selectedItems().length === 0 ||
|
|
461
|
-
!selectedItems().some(
|
|
554
|
+
!selectedItems().some(
|
|
555
|
+
(item) =>
|
|
556
|
+
(local.itemDeletable?.(item) ?? true) &&
|
|
557
|
+
!(local.itemDeleted?.(item) ?? false),
|
|
558
|
+
)
|
|
462
559
|
}
|
|
463
560
|
>
|
|
464
561
|
<Icon icon={IconTrash} class="mr-1" />
|
|
465
562
|
선택 삭제
|
|
466
563
|
</Button>
|
|
467
564
|
</Show>
|
|
565
|
+
<Show when={canEdit() && local.modalEdit?.restoreItems}>
|
|
566
|
+
<Button
|
|
567
|
+
size="sm"
|
|
568
|
+
theme="warning"
|
|
569
|
+
variant="ghost"
|
|
570
|
+
onClick={handleRestoreItems}
|
|
571
|
+
disabled={
|
|
572
|
+
selectedItems().length === 0 ||
|
|
573
|
+
!selectedItems().some((item) => local.itemDeleted?.(item) ?? false)
|
|
574
|
+
}
|
|
575
|
+
>
|
|
576
|
+
<Icon icon={IconTrashOff} class="mr-1" />
|
|
577
|
+
선택 복구
|
|
578
|
+
</Button>
|
|
579
|
+
</Show>
|
|
468
580
|
|
|
469
581
|
{/* Excel buttons */}
|
|
470
582
|
<Show when={canEdit() && local.excel?.upload}>
|
|
@@ -492,21 +604,21 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
492
604
|
class="h-full"
|
|
493
605
|
items={items}
|
|
494
606
|
persistKey={local.persistKey != null ? `${local.persistKey}-sheet` : undefined}
|
|
495
|
-
page={
|
|
607
|
+
page={totalPageCount() > 0 ? page() : undefined}
|
|
496
608
|
onPageChange={setPage}
|
|
497
609
|
totalPageCount={totalPageCount()}
|
|
498
|
-
itemsPerPage={local.itemsPerPage}
|
|
499
610
|
sorts={sorts()}
|
|
500
611
|
onSortsChange={setSorts}
|
|
612
|
+
isItemSelectable={local.isItemSelectable}
|
|
501
613
|
selectMode={
|
|
502
614
|
isSelectMode()
|
|
503
615
|
? local.selectMode
|
|
504
|
-
: local.modalEdit?.deleteItems != null
|
|
616
|
+
: local.modalEdit?.deleteItems != null || local.modalEdit?.restoreItems != null
|
|
505
617
|
? "multiple"
|
|
506
618
|
: undefined
|
|
507
619
|
}
|
|
508
620
|
selectedItems={selectedItems()}
|
|
509
|
-
onSelectedItemsChange={
|
|
621
|
+
onSelectedItemsChange={handleSelectedItemsChange}
|
|
510
622
|
autoSelect={isSelectMode() && local.selectMode === "single" ? "click" : undefined}
|
|
511
623
|
cellClass={(item) => {
|
|
512
624
|
if (isItemDeleted(item)) {
|
|
@@ -593,6 +705,31 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
593
705
|
</DataSheetColumn>
|
|
594
706
|
)}
|
|
595
707
|
</For>
|
|
708
|
+
|
|
709
|
+
{/* Auto lastModified columns */}
|
|
710
|
+
<Show when={local.lastModifiedAtProp}>
|
|
711
|
+
<DataSheetColumn<TItem> key={local.lastModifiedAtProp!} header="수정일시" hidden>
|
|
712
|
+
{(dsCtx) => (
|
|
713
|
+
<div class="px-2 py-1 text-center">
|
|
714
|
+
{(
|
|
715
|
+
objGetChainValue(dsCtx.item, local.lastModifiedAtProp!, true) as
|
|
716
|
+
| DateTime
|
|
717
|
+
| undefined
|
|
718
|
+
)?.toFormatString("yyyy-MM-dd HH:mm")}
|
|
719
|
+
</div>
|
|
720
|
+
)}
|
|
721
|
+
</DataSheetColumn>
|
|
722
|
+
</Show>
|
|
723
|
+
|
|
724
|
+
<Show when={local.lastModifiedByProp}>
|
|
725
|
+
<DataSheetColumn<TItem> key={local.lastModifiedByProp!} header="수정자" hidden>
|
|
726
|
+
{(dsCtx) => (
|
|
727
|
+
<div class="px-2 py-1 text-center">
|
|
728
|
+
{objGetChainValue(dsCtx.item, local.lastModifiedByProp!, true) as string}
|
|
729
|
+
</div>
|
|
730
|
+
)}
|
|
731
|
+
</DataSheetColumn>
|
|
732
|
+
</Show>
|
|
596
733
|
</DataSheet>
|
|
597
734
|
</form>
|
|
598
735
|
|
|
@@ -16,11 +16,13 @@ export interface InlineEditConfig<TItem> {
|
|
|
16
16
|
submit: (diffs: ArrayDiffs2Result<TItem>[]) => Promise<void>;
|
|
17
17
|
newItem: () => TItem;
|
|
18
18
|
deleteProp?: keyof TItem & string;
|
|
19
|
+
diffsExcludes?: string[];
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export interface ModalEditConfig<TItem> {
|
|
22
|
-
editItem: (item?: TItem) => Promise<boolean>;
|
|
23
|
+
editItem: (item?: TItem) => Promise<boolean | undefined>;
|
|
23
24
|
deleteItems?: (items: TItem[]) => Promise<boolean>;
|
|
25
|
+
restoreItems?: (items: TItem[]) => Promise<boolean>;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
export interface ExcelConfig<TItem> {
|
|
@@ -56,6 +58,7 @@ export interface CrudSheetContext<TItem> {
|
|
|
56
58
|
save(): Promise<void>;
|
|
57
59
|
refresh(): Promise<void>;
|
|
58
60
|
addItem(): void;
|
|
61
|
+
clearSelection(): void;
|
|
59
62
|
setPage(page: number): void;
|
|
60
63
|
setSorts(sorts: SortingDef[]): void;
|
|
61
64
|
}
|
|
@@ -73,20 +76,27 @@ export type CrudSheetProps<TItem, TFilter extends Record<string, any>> = CrudShe
|
|
|
73
76
|
);
|
|
74
77
|
|
|
75
78
|
interface CrudSheetBaseProps<TItem, TFilter extends Record<string, any>> {
|
|
76
|
-
search: (
|
|
79
|
+
search: (
|
|
80
|
+
filter: TFilter,
|
|
81
|
+
page: number | undefined,
|
|
82
|
+
sorts: SortingDef[],
|
|
83
|
+
) => Promise<SearchResult<TItem>>;
|
|
77
84
|
getItemKey: (item: TItem) => string | number | undefined;
|
|
78
85
|
persistKey?: string;
|
|
79
|
-
itemsPerPage?: number;
|
|
80
86
|
editable?: boolean;
|
|
81
87
|
itemEditable?: (item: TItem) => boolean;
|
|
82
88
|
itemDeletable?: (item: TItem) => boolean;
|
|
83
89
|
itemDeleted?: (item: TItem) => boolean;
|
|
90
|
+
isItemSelectable?: (item: TItem) => boolean | string;
|
|
91
|
+
lastModifiedAtProp?: string;
|
|
92
|
+
lastModifiedByProp?: string;
|
|
84
93
|
filterInitial?: TFilter;
|
|
85
94
|
items?: TItem[];
|
|
86
95
|
onItemsChange?: (items: TItem[]) => void;
|
|
87
96
|
excel?: ExcelConfig<TItem>;
|
|
88
97
|
selectMode?: "single" | "multiple";
|
|
89
98
|
onSelect?: (result: SelectResult<TItem>) => void;
|
|
99
|
+
onSubmitted?: () => void;
|
|
90
100
|
hideAutoTools?: boolean;
|
|
91
101
|
class?: string;
|
|
92
102
|
children: JSX.Element;
|
|
@@ -114,10 +114,8 @@ export const DataSheet: DataSheetComponent = <T,>(props: DataSheetProps<T>) => {
|
|
|
114
114
|
|
|
115
115
|
// #region Column Collection
|
|
116
116
|
const resolved = children(() => local.children);
|
|
117
|
-
const columnDefs = createMemo(
|
|
118
|
-
(resolved.toArray().filter(isDataSheetColumnDef) as unknown as DataSheetColumnDef<T>[]
|
|
119
|
-
(col) => !col.hidden,
|
|
120
|
-
),
|
|
117
|
+
const columnDefs = createMemo(
|
|
118
|
+
() => resolved.toArray().filter(isDataSheetColumnDef) as unknown as DataSheetColumnDef<T>[],
|
|
121
119
|
);
|
|
122
120
|
|
|
123
121
|
// #region Config (useSyncConfig)
|
|
@@ -130,14 +128,15 @@ export const DataSheet: DataSheetComponent = <T,>(props: DataSheetProps<T>) => {
|
|
|
130
128
|
|
|
131
129
|
// 설정이 적용된 최종 컬럼 — config의 hidden/fixed/width/displayOrder 오버라이드 적용
|
|
132
130
|
const effectiveColumns = createMemo(() => {
|
|
133
|
-
const cols = columnDefs();
|
|
131
|
+
const cols = columnDefs();
|
|
134
132
|
const record = config().columnRecord ?? {};
|
|
135
133
|
|
|
136
134
|
return cols
|
|
137
135
|
.filter((col) => {
|
|
138
|
-
// config
|
|
136
|
+
// config 오버라이드가 있으면 config 우선, 없으면 원래 col.hidden 사용
|
|
139
137
|
const saved = record[col.key];
|
|
140
|
-
|
|
138
|
+
const isHidden = saved?.hidden ?? col.hidden;
|
|
139
|
+
return !isHidden;
|
|
141
140
|
})
|
|
142
141
|
.map((col) => {
|
|
143
142
|
const saved = record[col.key];
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import clsx from "clsx";
|
|
2
2
|
import {
|
|
3
|
-
bgSurface,
|
|
4
3
|
borderDefault,
|
|
5
4
|
type ComponentSize,
|
|
6
5
|
disabledOpacity,
|
|
@@ -32,7 +31,8 @@ export const indicatorBaseClass = clsx(
|
|
|
32
31
|
"size-4",
|
|
33
32
|
"border",
|
|
34
33
|
borderDefault,
|
|
35
|
-
bgSurface,
|
|
34
|
+
// bgSurface,
|
|
35
|
+
"bg-primary-50 dark:bg-primary-950/30",
|
|
36
36
|
"transition-colors",
|
|
37
37
|
);
|
|
38
38
|
|
|
@@ -90,27 +90,25 @@ export const Checkbox: ParentComponent<CheckboxProps> = (props) => {
|
|
|
90
90
|
|
|
91
91
|
return (
|
|
92
92
|
<Invalid message={errorMsg()} variant="border" touchMode={local.touchMode}>
|
|
93
|
-
<div
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
>
|
|
105
|
-
<
|
|
106
|
-
<
|
|
107
|
-
<Icon icon={IconCheck} size="1em" />
|
|
108
|
-
</Show>
|
|
109
|
-
</div>
|
|
110
|
-
<Show when={local.children}>
|
|
111
|
-
<span>{local.children}</span>
|
|
93
|
+
<div
|
|
94
|
+
{...rest}
|
|
95
|
+
use:ripple={!local.disabled}
|
|
96
|
+
role="checkbox"
|
|
97
|
+
aria-checked={value()}
|
|
98
|
+
tabIndex={local.disabled ? -1 : 0}
|
|
99
|
+
class={getWrapperClass()}
|
|
100
|
+
style={local.style}
|
|
101
|
+
onClick={handleClick}
|
|
102
|
+
onKeyDown={handleKeyDown}
|
|
103
|
+
>
|
|
104
|
+
<div class={getIndicatorClass()}>
|
|
105
|
+
<Show when={value()}>
|
|
106
|
+
<Icon icon={IconCheck} size="1em" />
|
|
112
107
|
</Show>
|
|
113
|
-
</
|
|
108
|
+
</div>
|
|
109
|
+
<Show when={local.children}>
|
|
110
|
+
<span>{local.children}</span>
|
|
111
|
+
</Show>
|
|
114
112
|
</div>
|
|
115
113
|
</Invalid>
|
|
116
114
|
);
|
|
@@ -91,27 +91,25 @@ export const Radio: ParentComponent<RadioProps> = (props) => {
|
|
|
91
91
|
|
|
92
92
|
return (
|
|
93
93
|
<Invalid message={errorMsg()} variant="border" touchMode={local.touchMode}>
|
|
94
|
-
<div
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
>
|
|
106
|
-
<
|
|
107
|
-
<
|
|
108
|
-
<div class={radioDotClass} />
|
|
109
|
-
</Show>
|
|
110
|
-
</div>
|
|
111
|
-
<Show when={local.children}>
|
|
112
|
-
<span>{local.children}</span>
|
|
94
|
+
<div
|
|
95
|
+
{...rest}
|
|
96
|
+
use:ripple={!local.disabled}
|
|
97
|
+
role="radio"
|
|
98
|
+
aria-checked={value()}
|
|
99
|
+
tabIndex={local.disabled ? -1 : 0}
|
|
100
|
+
class={getWrapperClass()}
|
|
101
|
+
style={local.style}
|
|
102
|
+
onClick={handleClick}
|
|
103
|
+
onKeyDown={handleKeyDown}
|
|
104
|
+
>
|
|
105
|
+
<div class={getIndicatorClass()}>
|
|
106
|
+
<Show when={value()}>
|
|
107
|
+
<div class={radioDotClass} />
|
|
113
108
|
</Show>
|
|
114
|
-
</
|
|
109
|
+
</div>
|
|
110
|
+
<Show when={local.children}>
|
|
111
|
+
<span>{local.children}</span>
|
|
112
|
+
</Show>
|
|
115
113
|
</div>
|
|
116
114
|
</Invalid>
|
|
117
115
|
);
|