@simplysm/solid 13.0.57 → 13.0.59
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 +1 -1
- package/dist/components/data/crud-detail/CrudDetail.d.ts.map +1 -1
- package/dist/components/data/crud-detail/CrudDetail.js +55 -42
- 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 +120 -94
- package/dist/components/data/crud-sheet/CrudSheet.js.map +2 -2
- package/dist/components/data/crud-sheet/CrudSheetColumn.js +1 -1
- package/dist/components/data/crud-sheet/CrudSheetColumn.js.map +2 -2
- package/dist/components/data/crud-sheet/types.d.ts +4 -3
- package/dist/components/data/crud-sheet/types.d.ts.map +1 -1
- package/dist/components/data/kanban/Kanban.d.ts.map +1 -1
- package/dist/components/data/kanban/Kanban.js +3 -4
- package/dist/components/data/kanban/Kanban.js.map +2 -2
- package/dist/components/data/kanban/KanbanContext.d.ts +2 -3
- package/dist/components/data/kanban/KanbanContext.d.ts.map +1 -1
- package/dist/components/data/kanban/KanbanContext.js.map +1 -1
- package/dist/components/data/list/ListItem.d.ts.map +1 -1
- package/dist/components/data/list/ListItem.js +3 -3
- package/dist/components/data/list/ListItem.js.map +2 -2
- package/dist/components/data/sheet/DataSheet.styles.d.ts.map +1 -1
- package/dist/components/data/sheet/DataSheet.styles.js +3 -8
- package/dist/components/data/sheet/DataSheet.styles.js.map +1 -1
- package/dist/components/disclosure/Dialog.d.ts.map +1 -1
- package/dist/components/disclosure/Dialog.js +36 -27
- package/dist/components/disclosure/Dialog.js.map +2 -2
- package/dist/components/disclosure/Dropdown.d.ts.map +1 -1
- package/dist/components/disclosure/Dropdown.js +7 -15
- package/dist/components/disclosure/Dropdown.js.map +2 -2
- package/dist/components/display/Icon.js +1 -1
- package/dist/components/display/Icon.js.map +1 -1
- package/dist/components/feedback/notification/NotificationBanner.js +1 -1
- package/dist/components/feedback/notification/NotificationBanner.js.map +1 -1
- package/dist/components/feedback/notification/NotificationContext.d.ts +1 -1
- package/dist/components/feedback/notification/NotificationContext.d.ts.map +1 -1
- package/dist/components/feedback/notification/NotificationContext.js.map +1 -1
- package/dist/components/feedback/notification/NotificationProvider.d.ts.map +1 -1
- package/dist/components/feedback/notification/NotificationProvider.js +8 -12
- package/dist/components/feedback/notification/NotificationProvider.js.map +2 -2
- package/dist/components/form-control/color-picker/ColorPicker.d.ts +5 -3
- package/dist/components/form-control/color-picker/ColorPicker.d.ts.map +1 -1
- package/dist/components/form-control/color-picker/ColorPicker.js +11 -6
- package/dist/components/form-control/color-picker/ColorPicker.js.map +2 -2
- package/dist/components/form-control/field/NumberInput.d.ts.map +1 -1
- package/dist/components/form-control/field/NumberInput.js +2 -2
- package/dist/components/form-control/field/NumberInput.js.map +2 -2
- package/dist/components/form-control/field/TextInput.d.ts.map +1 -1
- package/dist/components/form-control/field/TextInput.js +3 -3
- package/dist/components/form-control/field/TextInput.js.map +2 -2
- package/dist/components/form-control/select/Select.d.ts.map +1 -1
- package/dist/components/form-control/select/Select.js +3 -4
- package/dist/components/form-control/select/Select.js.map +2 -2
- package/dist/components/form-control/select/SelectContext.d.ts +1 -2
- package/dist/components/form-control/select/SelectContext.d.ts.map +1 -1
- package/dist/components/form-control/select/SelectContext.js.map +1 -1
- package/dist/components/form-control/select/SelectItem.d.ts.map +1 -1
- package/dist/components/form-control/select/SelectItem.js +3 -3
- package/dist/components/form-control/select/SelectItem.js.map +2 -2
- package/dist/helpers/createAppStructure.d.ts +7 -4
- package/dist/helpers/createAppStructure.d.ts.map +1 -1
- package/dist/helpers/createAppStructure.js +20 -2
- package/dist/helpers/createAppStructure.js.map +1 -1
- package/dist/hooks/createPointerDrag.d.ts +1 -1
- package/dist/hooks/createPointerDrag.d.ts.map +1 -1
- package/dist/hooks/createPointerDrag.js +6 -4
- package/dist/hooks/createPointerDrag.js.map +1 -1
- package/dist/hooks/createSlotSignal.d.ts +9 -0
- package/dist/hooks/createSlotSignal.d.ts.map +1 -0
- package/dist/hooks/createSlotSignal.js +10 -0
- package/dist/hooks/createSlotSignal.js.map +6 -0
- package/dist/index.d.ts +15 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -39
- package/dist/index.js.map +1 -1
- package/docs/data-components.md +18 -8
- package/docs/feedback.md +17 -10
- package/docs/helpers.md +24 -0
- package/docs/hooks.md +166 -83
- package/docs/styling.md +1 -0
- package/package.json +3 -3
- package/src/components/data/crud-detail/CrudDetail.tsx +45 -40
- package/src/components/data/crud-sheet/CrudSheet.tsx +99 -103
- package/src/components/data/crud-sheet/CrudSheetColumn.tsx +1 -1
- package/src/components/data/crud-sheet/types.ts +4 -3
- package/src/components/data/kanban/Kanban.tsx +3 -5
- package/src/components/data/kanban/KanbanContext.ts +2 -3
- package/src/components/data/list/ListItem.tsx +2 -5
- package/src/components/data/sheet/DataSheet.styles.ts +3 -8
- package/src/components/disclosure/Dialog.tsx +26 -26
- package/src/components/disclosure/Dropdown.tsx +7 -20
- package/src/components/display/Icon.tsx +1 -1
- package/src/components/feedback/notification/NotificationBanner.tsx +1 -1
- package/src/components/feedback/notification/NotificationContext.ts +2 -7
- package/src/components/feedback/notification/NotificationProvider.tsx +8 -15
- package/src/components/form-control/color-picker/ColorPicker.tsx +19 -9
- package/src/components/form-control/field/NumberInput.tsx +2 -4
- package/src/components/form-control/field/TextInput.tsx +2 -5
- package/src/components/form-control/select/Select.tsx +3 -6
- package/src/components/form-control/select/SelectContext.ts +1 -2
- package/src/components/form-control/select/SelectItem.tsx +2 -5
- package/src/helpers/createAppStructure.ts +36 -6
- package/src/hooks/createPointerDrag.ts +8 -5
- package/src/hooks/createSlotSignal.ts +14 -0
- package/src/index.ts +15 -41
- package/tailwind.config.ts +1 -0
|
@@ -21,7 +21,7 @@ import { useNotification } from "../../feedback/notification/NotificationContext
|
|
|
21
21
|
import { Button } from "../../form-control/Button";
|
|
22
22
|
import { Icon } from "../../display/Icon";
|
|
23
23
|
import { FormGroup } from "../../layout/FormGroup";
|
|
24
|
-
import {
|
|
24
|
+
import { createTopbarActions, TopbarContext } from "../../layout/topbar/TopbarContext";
|
|
25
25
|
import { useDialogInstance } from "../../disclosure/DialogInstanceContext";
|
|
26
26
|
import { Dialog } from "../../disclosure/Dialog";
|
|
27
27
|
import { Link } from "../../display/Link";
|
|
@@ -29,6 +29,7 @@ import { createEventListener } from "@solid-primitives/event-listener";
|
|
|
29
29
|
import clsx from "clsx";
|
|
30
30
|
import {
|
|
31
31
|
IconDeviceFloppy,
|
|
32
|
+
IconExternalLink,
|
|
32
33
|
IconFileExcel,
|
|
33
34
|
IconPlus,
|
|
34
35
|
IconRefresh,
|
|
@@ -37,10 +38,10 @@ import {
|
|
|
37
38
|
IconTrashOff,
|
|
38
39
|
IconUpload,
|
|
39
40
|
} from "@tabler/icons-solidjs";
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
41
|
+
import { CrudSheetColumn, isCrudSheetColumnDef } from "./CrudSheetColumn";
|
|
42
|
+
import { CrudSheetFilter, isCrudSheetFilterDef } from "./CrudSheetFilter";
|
|
43
|
+
import { CrudSheetTools, isCrudSheetToolsDef } from "./CrudSheetTools";
|
|
44
|
+
import { CrudSheetHeader, isCrudSheetHeaderDef } from "./CrudSheetHeader";
|
|
44
45
|
import type {
|
|
45
46
|
CrudSheetColumnDef,
|
|
46
47
|
CrudSheetContext,
|
|
@@ -70,6 +71,7 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
70
71
|
"editable",
|
|
71
72
|
"itemEditable",
|
|
72
73
|
"itemDeletable",
|
|
74
|
+
"itemDeleted",
|
|
73
75
|
"filterInitial",
|
|
74
76
|
"items",
|
|
75
77
|
"onItemsChange",
|
|
@@ -124,28 +126,27 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
124
126
|
|
|
125
127
|
let formRef: HTMLFormElement | undefined;
|
|
126
128
|
|
|
127
|
-
// -- Auto Refresh Effect --
|
|
128
129
|
createEffect(() => {
|
|
129
|
-
|
|
130
|
-
const currSorts = sorts();
|
|
131
|
-
const currPage = page();
|
|
132
|
-
|
|
133
|
-
queueMicrotask(async () => {
|
|
134
|
-
setBusyCount((c) => c + 1);
|
|
135
|
-
await noti.try(async () => {
|
|
136
|
-
await refresh(currLastFilter, currSorts, currPage);
|
|
137
|
-
}, "조회 실패");
|
|
138
|
-
setBusyCount((c) => c - 1);
|
|
139
|
-
setReady(true);
|
|
140
|
-
});
|
|
130
|
+
void doRefresh();
|
|
141
131
|
});
|
|
142
132
|
|
|
143
|
-
async function
|
|
133
|
+
async function doRefresh() {
|
|
134
|
+
setBusyCount((c) => c + 1);
|
|
135
|
+
try {
|
|
136
|
+
await refresh();
|
|
137
|
+
} catch (err) {
|
|
138
|
+
noti.error(err, "조회 실패");
|
|
139
|
+
}
|
|
140
|
+
setBusyCount((c) => c - 1);
|
|
141
|
+
setReady(true);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function refresh() {
|
|
144
145
|
const usePagination = local.itemsPerPage != null;
|
|
145
146
|
const result: SearchResult<TItem> = await local.search(
|
|
146
|
-
|
|
147
|
-
usePagination ?
|
|
148
|
-
|
|
147
|
+
lastFilter(),
|
|
148
|
+
usePagination ? page() : 0,
|
|
149
|
+
sorts(),
|
|
149
150
|
);
|
|
150
151
|
setItems(reconcile(result.items));
|
|
151
152
|
originalItems = objClone(result.items);
|
|
@@ -167,8 +168,8 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
167
168
|
setLastFilter(() => objClone(filter));
|
|
168
169
|
}
|
|
169
170
|
|
|
170
|
-
function handleRefresh() {
|
|
171
|
-
|
|
171
|
+
async function handleRefresh() {
|
|
172
|
+
await doRefresh();
|
|
172
173
|
}
|
|
173
174
|
|
|
174
175
|
// -- Inline Edit --
|
|
@@ -210,17 +211,14 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
210
211
|
return;
|
|
211
212
|
}
|
|
212
213
|
|
|
213
|
-
const currLastFilter = lastFilter();
|
|
214
|
-
const currSorts = sorts();
|
|
215
|
-
const currPage = page();
|
|
216
|
-
|
|
217
214
|
setBusyCount((c) => c + 1);
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
await local.inlineEdit!.submit(diffs);
|
|
215
|
+
try {
|
|
216
|
+
await local.inlineEdit.submit(diffs);
|
|
221
217
|
noti.success("저장 완료", "저장되었습니다.");
|
|
222
|
-
await refresh(
|
|
223
|
-
}
|
|
218
|
+
await refresh();
|
|
219
|
+
} catch (err) {
|
|
220
|
+
noti.error(err, "저장 실패");
|
|
221
|
+
}
|
|
224
222
|
setBusyCount((c) => c - 1);
|
|
225
223
|
}
|
|
226
224
|
|
|
@@ -236,10 +234,11 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
236
234
|
if (!result) return;
|
|
237
235
|
|
|
238
236
|
setBusyCount((c) => c + 1);
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
237
|
+
try {
|
|
238
|
+
await refresh();
|
|
239
|
+
} catch (err) {
|
|
240
|
+
noti.error(err, "조회 실패");
|
|
241
|
+
}
|
|
243
242
|
setBusyCount((c) => c - 1);
|
|
244
243
|
}
|
|
245
244
|
|
|
@@ -249,11 +248,12 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
249
248
|
if (!result) return;
|
|
250
249
|
|
|
251
250
|
setBusyCount((c) => c + 1);
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
await refresh(lastFilter(), sorts(), page());
|
|
251
|
+
try {
|
|
252
|
+
await refresh();
|
|
255
253
|
noti.success("삭제 완료", "삭제되었습니다.");
|
|
256
|
-
}
|
|
254
|
+
} catch (err) {
|
|
255
|
+
noti.error(err, "삭제 실패");
|
|
256
|
+
}
|
|
257
257
|
setBusyCount((c) => c - 1);
|
|
258
258
|
}
|
|
259
259
|
|
|
@@ -262,11 +262,12 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
262
262
|
if (!local.excel) return;
|
|
263
263
|
|
|
264
264
|
setBusyCount((c) => c + 1);
|
|
265
|
-
|
|
266
|
-
await noti.try(async () => {
|
|
265
|
+
try {
|
|
267
266
|
const result = await local.search(lastFilter(), 0, sorts());
|
|
268
|
-
await local.excel
|
|
269
|
-
}
|
|
267
|
+
await local.excel.download(result.items);
|
|
268
|
+
} catch (err) {
|
|
269
|
+
noti.error(err, "엑셀 다운로드 실패");
|
|
270
|
+
}
|
|
270
271
|
setBusyCount((c) => c - 1);
|
|
271
272
|
}
|
|
272
273
|
|
|
@@ -281,12 +282,13 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
281
282
|
if (file == null) return;
|
|
282
283
|
|
|
283
284
|
setBusyCount((c) => c + 1);
|
|
284
|
-
|
|
285
|
-
await noti.try(async () => {
|
|
285
|
+
try {
|
|
286
286
|
await local.excel!.upload!(file);
|
|
287
287
|
noti.success("완료", "엑셀 업로드가 완료되었습니다.");
|
|
288
|
-
await refresh(
|
|
289
|
-
}
|
|
288
|
+
await refresh();
|
|
289
|
+
} catch (err) {
|
|
290
|
+
noti.error(err, "엑셀 업로드 실패");
|
|
291
|
+
}
|
|
290
292
|
setBusyCount((c) => c - 1);
|
|
291
293
|
};
|
|
292
294
|
input.click();
|
|
@@ -307,14 +309,14 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
307
309
|
}
|
|
308
310
|
|
|
309
311
|
// -- Keyboard Shortcuts --
|
|
310
|
-
createEventListener(document, "keydown", (e: KeyboardEvent) => {
|
|
312
|
+
createEventListener(document, "keydown", async (e: KeyboardEvent) => {
|
|
311
313
|
if (e.ctrlKey && e.key === "s" && !isSelectMode()) {
|
|
312
314
|
e.preventDefault();
|
|
313
315
|
formRef?.requestSubmit();
|
|
314
316
|
}
|
|
315
317
|
if (e.ctrlKey && e.altKey && e.key === "l") {
|
|
316
318
|
e.preventDefault();
|
|
317
|
-
|
|
319
|
+
await doRefresh();
|
|
318
320
|
}
|
|
319
321
|
});
|
|
320
322
|
|
|
@@ -354,8 +356,7 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
354
356
|
},
|
|
355
357
|
save: handleSave,
|
|
356
358
|
refresh: async () => {
|
|
357
|
-
|
|
358
|
-
await Promise.resolve();
|
|
359
|
+
await doRefresh();
|
|
359
360
|
},
|
|
360
361
|
addItem: handleAddRow,
|
|
361
362
|
setPage,
|
|
@@ -364,6 +365,7 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
364
365
|
|
|
365
366
|
// -- Render --
|
|
366
367
|
const deleteProp = () => local.inlineEdit?.deleteProp;
|
|
368
|
+
const isItemDeleted = (item: TItem) => local.itemDeleted?.(item) ?? false;
|
|
367
369
|
|
|
368
370
|
return (
|
|
369
371
|
<>
|
|
@@ -385,17 +387,19 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
385
387
|
class={clsx("flex h-full flex-col", local.class)}
|
|
386
388
|
>
|
|
387
389
|
{/* Control mode: inline save/refresh bar */}
|
|
388
|
-
<Show when={!isModal && !topbarCtx
|
|
390
|
+
<Show when={!isModal && !topbarCtx}>
|
|
389
391
|
<div class="flex gap-2 p-2 pb-0">
|
|
390
|
-
<
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
392
|
+
<Show when={canEdit() && local.inlineEdit}>
|
|
393
|
+
<Button
|
|
394
|
+
size="sm"
|
|
395
|
+
theme="primary"
|
|
396
|
+
variant="ghost"
|
|
397
|
+
onClick={() => formRef?.requestSubmit()}
|
|
398
|
+
>
|
|
399
|
+
<Icon icon={IconDeviceFloppy} class="mr-1" />
|
|
400
|
+
저장
|
|
401
|
+
</Button>
|
|
402
|
+
</Show>
|
|
399
403
|
<Button size="sm" theme="info" variant="ghost" onClick={handleRefresh}>
|
|
400
404
|
<Icon icon={IconRefresh} class="mr-1" />
|
|
401
405
|
새로고침
|
|
@@ -496,9 +500,7 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
496
500
|
onSortsChange={setSorts}
|
|
497
501
|
selectMode={
|
|
498
502
|
isSelectMode()
|
|
499
|
-
? local.selectMode
|
|
500
|
-
? "multiple"
|
|
501
|
-
: "single"
|
|
503
|
+
? local.selectMode
|
|
502
504
|
: local.modalEdit?.deleteItems != null
|
|
503
505
|
? "multiple"
|
|
504
506
|
: undefined
|
|
@@ -506,43 +508,34 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
506
508
|
selectedItems={selectedItems()}
|
|
507
509
|
onSelectedItemsChange={setSelectedItems}
|
|
508
510
|
autoSelect={isSelectMode() && local.selectMode === "single" ? "click" : undefined}
|
|
509
|
-
cellClass={(item
|
|
510
|
-
|
|
511
|
-
if (dp != null && Boolean((item as Record<string, unknown>)[dp])) {
|
|
511
|
+
cellClass={(item) => {
|
|
512
|
+
if (isItemDeleted(item)) {
|
|
512
513
|
return clsx("line-through");
|
|
513
514
|
}
|
|
514
515
|
return undefined;
|
|
515
516
|
}}
|
|
516
517
|
>
|
|
517
|
-
{/* Auto delete column */}
|
|
518
|
-
<Show when={deleteProp() != null && canEdit()
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
<
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
: IconTrash
|
|
539
|
-
}
|
|
540
|
-
/>
|
|
541
|
-
</Link>
|
|
542
|
-
</div>
|
|
543
|
-
)}
|
|
544
|
-
</DataSheetColumn>
|
|
545
|
-
)}
|
|
518
|
+
{/* Auto delete column (inline edit only) */}
|
|
519
|
+
<Show when={deleteProp() != null && canEdit()}>
|
|
520
|
+
<DataSheetColumn<TItem>
|
|
521
|
+
key="__delete"
|
|
522
|
+
header=""
|
|
523
|
+
fixed
|
|
524
|
+
sortable={false}
|
|
525
|
+
resizable={false}
|
|
526
|
+
>
|
|
527
|
+
{(dsCtx) => (
|
|
528
|
+
<div class="flex items-center justify-center px-1 py-0.5">
|
|
529
|
+
<Link
|
|
530
|
+
theme="danger"
|
|
531
|
+
disabled={!(local.itemDeletable?.(dsCtx.item) ?? true)}
|
|
532
|
+
onClick={() => handleToggleDelete(dsCtx.item, dsCtx.index)}
|
|
533
|
+
>
|
|
534
|
+
<Icon icon={isItemDeleted(dsCtx.item) ? IconTrashOff : IconTrash} />
|
|
535
|
+
</Link>
|
|
536
|
+
</div>
|
|
537
|
+
)}
|
|
538
|
+
</DataSheetColumn>
|
|
546
539
|
</Show>
|
|
547
540
|
|
|
548
541
|
{/* User-defined columns -- map CrudSheetColumn to DataSheetColumn */}
|
|
@@ -574,20 +567,23 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
574
567
|
// modalEdit editable column -- wrap with edit link
|
|
575
568
|
if (
|
|
576
569
|
local.modalEdit &&
|
|
577
|
-
col.
|
|
570
|
+
col.editTrigger &&
|
|
578
571
|
canEdit() &&
|
|
579
572
|
(local.itemEditable?.(dsCtx.item) ?? true)
|
|
580
573
|
) {
|
|
581
574
|
return (
|
|
582
575
|
<Link
|
|
583
|
-
class="flex
|
|
576
|
+
class={clsx("flex", "gap-1")}
|
|
584
577
|
onClick={(e) => {
|
|
585
578
|
e.preventDefault();
|
|
586
579
|
e.stopPropagation();
|
|
587
580
|
void handleEditItem(dsCtx.item);
|
|
588
581
|
}}
|
|
589
582
|
>
|
|
590
|
-
{
|
|
583
|
+
<div class={"p-1"}>
|
|
584
|
+
<Icon icon={IconExternalLink} />
|
|
585
|
+
</div>
|
|
586
|
+
<div class={"flex-1"}>{col.cell(crudCtx)}</div>
|
|
591
587
|
</Link>
|
|
592
588
|
);
|
|
593
589
|
}
|
|
@@ -606,10 +602,10 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
606
602
|
<div class="flex-1" />
|
|
607
603
|
<Show when={selectedItems().length > 0}>
|
|
608
604
|
<Button size="sm" theme="danger" onClick={handleSelectCancel}>
|
|
609
|
-
{local.selectMode === "
|
|
605
|
+
{local.selectMode === "multiple" ? "모두" : "선택"} 해제
|
|
610
606
|
</Button>
|
|
611
607
|
</Show>
|
|
612
|
-
<Show when={local.selectMode === "
|
|
608
|
+
<Show when={local.selectMode === "multiple"}>
|
|
613
609
|
<Button size="sm" theme="primary" onClick={handleSelectConfirm}>
|
|
614
610
|
확인({selectedItems().length})
|
|
615
611
|
</Button>
|
|
@@ -28,7 +28,7 @@ export function CrudSheetColumn<TItem>(props: CrudSheetColumnProps<TItem>): JSX.
|
|
|
28
28
|
width: props.width,
|
|
29
29
|
sortable: props.sortable ?? true,
|
|
30
30
|
resizable: props.resizable ?? true,
|
|
31
|
-
|
|
31
|
+
editTrigger: props.editTrigger ?? false,
|
|
32
32
|
} as unknown as JSX.Element;
|
|
33
33
|
}
|
|
34
34
|
/* eslint-enable solid/reactivity */
|
|
@@ -80,11 +80,12 @@ interface CrudSheetBaseProps<TItem, TFilter extends Record<string, any>> {
|
|
|
80
80
|
editable?: boolean;
|
|
81
81
|
itemEditable?: (item: TItem) => boolean;
|
|
82
82
|
itemDeletable?: (item: TItem) => boolean;
|
|
83
|
+
itemDeleted?: (item: TItem) => boolean;
|
|
83
84
|
filterInitial?: TFilter;
|
|
84
85
|
items?: TItem[];
|
|
85
86
|
onItemsChange?: (items: TItem[]) => void;
|
|
86
87
|
excel?: ExcelConfig<TItem>;
|
|
87
|
-
selectMode?: "single" | "
|
|
88
|
+
selectMode?: "single" | "multiple";
|
|
88
89
|
onSelect?: (result: SelectResult<TItem>) => void;
|
|
89
90
|
hideAutoTools?: boolean;
|
|
90
91
|
class?: string;
|
|
@@ -108,12 +109,12 @@ export interface CrudSheetColumnDef<TItem> {
|
|
|
108
109
|
class?: string;
|
|
109
110
|
sortable: boolean;
|
|
110
111
|
resizable: boolean;
|
|
111
|
-
|
|
112
|
+
editTrigger: boolean;
|
|
112
113
|
cell: (ctx: CrudSheetCellContext<TItem>) => JSX.Element;
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
export interface CrudSheetColumnProps<TItem> extends Omit<DataSheetColumnProps<TItem>, "children"> {
|
|
116
|
-
|
|
117
|
+
editTrigger?: boolean;
|
|
117
118
|
children: (ctx: CrudSheetCellContext<TItem>) => JSX.Element;
|
|
118
119
|
}
|
|
119
120
|
|
|
@@ -18,6 +18,7 @@ import { Checkbox } from "../../form-control/checkbox/Checkbox";
|
|
|
18
18
|
import { Icon } from "../../display/Icon";
|
|
19
19
|
import { BusyContainer } from "../../feedback/busy/BusyContainer";
|
|
20
20
|
import { createControllableSignal } from "../../../hooks/createControllableSignal";
|
|
21
|
+
import { createSlotSignal } from "../../../hooks/createSlotSignal";
|
|
21
22
|
import "./Kanban.css";
|
|
22
23
|
import { iconButtonBase } from "../../../styles/patterns.styles";
|
|
23
24
|
import {
|
|
@@ -386,11 +387,8 @@ const KanbanLane: ParentComponent<KanbanLaneProps> = (props) => {
|
|
|
386
387
|
};
|
|
387
388
|
|
|
388
389
|
// Slot signals
|
|
389
|
-
|
|
390
|
-
const [
|
|
391
|
-
const [tools, _setTools] = createSignal<SlotAccessor>();
|
|
392
|
-
const setTitle = (content: SlotAccessor) => _setTitle(() => content);
|
|
393
|
-
const setTools = (content: SlotAccessor) => _setTools(() => content);
|
|
390
|
+
const [title, setTitle] = createSlotSignal();
|
|
391
|
+
const [tools, setTools] = createSlotSignal();
|
|
394
392
|
|
|
395
393
|
const laneContextValue: KanbanLaneContextValue = {
|
|
396
394
|
value: () => local.value,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { createContext, useContext, type Accessor, type
|
|
1
|
+
import { createContext, useContext, type Accessor, type Setter } from "solid-js";
|
|
2
|
+
import type { SlotAccessor } from "../../../hooks/createSlotSignal";
|
|
2
3
|
|
|
3
4
|
// ── 타입 ──────────────────────────────────────────────────────
|
|
4
5
|
|
|
@@ -50,8 +51,6 @@ export function useKanbanContext(): KanbanContextValue {
|
|
|
50
51
|
|
|
51
52
|
// ── Lane Context ───────────────────────────────────────────────
|
|
52
53
|
|
|
53
|
-
type SlotAccessor = (() => JSX.Element) | undefined;
|
|
54
|
-
|
|
55
54
|
export interface KanbanLaneContextValue<L = unknown, T = unknown> {
|
|
56
55
|
value: Accessor<L | undefined>;
|
|
57
56
|
dropTarget: Accessor<KanbanDropTarget<T> | undefined>;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type Component,
|
|
3
3
|
createContext,
|
|
4
|
-
createSignal,
|
|
5
4
|
type JSX,
|
|
6
5
|
onCleanup,
|
|
7
6
|
type ParentComponent,
|
|
@@ -16,6 +15,7 @@ import { twMerge } from "tailwind-merge";
|
|
|
16
15
|
import { ripple } from "../../../directives/ripple";
|
|
17
16
|
import { Collapse } from "../../disclosure/Collapse";
|
|
18
17
|
import { createControllableSignal } from "../../../hooks/createControllableSignal";
|
|
18
|
+
import { createSlotSignal, type SlotAccessor } from "../../../hooks/createSlotSignal";
|
|
19
19
|
import { useListContext } from "./ListContext";
|
|
20
20
|
import { List } from "./List";
|
|
21
21
|
import {
|
|
@@ -32,8 +32,6 @@ import type { ComponentSize } from "../../../styles/tokens.styles";
|
|
|
32
32
|
|
|
33
33
|
void ripple;
|
|
34
34
|
|
|
35
|
-
type SlotAccessor = (() => JSX.Element) | undefined;
|
|
36
|
-
|
|
37
35
|
interface ListItemSlotsContextValue {
|
|
38
36
|
setChildren: (content: SlotAccessor) => void;
|
|
39
37
|
}
|
|
@@ -166,8 +164,7 @@ export const ListItem: ListItemComponent = (props) => {
|
|
|
166
164
|
onChange: () => local.onOpenChange,
|
|
167
165
|
});
|
|
168
166
|
|
|
169
|
-
const [childrenSlot,
|
|
170
|
-
const setChildrenSlot = (content: SlotAccessor) => _setChildrenSlot(() => content);
|
|
167
|
+
const [childrenSlot, setChildrenSlot] = createSlotSignal();
|
|
171
168
|
const hasChildren = () => childrenSlot() !== undefined;
|
|
172
169
|
|
|
173
170
|
const useRipple = () => !(local.readonly || local.disabled);
|
|
@@ -3,7 +3,8 @@ import { borderDefault, borderSubtle } from "../../../styles/tokens.styles";
|
|
|
3
3
|
|
|
4
4
|
export const dataSheetContainerClass = clsx(
|
|
5
5
|
"relative",
|
|
6
|
-
"bg-white dark:bg-base-950",
|
|
6
|
+
// "bg-white dark:bg-base-950",
|
|
7
|
+
"bg-base-100 dark:bg-base-900",
|
|
7
8
|
"overflow-auto",
|
|
8
9
|
);
|
|
9
10
|
|
|
@@ -50,13 +51,7 @@ export const sortableThClass = clsx("cursor-pointer", "hover:underline");
|
|
|
50
51
|
export const sortIconClass = clsx("px-1 py-0.5", "bg-base-100 dark:bg-base-900");
|
|
51
52
|
|
|
52
53
|
// 상단 툴바 (설정 버튼 + 페이지네이션)
|
|
53
|
-
export const toolbarClass = clsx(
|
|
54
|
-
"flex items-center gap-2",
|
|
55
|
-
"px-2 py-1",
|
|
56
|
-
"bg-base-50 dark:bg-base-900",
|
|
57
|
-
"border-b",
|
|
58
|
-
borderDefault,
|
|
59
|
-
);
|
|
54
|
+
export const toolbarClass = clsx("flex items-center gap-2", "px-2 py-1", "border-b", borderDefault);
|
|
60
55
|
|
|
61
56
|
// 고정 컬럼 기본 (sticky)
|
|
62
57
|
export const fixedClass = "sticky";
|
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
import {
|
|
2
|
-
type JSX,
|
|
3
|
-
type ParentComponent,
|
|
4
2
|
createContext,
|
|
5
3
|
createEffect,
|
|
4
|
+
createSignal,
|
|
6
5
|
createUniqueId,
|
|
6
|
+
For,
|
|
7
|
+
type JSX,
|
|
7
8
|
onCleanup,
|
|
9
|
+
type ParentComponent,
|
|
8
10
|
Show,
|
|
9
11
|
splitProps,
|
|
10
|
-
For,
|
|
11
12
|
useContext,
|
|
12
|
-
createSignal,
|
|
13
13
|
} from "solid-js";
|
|
14
14
|
import { Portal } from "solid-js/web";
|
|
15
15
|
import clsx from "clsx";
|
|
16
16
|
import { twMerge } from "tailwind-merge";
|
|
17
17
|
import { IconX } from "@tabler/icons-solidjs";
|
|
18
18
|
import { createControllableSignal } from "../../hooks/createControllableSignal";
|
|
19
|
+
import { createSlotSignal, type SlotAccessor } from "../../hooks/createSlotSignal";
|
|
19
20
|
import { createMountTransition } from "../../hooks/createMountTransition";
|
|
20
21
|
import { createPointerDrag } from "../../hooks/createPointerDrag";
|
|
21
22
|
import { mergeStyles } from "../../helpers/mergeStyles";
|
|
22
23
|
import { Icon } from "../display/Icon";
|
|
23
24
|
import { borderSubtle } from "../../styles/tokens.styles";
|
|
24
25
|
import { DialogDefaultsContext } from "./DialogContext";
|
|
25
|
-
import { registerDialog, unregisterDialog
|
|
26
|
-
|
|
27
|
-
type SlotAccessor = (() => JSX.Element) | undefined;
|
|
26
|
+
import { bringToFront, registerDialog, unregisterDialog } from "./dialogZIndex";
|
|
27
|
+
import { Button } from "../form-control/Button";
|
|
28
28
|
|
|
29
29
|
interface DialogSlotsContextValue {
|
|
30
30
|
setHeader: (content: SlotAccessor) => void;
|
|
@@ -188,10 +188,8 @@ export const Dialog: DialogComponent = (props) => {
|
|
|
188
188
|
|
|
189
189
|
const headerId = "dialog-header-" + createUniqueId();
|
|
190
190
|
|
|
191
|
-
const [header,
|
|
192
|
-
const
|
|
193
|
-
const [action, _setAction] = createSignal<SlotAccessor>();
|
|
194
|
-
const setAction = (content: SlotAccessor) => _setAction(() => content);
|
|
191
|
+
const [header, setHeader] = createSlotSignal();
|
|
192
|
+
const [action, setAction] = createSlotSignal();
|
|
195
193
|
const hasHeader = () => header() !== undefined;
|
|
196
194
|
|
|
197
195
|
const [open, setOpen] = createControllableSignal({
|
|
@@ -212,11 +210,19 @@ export const Dialog: DialogComponent = (props) => {
|
|
|
212
210
|
local.onCloseComplete?.();
|
|
213
211
|
};
|
|
214
212
|
|
|
215
|
-
// open 변경 시 closeCompleteEmitted 초기화
|
|
213
|
+
// open 변경 시 closeCompleteEmitted 초기화 + fallback unmount 감지
|
|
214
|
+
let wasMounted = false;
|
|
216
215
|
createEffect(() => {
|
|
217
216
|
if (open()) {
|
|
218
217
|
closeCompleteEmitted = false;
|
|
219
218
|
}
|
|
219
|
+
if (mounted()) {
|
|
220
|
+
wasMounted = true;
|
|
221
|
+
} else if (wasMounted) {
|
|
222
|
+
// fallback timer가 transitionend보다 먼저 실행되어 DOM이 제거된 경우,
|
|
223
|
+
// onCloseComplete가 호출되지 않는 문제 방지
|
|
224
|
+
emitCloseComplete();
|
|
225
|
+
}
|
|
220
226
|
});
|
|
221
227
|
|
|
222
228
|
// dialog ref
|
|
@@ -477,7 +483,8 @@ export const Dialog: DialogComponent = (props) => {
|
|
|
477
483
|
);
|
|
478
484
|
|
|
479
485
|
// 헤더 클래스
|
|
480
|
-
const headerClass = () =>
|
|
486
|
+
const headerClass = () =>
|
|
487
|
+
clsx("flex items-center gap-2", "px-3 py-1", "select-none", "border-b", borderSubtle);
|
|
481
488
|
|
|
482
489
|
return (
|
|
483
490
|
<Show when={mounted()}>
|
|
@@ -516,26 +523,19 @@ export const Dialog: DialogComponent = (props) => {
|
|
|
516
523
|
}
|
|
517
524
|
onPointerDown={handleHeaderPointerDown}
|
|
518
525
|
>
|
|
519
|
-
<h5 id={headerId} class={clsx("flex-1
|
|
526
|
+
<h5 id={headerId} class={clsx("flex-1 font-bold")}>
|
|
520
527
|
{header()!()}
|
|
521
528
|
</h5>
|
|
522
529
|
<Show when={action()}>{action()!()}</Show>
|
|
523
530
|
<Show when={local.closable ?? true}>
|
|
524
|
-
<
|
|
531
|
+
<Button
|
|
525
532
|
data-modal-close
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
"inline-flex items-center justify-center",
|
|
529
|
-
"px-3 py-2",
|
|
530
|
-
"text-base-400 dark:text-base-500",
|
|
531
|
-
"hover:text-base-600 dark:hover:text-base-300",
|
|
532
|
-
"cursor-pointer",
|
|
533
|
-
"transition-colors",
|
|
534
|
-
)}
|
|
533
|
+
size={"sm"}
|
|
534
|
+
variant={"ghost"}
|
|
535
535
|
onClick={handleCloseClick}
|
|
536
536
|
>
|
|
537
|
-
<Icon icon={IconX}
|
|
538
|
-
</
|
|
537
|
+
<Icon icon={IconX} />
|
|
538
|
+
</Button>
|
|
539
539
|
</Show>
|
|
540
540
|
</div>
|
|
541
541
|
</Show>
|