@simplysm/solid 13.0.84 → 13.0.86
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 +143 -28
- package/dist/components/data/list/ListItem.d.ts.map +1 -1
- package/dist/components/data/list/ListItem.js +11 -4
- package/dist/components/data/list/ListItem.js.map +2 -2
- package/dist/components/data/list/ListItem.styles.d.ts +2 -0
- package/dist/components/data/list/ListItem.styles.d.ts.map +1 -1
- package/dist/components/data/list/ListItem.styles.js +11 -1
- package/dist/components/data/list/ListItem.styles.js.map +1 -1
- package/dist/components/data/sheet/DataSheet.d.ts.map +1 -1
- package/dist/components/data/sheet/DataSheet.js +6 -9
- package/dist/components/data/sheet/DataSheet.js.map +2 -2
- package/dist/components/data/sheet/hooks/createDataSheetExpansion.d.ts.map +1 -1
- package/dist/components/data/sheet/hooks/createDataSheetExpansion.js +15 -17
- package/dist/components/data/sheet/hooks/createDataSheetExpansion.js.map +1 -1
- package/dist/components/data/sheet/hooks/createDataSheetReorder.d.ts.map +1 -1
- package/dist/components/data/sheet/hooks/createDataSheetReorder.js +12 -12
- package/dist/components/data/sheet/hooks/createDataSheetReorder.js.map +1 -1
- package/dist/components/data/sheet/hooks/createDataSheetSelection.d.ts.map +1 -1
- package/dist/components/data/sheet/hooks/createDataSheetSelection.js +9 -3
- package/dist/components/data/sheet/hooks/createDataSheetSelection.js.map +1 -1
- package/dist/components/disclosure/Dialog.d.ts.map +1 -1
- package/dist/components/disclosure/Dialog.js +3 -21
- 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 +1 -11
- package/dist/components/disclosure/Dropdown.js.map +2 -2
- package/dist/components/disclosure/Tabs.d.ts.map +1 -1
- package/dist/components/disclosure/Tabs.js +1 -3
- package/dist/components/disclosure/Tabs.js.map +2 -2
- package/dist/components/features/crud-detail/CrudDetail.js +103 -102
- package/dist/components/features/crud-detail/CrudDetail.js.map +2 -2
- package/dist/components/features/crud-sheet/CrudSheet.d.ts.map +1 -1
- package/dist/components/features/crud-sheet/CrudSheet.js +10 -5
- package/dist/components/features/crud-sheet/CrudSheet.js.map +2 -2
- package/dist/components/features/data-select-button/DataSelectButton.d.ts.map +1 -1
- package/dist/components/features/data-select-button/DataSelectButton.js +30 -26
- package/dist/components/features/data-select-button/DataSelectButton.js.map +2 -2
- package/dist/components/features/permission-table/PermissionTable.js +5 -1
- package/dist/components/features/permission-table/PermissionTable.js.map +2 -2
- package/dist/components/feedback/busy/BusyContainer.d.ts.map +1 -1
- package/dist/components/feedback/busy/BusyContainer.js +1 -6
- package/dist/components/feedback/busy/BusyContainer.js.map +2 -2
- package/dist/components/form-control/DropdownTrigger.styles.js +1 -1
- package/dist/components/form-control/checkbox/SelectableBase.d.ts.map +1 -1
- package/dist/components/form-control/checkbox/SelectableBase.js +2 -4
- package/dist/components/form-control/checkbox/SelectableBase.js.map +2 -2
- package/dist/components/form-control/combobox/Combobox.d.ts +19 -5
- package/dist/components/form-control/combobox/Combobox.d.ts.map +1 -1
- package/dist/components/form-control/combobox/Combobox.js +2 -4
- package/dist/components/form-control/combobox/Combobox.js.map +1 -1
- package/dist/components/form-control/date-range-picker/DateRangePicker.d.ts +2 -2
- package/dist/components/form-control/date-range-picker/DateRangePicker.d.ts.map +1 -1
- package/dist/components/form-control/date-range-picker/DateRangePicker.js +11 -3
- package/dist/components/form-control/date-range-picker/DateRangePicker.js.map +2 -2
- package/dist/components/form-control/editor/RichTextEditor.d.ts +2 -2
- package/dist/components/form-control/editor/RichTextEditor.d.ts.map +1 -1
- package/dist/components/form-control/editor/RichTextEditor.js +2 -4
- package/dist/components/form-control/editor/RichTextEditor.js.map +2 -2
- package/dist/components/form-control/field/DatePicker.d.ts +2 -2
- package/dist/components/form-control/field/DatePicker.d.ts.map +1 -1
- package/dist/components/form-control/field/DatePicker.js.map +1 -1
- package/dist/components/form-control/field/DateTimePicker.d.ts +2 -2
- package/dist/components/form-control/field/DateTimePicker.d.ts.map +1 -1
- package/dist/components/form-control/field/DateTimePicker.js.map +1 -1
- package/dist/components/form-control/field/Field.styles.d.ts +6 -7
- package/dist/components/form-control/field/Field.styles.d.ts.map +1 -1
- package/dist/components/form-control/field/Field.styles.js.map +1 -1
- package/dist/components/form-control/field/NumberInput.d.ts +2 -2
- package/dist/components/form-control/field/NumberInput.d.ts.map +1 -1
- package/dist/components/form-control/field/NumberInput.js +7 -7
- package/dist/components/form-control/field/NumberInput.js.map +2 -2
- package/dist/components/form-control/field/TextInput.d.ts +2 -2
- package/dist/components/form-control/field/TextInput.d.ts.map +1 -1
- package/dist/components/form-control/field/TextInput.js.map +1 -1
- package/dist/components/form-control/field/Textarea.d.ts +2 -2
- package/dist/components/form-control/field/Textarea.d.ts.map +1 -1
- package/dist/components/form-control/field/Textarea.js +1 -3
- package/dist/components/form-control/field/Textarea.js.map +2 -2
- package/dist/components/form-control/field/TimePicker.d.ts +2 -2
- package/dist/components/form-control/field/TimePicker.d.ts.map +1 -1
- package/dist/components/form-control/field/TimePicker.js.map +1 -1
- package/dist/components/form-control/numpad/Numpad.d.ts.map +1 -1
- package/dist/components/form-control/numpad/Numpad.js +4 -17
- package/dist/components/form-control/numpad/Numpad.js.map +2 -2
- package/dist/components/form-control/select/Select.d.ts +2 -0
- package/dist/components/form-control/select/Select.d.ts.map +1 -1
- package/dist/components/form-control/select/Select.js +29 -15
- package/dist/components/form-control/select/Select.js.map +2 -2
- package/dist/components/form-control/state-preset/StatePreset.d.ts +1 -3
- package/dist/components/form-control/state-preset/StatePreset.d.ts.map +1 -1
- package/dist/components/form-control/state-preset/StatePreset.js +69 -95
- package/dist/components/form-control/state-preset/StatePreset.js.map +2 -2
- package/dist/components/layout/FormGroup.js +1 -1
- package/dist/components/layout/FormGroup.js.map +1 -1
- package/dist/components/layout/FormTable.js +3 -3
- package/dist/components/layout/FormTable.js.map +1 -1
- package/dist/components/layout/sidebar/Sidebar.d.ts.map +1 -1
- package/dist/components/layout/sidebar/Sidebar.js +3 -6
- package/dist/components/layout/sidebar/Sidebar.js.map +2 -2
- package/dist/components/layout/topbar/Topbar.js +1 -3
- package/dist/components/layout/topbar/Topbar.js.map +2 -2
- package/dist/hooks/createControllableStore.d.ts.map +1 -1
- package/dist/hooks/createControllableStore.js +8 -5
- package/dist/hooks/createControllableStore.js.map +1 -1
- package/dist/hooks/useLocalStorage.d.ts.map +1 -1
- package/dist/hooks/useLocalStorage.js +3 -2
- package/dist/hooks/useLocalStorage.js.map +1 -1
- package/dist/hooks/useSyncConfig.d.ts.map +1 -1
- package/dist/hooks/useSyncConfig.js +5 -4
- package/dist/hooks/useSyncConfig.js.map +1 -1
- package/dist/providers/i18n/locales/en.d.ts +2 -3
- package/dist/providers/i18n/locales/en.d.ts.map +1 -1
- package/dist/providers/i18n/locales/en.js +3 -4
- package/dist/providers/i18n/locales/en.js.map +1 -1
- package/dist/providers/i18n/locales/ko.d.ts +2 -3
- package/dist/providers/i18n/locales/ko.d.ts.map +1 -1
- package/dist/providers/i18n/locales/ko.js +3 -4
- package/dist/providers/i18n/locales/ko.js.map +1 -1
- package/dist/providers/shared-data/SharedDataProvider.d.ts.map +1 -1
- package/dist/providers/shared-data/SharedDataProvider.js +0 -1
- package/dist/providers/shared-data/SharedDataProvider.js.map +1 -1
- package/docs/display-feedback.md +279 -0
- package/docs/features.md +357 -213
- package/docs/form-controls.md +261 -403
- package/docs/layout-data.md +386 -0
- package/docs/providers-hooks.md +411 -0
- package/package.json +5 -5
- package/src/components/data/list/ListItem.styles.ts +14 -2
- package/src/components/data/list/ListItem.tsx +13 -4
- package/src/components/data/sheet/DataSheet.tsx +6 -10
- package/src/components/data/sheet/hooks/createDataSheetExpansion.ts +17 -18
- package/src/components/data/sheet/hooks/createDataSheetReorder.ts +12 -13
- package/src/components/data/sheet/hooks/createDataSheetSelection.ts +9 -3
- package/src/components/disclosure/Dialog.tsx +45 -59
- package/src/components/disclosure/Dropdown.tsx +4 -14
- package/src/components/disclosure/Tabs.tsx +12 -17
- package/src/components/features/crud-detail/CrudDetail.tsx +4 -4
- package/src/components/features/crud-sheet/CrudSheet.tsx +12 -5
- package/src/components/features/data-select-button/DataSelectButton.tsx +39 -32
- package/src/components/features/permission-table/PermissionTable.tsx +1 -1
- package/src/components/feedback/busy/BusyContainer.tsx +12 -18
- package/src/components/form-control/DropdownTrigger.styles.ts +1 -1
- package/src/components/form-control/checkbox/SelectableBase.tsx +10 -16
- package/src/components/form-control/combobox/Combobox.tsx +42 -16
- package/src/components/form-control/date-range-picker/DateRangePicker.tsx +7 -8
- package/src/components/form-control/editor/RichTextEditor.tsx +14 -16
- package/src/components/form-control/field/DatePicker.tsx +3 -2
- package/src/components/form-control/field/DateTimePicker.tsx +3 -2
- package/src/components/form-control/field/Field.styles.ts +6 -8
- package/src/components/form-control/field/NumberInput.tsx +9 -10
- package/src/components/form-control/field/TextInput.tsx +3 -2
- package/src/components/form-control/field/Textarea.tsx +14 -12
- package/src/components/form-control/field/TimePicker.tsx +3 -2
- package/src/components/form-control/numpad/Numpad.tsx +16 -18
- package/src/components/form-control/select/Select.tsx +41 -13
- package/src/components/form-control/state-preset/StatePreset.tsx +39 -71
- package/src/components/layout/FormGroup.tsx +1 -1
- package/src/components/layout/FormTable.tsx +3 -3
- package/src/components/layout/sidebar/Sidebar.tsx +2 -3
- package/src/components/layout/topbar/Topbar.tsx +2 -2
- package/src/hooks/createControllableStore.ts +8 -4
- package/src/hooks/useLocalStorage.ts +3 -2
- package/src/hooks/useSyncConfig.ts +5 -4
- package/src/providers/i18n/locales/en.ts +2 -3
- package/src/providers/i18n/locales/ko.ts +2 -3
- package/src/providers/shared-data/SharedDataProvider.tsx +0 -1
- package/tests/components/features/crud-detail/CrudDetail.spec.tsx +49 -0
- package/tests/components/features/data-select-button/DataSelectButton.spec.tsx +62 -7
- package/tests/components/form-control/combobox/Combobox.spec.tsx +3 -3
- package/tests/components/form-control/date-range-picker/DateRangePicker.spec.tsx +56 -0
- package/tests/components/form-control/select/SelectItem.spec.tsx +5 -0
- package/tests/providers/shared-data/SharedDataProvider.spec.tsx +0 -104
- package/docs/data.md +0 -204
- package/docs/disclosure.md +0 -146
- package/docs/display.md +0 -125
- package/docs/feedback.md +0 -156
- package/docs/helpers.md +0 -173
- package/docs/hooks.md +0 -146
- package/docs/layout.md +0 -94
- package/docs/providers.md +0 -180
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
# 프로바이더 & 훅
|
|
2
|
+
|
|
3
|
+
## SystemProvider
|
|
4
|
+
|
|
5
|
+
모든 필수 프로바이더를 한 번에 감싸는 편의 컴포넌트. 대부분의 앱에서는 이것 하나로 충분하다.
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { SystemProvider } from "@simplysm/solid";
|
|
9
|
+
|
|
10
|
+
<SystemProvider clientName="my-app" busyVariant="spinner">
|
|
11
|
+
<App />
|
|
12
|
+
</SystemProvider>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
| Prop | 타입 | 설명 |
|
|
16
|
+
|------|------|------|
|
|
17
|
+
| `clientName` | `string` | 클라이언트 식별 이름 (localStorage 키 접두사 등에 사용) |
|
|
18
|
+
| `busyVariant` | `"spinner" \| "bar"` | 글로벌 BusyProvider 변형 |
|
|
19
|
+
|
|
20
|
+
내부 프로바이더 스택 (위에서 아래 순서):
|
|
21
|
+
`ConfigProvider` > `I18nProvider` > `SyncStorageProvider` > `LoggerProvider` > `NotificationProvider` + `NotificationBanner` > `ErrorLoggerProvider` > `PwaUpdateProvider` > `ClipboardProvider` > `ThemeProvider` > `ServiceClientProvider` > `SharedDataProvider` > `BusyProvider`
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## ThemeProvider
|
|
26
|
+
|
|
27
|
+
라이트/다크/시스템 테마 관리. localStorage에 설정 저장.
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { ThemeProvider, useTheme } from "@simplysm/solid";
|
|
31
|
+
|
|
32
|
+
<ThemeProvider>
|
|
33
|
+
<App />
|
|
34
|
+
</ThemeProvider>
|
|
35
|
+
|
|
36
|
+
const theme = useTheme();
|
|
37
|
+
|
|
38
|
+
theme.mode(); // "light" | "dark" | "system"
|
|
39
|
+
theme.resolvedTheme(); // "light" | "dark" (OS 설정 반영)
|
|
40
|
+
theme.setMode("dark");
|
|
41
|
+
theme.cycleMode(); // light → system → dark → light
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## I18nProvider
|
|
47
|
+
|
|
48
|
+
다국어 지원. 한국어(ko), 영어(en) 내장.
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import { I18nProvider, useI18n } from "@simplysm/solid";
|
|
52
|
+
|
|
53
|
+
<I18nProvider>
|
|
54
|
+
<App />
|
|
55
|
+
</I18nProvider>
|
|
56
|
+
|
|
57
|
+
const i18n = useI18n();
|
|
58
|
+
|
|
59
|
+
i18n.t("save"); // 번역 조회
|
|
60
|
+
i18n.t("greeting", { name: "Alice" }); // 파라미터 치환
|
|
61
|
+
i18n.locale(); // 현재 로케일
|
|
62
|
+
i18n.setLocale("en");
|
|
63
|
+
|
|
64
|
+
// 사전 확장
|
|
65
|
+
i18n.configure({
|
|
66
|
+
dictionaries: {
|
|
67
|
+
ko: { myKey: "내 값" },
|
|
68
|
+
en: { myKey: "My Value" },
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## SharedDataProvider
|
|
76
|
+
|
|
77
|
+
서버 데이터를 구독하고 실시간 동기화하는 프로바이더. `ServiceClientProvider`와 `NotificationProvider` 내부에서 사용해야 한다.
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
import { SharedDataProvider, useSharedData } from "@simplysm/solid";
|
|
81
|
+
|
|
82
|
+
// 프로바이더 설정 (SystemProvider 사용 시 자동 포함)
|
|
83
|
+
<SharedDataProvider>
|
|
84
|
+
<App />
|
|
85
|
+
</SharedDataProvider>
|
|
86
|
+
|
|
87
|
+
// 데이터 정의 (자식 컴포넌트에서 한 번만 호출)
|
|
88
|
+
const sharedData = useSharedData<{
|
|
89
|
+
users: User;
|
|
90
|
+
departments: Department;
|
|
91
|
+
}>();
|
|
92
|
+
|
|
93
|
+
sharedData.configure(() => ({
|
|
94
|
+
users: {
|
|
95
|
+
fetch: async (changeKeys) => await api.getUsers(changeKeys),
|
|
96
|
+
getKey: (item) => item.id,
|
|
97
|
+
orderBy: [[(item) => item.name, "asc"]],
|
|
98
|
+
itemSearchText: (item) => item.name,
|
|
99
|
+
isItemHidden: (item) => item.isDeleted,
|
|
100
|
+
},
|
|
101
|
+
departments: {
|
|
102
|
+
fetch: async (changeKeys) => await api.getDepartments(changeKeys),
|
|
103
|
+
getKey: (item) => item.id,
|
|
104
|
+
orderBy: [[(item) => item.sortOrder, "asc"]],
|
|
105
|
+
getParentKey: (item) => item.parentId, // 트리 구조 지원
|
|
106
|
+
},
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
// 데이터 사용
|
|
110
|
+
const users = sharedData.users.items(); // 반응형 배열
|
|
111
|
+
const user = sharedData.users.get(userId); // 키로 조회
|
|
112
|
+
|
|
113
|
+
// 변경 이벤트 발행 (서버의 모든 구독자에게 전파)
|
|
114
|
+
await sharedData.users.emit([changedUserId]);
|
|
115
|
+
|
|
116
|
+
// 전체 로딩 대기
|
|
117
|
+
await sharedData.wait();
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### SharedDataDefinition
|
|
121
|
+
|
|
122
|
+
| 속성 | 타입 | 설명 |
|
|
123
|
+
|------|------|------|
|
|
124
|
+
| `fetch` | `(changeKeys?) => Promise<TData[]>` | 데이터 조회 함수 |
|
|
125
|
+
| `getKey` | `(item) => string \| number` | 항목 고유 키 추출 |
|
|
126
|
+
| `orderBy` | `[(item) => unknown, "asc" \| "desc"][]` | 정렬 기준 (다중) |
|
|
127
|
+
| `serviceKey` | `string` | 서비스 연결 키 (기본: `"default"`) |
|
|
128
|
+
| `filter` | `unknown` | 서버 이벤트 필터 |
|
|
129
|
+
| `itemSearchText` | `(item) => string` | 검색 텍스트 추출 |
|
|
130
|
+
| `isItemHidden` | `(item) => boolean` | 숨김 여부 |
|
|
131
|
+
| `getParentKey` | `(item) => string \| number \| undefined` | 부모 키 (트리 구조) |
|
|
132
|
+
|
|
133
|
+
### SharedDataAccessor
|
|
134
|
+
|
|
135
|
+
| 메서드/속성 | 타입 | 설명 |
|
|
136
|
+
|------------|------|------|
|
|
137
|
+
| `items()` | `Accessor<TData[]>` | 반응형 데이터 배열 |
|
|
138
|
+
| `get(key)` | `(key) => TData \| undefined` | 키로 단건 조회 |
|
|
139
|
+
| `emit(changeKeys?)` | `(keys?) => Promise<void>` | 변경 이벤트 발행 |
|
|
140
|
+
| `getKey` | `(item) => string \| number` | 키 추출 함수 |
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## 기타 프로바이더
|
|
145
|
+
|
|
146
|
+
| 프로바이더 | 설명 |
|
|
147
|
+
|-----------|------|
|
|
148
|
+
| `ConfigProvider` | 클라이언트 설정 (localStorage 키 접두사). prop: `clientName: string` |
|
|
149
|
+
| `ServiceClientProvider` | `@simplysm/service-client` WebSocket 연결 통합 |
|
|
150
|
+
| `SyncStorageProvider` | localStorage 동기화 |
|
|
151
|
+
| `LoggerProvider` | 로깅 설정 |
|
|
152
|
+
| `ErrorLoggerProvider` | 글로벌 에러 핸들링 (window.onerror 등) |
|
|
153
|
+
| `ClipboardProvider` | 클립보드 기능 |
|
|
154
|
+
| `PwaUpdateProvider` | PWA Service Worker 업데이트 감지 (5분 간격 폴링, 알림 표시) |
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## 훅
|
|
159
|
+
|
|
160
|
+
### createControllableSignal
|
|
161
|
+
|
|
162
|
+
제어/비제어 컴포넌트 패턴 구현.
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
import { createControllableSignal } from "@simplysm/solid";
|
|
166
|
+
|
|
167
|
+
const [value, setValue] = createControllableSignal({
|
|
168
|
+
value: () => props.value,
|
|
169
|
+
onChange: () => props.onValueChange,
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### createControllableStore
|
|
174
|
+
|
|
175
|
+
객체 상태용 제어/비제어 패턴.
|
|
176
|
+
|
|
177
|
+
### createIMEHandler
|
|
178
|
+
|
|
179
|
+
IME(한글 등) 입력 처리.
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
const ime = createIMEHandler();
|
|
183
|
+
// ime.handleCompositionStart()
|
|
184
|
+
// ime.handleCompositionEnd()
|
|
185
|
+
// ime.handleInput()
|
|
186
|
+
// ime.composingValue()
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### useLocalStorage
|
|
190
|
+
|
|
191
|
+
반응형 localStorage.
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
import { useLocalStorage } from "@simplysm/solid";
|
|
195
|
+
|
|
196
|
+
const [theme, setTheme] = useLocalStorage("theme", "light");
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### useSyncConfig
|
|
200
|
+
|
|
201
|
+
클라이언트명 접두사 붙은 localStorage 동기화.
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
const [config, setConfig] = useSyncConfig("my-setting", defaultValue);
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### useLogger
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
const logger = useLogger();
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### useRouterLink
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
const navigate = useRouterLink();
|
|
217
|
+
navigate("/users");
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### createMountTransition
|
|
221
|
+
|
|
222
|
+
마운트 애니메이션 상태 관리.
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
const { mounted, animating, unmount } = createMountTransition(() => isVisible());
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## createAppStructure
|
|
231
|
+
|
|
232
|
+
앱 메뉴, 라우트, 권한 구조를 선언적으로 정의한다. 모듈별 필터링과 권한 기반 접근 제어를 지원한다.
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
import { createAppStructure } from "@simplysm/solid";
|
|
236
|
+
import type { AppStructureItem } from "@simplysm/solid";
|
|
237
|
+
|
|
238
|
+
type Module = "basic" | "pro" | "enterprise";
|
|
239
|
+
|
|
240
|
+
const items: AppStructureItem<Module>[] = [
|
|
241
|
+
{
|
|
242
|
+
code: "admin",
|
|
243
|
+
title: "관리",
|
|
244
|
+
children: [
|
|
245
|
+
{
|
|
246
|
+
code: "users",
|
|
247
|
+
title: "사용자 관리",
|
|
248
|
+
component: UserPage,
|
|
249
|
+
perms: ["use", "edit"],
|
|
250
|
+
modules: ["basic"],
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
code: "roles",
|
|
254
|
+
title: "역할 관리",
|
|
255
|
+
component: RolePage,
|
|
256
|
+
perms: ["use", "edit"],
|
|
257
|
+
modules: ["pro"],
|
|
258
|
+
subPerms: [
|
|
259
|
+
{ code: "advanced", title: "고급 설정", perms: ["use", "edit"], modules: ["enterprise"] },
|
|
260
|
+
],
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
},
|
|
264
|
+
];
|
|
265
|
+
|
|
266
|
+
const { AppStructureProvider, useAppStructure } = createAppStructure(() => ({
|
|
267
|
+
items,
|
|
268
|
+
usableModules: () => activeModules(),
|
|
269
|
+
permRecord: () => userPermissions(),
|
|
270
|
+
}));
|
|
271
|
+
|
|
272
|
+
// 프로바이더 설정
|
|
273
|
+
<AppStructureProvider>
|
|
274
|
+
<App />
|
|
275
|
+
</AppStructureProvider>
|
|
276
|
+
|
|
277
|
+
// 사용
|
|
278
|
+
const app = useAppStructure();
|
|
279
|
+
|
|
280
|
+
app.usableRoutes(); // 접근 가능한 라우트 배열
|
|
281
|
+
app.usableMenus(); // 접근 가능한 메뉴 트리
|
|
282
|
+
app.usableFlatMenus(); // 플랫 메뉴 배열 (검색용)
|
|
283
|
+
app.usablePerms(); // 권한 트리 (PermissionTable에 전달)
|
|
284
|
+
app.allFlatPerms; // 모든 권한 목록 (관리용)
|
|
285
|
+
app.perms; // 타입 안전한 권한 객체 (app.perms.admin.users.use)
|
|
286
|
+
|
|
287
|
+
app.getTitleChainByHref("/admin/users"); // ["관리", "사용자 관리"]
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### AppStructure 반환 타입
|
|
291
|
+
|
|
292
|
+
| 속성 | 타입 | 설명 |
|
|
293
|
+
|------|------|------|
|
|
294
|
+
| `usableRoutes` | `Accessor<AppRoute[]>` | 모듈/권한 필터링된 라우트 |
|
|
295
|
+
| `usableMenus` | `Accessor<AppMenu[]>` | 필터링된 메뉴 트리 |
|
|
296
|
+
| `usableFlatMenus` | `Accessor<AppFlatMenu[]>` | 플랫 메뉴 배열 |
|
|
297
|
+
| `usablePerms` | `Accessor<AppPerm[]>` | 필터링된 권한 트리 |
|
|
298
|
+
| `allFlatPerms` | `AppFlatPerm[]` | 전체 권한 목록 |
|
|
299
|
+
| `perms` | `InferPerms<TItems>` | 타입 추론된 권한 접근 객체 |
|
|
300
|
+
| `getTitleChainByHref` | `(href: string) => string[]` | href로 타이틀 체인 조회 |
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## 스타일 유틸리티
|
|
305
|
+
|
|
306
|
+
### 기본 스타일
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
import { bg, border, text } from "@simplysm/solid";
|
|
310
|
+
|
|
311
|
+
// Tailwind 클래스 프리셋
|
|
312
|
+
bg.surface // bg-white dark:bg-base-900
|
|
313
|
+
bg.muted // bg-base-100 dark:bg-base-800
|
|
314
|
+
bg.subtle // bg-base-200 dark:bg-base-700
|
|
315
|
+
border.default // border-base-200 dark:border-base-700
|
|
316
|
+
text.default // text-base-900 dark:text-base-100
|
|
317
|
+
text.muted // text-base-400 dark:text-base-500
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### 컨트롤 스타일
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
import { pad, gap } from "@simplysm/solid";
|
|
324
|
+
|
|
325
|
+
pad.xs // px-1 py-0
|
|
326
|
+
pad.sm // px-1.5 py-0.5
|
|
327
|
+
pad.md // px-2 py-1
|
|
328
|
+
pad.lg // px-3 py-2
|
|
329
|
+
pad.xl // px-4 py-3
|
|
330
|
+
|
|
331
|
+
gap.xs // gap-0
|
|
332
|
+
gap.sm // gap-0.5
|
|
333
|
+
gap.md // gap-1
|
|
334
|
+
gap.lg // gap-1.5
|
|
335
|
+
gap.xl // gap-2
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### 테마 토큰
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
import { themeTokens, type SemanticTheme } from "@simplysm/solid";
|
|
342
|
+
|
|
343
|
+
// 각 semantic theme(base, primary, success, warning, danger, info)별:
|
|
344
|
+
themeTokens.primary.solid // bg-primary-500 text-white
|
|
345
|
+
themeTokens.primary.solidHover // hover:bg-primary-600 dark:hover:bg-primary-400
|
|
346
|
+
themeTokens.primary.light // bg-primary-100 text-primary-900 ...
|
|
347
|
+
themeTokens.primary.text // text-primary-600 dark:text-primary-400
|
|
348
|
+
themeTokens.primary.hoverBg // hover:bg-primary-100 ...
|
|
349
|
+
themeTokens.primary.border // border-primary-300 ...
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## 디렉티브
|
|
355
|
+
|
|
356
|
+
### ripple
|
|
357
|
+
|
|
358
|
+
Material Design 스타일 리플 효과.
|
|
359
|
+
|
|
360
|
+
```tsx
|
|
361
|
+
import { ripple } from "@simplysm/solid";
|
|
362
|
+
|
|
363
|
+
<button use:ripple>클릭</button>
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## 헬퍼
|
|
369
|
+
|
|
370
|
+
### mergeStyles
|
|
371
|
+
|
|
372
|
+
인라인 CSS 문자열 병합.
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
import { mergeStyles } from "@simplysm/solid";
|
|
376
|
+
|
|
377
|
+
mergeStyles("color: red", "font-size: 14px"); // "color: red; font-size: 14px"
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### createSlot / createSlots
|
|
381
|
+
|
|
382
|
+
컴포넌트 합성을 위한 슬롯 패턴. `createSlot`은 단일 슬롯, `createSlots`는 복수 슬롯을 지원한다.
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
import { createSlot } from "@simplysm/solid";
|
|
386
|
+
|
|
387
|
+
// 슬롯 정의
|
|
388
|
+
const [MySlot, createMySlotAccessor] = createSlot<{ children: JSX.Element }>();
|
|
389
|
+
|
|
390
|
+
// 컴포넌트 내부에서 슬롯 접근
|
|
391
|
+
const [slotValue, SlotProvider] = createMySlotAccessor();
|
|
392
|
+
|
|
393
|
+
// 사용
|
|
394
|
+
<SlotProvider>
|
|
395
|
+
<MySlot><span>슬롯 내용</span></MySlot>
|
|
396
|
+
{/* slotValue()로 접근 */}
|
|
397
|
+
</SlotProvider>
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### startPointerDrag
|
|
401
|
+
|
|
402
|
+
포인터 드래그 인터랙션 관리. 포인터 캡처를 설정하고 move/end 이벤트를 관리한다.
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
import { startPointerDrag } from "@simplysm/solid";
|
|
406
|
+
|
|
407
|
+
startPointerDrag(element, event.pointerId, {
|
|
408
|
+
onMove: (e) => { /* 드래그 중 */ },
|
|
409
|
+
onEnd: (e) => { /* 드래그 종료 */ },
|
|
410
|
+
});
|
|
411
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simplysm/solid",
|
|
3
|
-
"version": "13.0.
|
|
3
|
+
"version": "13.0.86",
|
|
4
4
|
"description": "Simplysm package - SolidJS library",
|
|
5
5
|
"author": "simplysm",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -52,10 +52,10 @@
|
|
|
52
52
|
"tabbable": "^6.4.0",
|
|
53
53
|
"tailwind-merge": "^3.5.0",
|
|
54
54
|
"tailwindcss": "^3.4.19",
|
|
55
|
-
"@simplysm/core-browser": "13.0.
|
|
56
|
-
"@simplysm/core-common": "13.0.
|
|
57
|
-
"@simplysm/service-client": "13.0.
|
|
58
|
-
"@simplysm/service-common": "13.0.
|
|
55
|
+
"@simplysm/core-browser": "13.0.86",
|
|
56
|
+
"@simplysm/core-common": "13.0.86",
|
|
57
|
+
"@simplysm/service-client": "13.0.86",
|
|
58
|
+
"@simplysm/service-common": "13.0.86"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@solidjs/testing-library": "^0.8.10"
|
|
@@ -40,8 +40,20 @@ export const listItemDisabledClass = clsx("pointer-events-none cursor-auto opaci
|
|
|
40
40
|
// Readonly state
|
|
41
41
|
export const listItemReadonlyClass = clsx("cursor-auto select-text hover:bg-transparent");
|
|
42
42
|
|
|
43
|
-
// Indent guide (for nested items)
|
|
44
|
-
export const listItemIndentGuideClass = clsx("
|
|
43
|
+
// Indent guide (for nested items, absolutely positioned)
|
|
44
|
+
export const listItemIndentGuideClass = clsx("absolute inset-y-0 border-l", border.default);
|
|
45
|
+
|
|
46
|
+
// Base left padding per size (rem) for indent calculation
|
|
47
|
+
export const listItemBasePadLeft: Record<ComponentSize, number> = {
|
|
48
|
+
xs: 0.25,
|
|
49
|
+
sm: 0.375,
|
|
50
|
+
md: 0.5,
|
|
51
|
+
lg: 0.75,
|
|
52
|
+
xl: 1,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Indent size per nesting level (rem)
|
|
56
|
+
export const LIST_ITEM_INDENT_SIZE = 1.5;
|
|
45
57
|
|
|
46
58
|
// Item content area
|
|
47
59
|
export const listItemContentClass = clsx("flex flex-1 flex-row", "items-center gap-1", "text-left");
|
|
@@ -23,6 +23,8 @@ import {
|
|
|
23
23
|
listItemIndentGuideClass,
|
|
24
24
|
listItemContentClass,
|
|
25
25
|
getListItemSelectedIconClass,
|
|
26
|
+
listItemBasePadLeft,
|
|
27
|
+
LIST_ITEM_INDENT_SIZE,
|
|
26
28
|
} from "./ListItem.styles";
|
|
27
29
|
import type { ComponentSize } from "../../../styles/control.styles";
|
|
28
30
|
|
|
@@ -153,6 +155,13 @@ const ListItemInner: ParentComponent<ListItemProps> = (props) => {
|
|
|
153
155
|
|
|
154
156
|
const getSelectedIconClassName = () => getListItemSelectedIconClass(local.selected ?? false);
|
|
155
157
|
|
|
158
|
+
const indentPaddingLeft = level > 1
|
|
159
|
+
? `${listItemBasePadLeft[local.size ?? "md"] + (level - 1) * LIST_ITEM_INDENT_SIZE}rem`
|
|
160
|
+
: undefined;
|
|
161
|
+
|
|
162
|
+
const getGuideLeft = () =>
|
|
163
|
+
`${listItemBasePadLeft[local.size ?? "md"] + (level - 1) * LIST_ITEM_INDENT_SIZE + LIST_ITEM_INDENT_SIZE * 0.5}rem`;
|
|
164
|
+
|
|
156
165
|
return (
|
|
157
166
|
<ChildrenProvider>
|
|
158
167
|
<button
|
|
@@ -160,7 +169,7 @@ const ListItemInner: ParentComponent<ListItemProps> = (props) => {
|
|
|
160
169
|
type="button"
|
|
161
170
|
use:ripple={useRipple()}
|
|
162
171
|
class={getHeaderClassName()}
|
|
163
|
-
style={local.style}
|
|
172
|
+
style={{ ...(local.style as JSX.CSSProperties), "padding-left": indentPaddingLeft }}
|
|
164
173
|
data-list-item
|
|
165
174
|
role="treeitem"
|
|
166
175
|
aria-expanded={hasChildren() ? openState() : undefined}
|
|
@@ -187,9 +196,9 @@ const ListItemInner: ParentComponent<ListItemProps> = (props) => {
|
|
|
187
196
|
</button>
|
|
188
197
|
<Show when={hasChildren()}>
|
|
189
198
|
<Collapse open={openState()} data-collapsed={!openState() || undefined}>
|
|
190
|
-
<div class="
|
|
191
|
-
<div class={listItemIndentGuideClass} />
|
|
192
|
-
<List inset
|
|
199
|
+
<div class="relative">
|
|
200
|
+
<div class={listItemIndentGuideClass} style={{ left: getGuideLeft() }} />
|
|
201
|
+
<List inset>
|
|
193
202
|
{childrenSlot()!.children}
|
|
194
203
|
</List>
|
|
195
204
|
</div>
|
|
@@ -368,7 +368,6 @@ const DataSheetInner = <TItem,>(props: DataSheetProps<TItem>) => {
|
|
|
368
368
|
);
|
|
369
369
|
|
|
370
370
|
// #region Display
|
|
371
|
-
const displayItems = flatItems;
|
|
372
371
|
|
|
373
372
|
// #region Selection
|
|
374
373
|
const {
|
|
@@ -387,7 +386,7 @@ const DataSheetInner = <TItem,>(props: DataSheetProps<TItem>) => {
|
|
|
387
386
|
get onSelectionChange() { return local.onSelectionChange; },
|
|
388
387
|
get isItemSelectable() { return local.isItemSelectable; },
|
|
389
388
|
},
|
|
390
|
-
|
|
389
|
+
flatItems,
|
|
391
390
|
);
|
|
392
391
|
|
|
393
392
|
// #region AutoSelect
|
|
@@ -408,7 +407,7 @@ const DataSheetInner = <TItem,>(props: DataSheetProps<TItem>) => {
|
|
|
408
407
|
get onItemsReorder() { return local.onItemsReorder; },
|
|
409
408
|
get itemChildren() { return local.itemChildren; },
|
|
410
409
|
},
|
|
411
|
-
|
|
410
|
+
flatItems,
|
|
412
411
|
);
|
|
413
412
|
|
|
414
413
|
// #region Keyboard Navigation (move rows with Enter/Shift+Enter)
|
|
@@ -428,11 +427,8 @@ const DataSheetInner = <TItem,>(props: DataSheetProps<TItem>) => {
|
|
|
428
427
|
if (!tbody) return;
|
|
429
428
|
|
|
430
429
|
const rows = tbody.rows;
|
|
431
|
-
const rowIndex =
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
const cellIndex = Array.from(tr.cells).indexOf(td);
|
|
435
|
-
if (cellIndex < 0) return;
|
|
430
|
+
const rowIndex = tr.sectionRowIndex;
|
|
431
|
+
const cellIndex = td.cellIndex;
|
|
436
432
|
|
|
437
433
|
const targetRowIndex = e.shiftKey ? rowIndex - 1 : rowIndex + 1;
|
|
438
434
|
if (targetRowIndex < 0 || targetRowIndex >= rows.length) return;
|
|
@@ -784,7 +780,7 @@ const DataSheetInner = <TItem,>(props: DataSheetProps<TItem>) => {
|
|
|
784
780
|
<div class={featureCellClickableClass} onClick={() => toggleSelectAll()}>
|
|
785
781
|
<Checkbox
|
|
786
782
|
checked={(() => {
|
|
787
|
-
const selectableItems =
|
|
783
|
+
const selectableItems = flatItems()
|
|
788
784
|
.map((flat) => flat.item)
|
|
789
785
|
.filter((item) => getItemSelectable(item) === true);
|
|
790
786
|
return (
|
|
@@ -829,7 +825,7 @@ const DataSheetInner = <TItem,>(props: DataSheetProps<TItem>) => {
|
|
|
829
825
|
{renderSummaryRow()}
|
|
830
826
|
</thead>
|
|
831
827
|
<tbody>
|
|
832
|
-
<For each={
|
|
828
|
+
<For each={flatItems()}>
|
|
833
829
|
{(flat) => (
|
|
834
830
|
<tr
|
|
835
831
|
data-selected={selection().includes(flat.item) ? "" : undefined}
|
|
@@ -37,20 +37,6 @@ export function createDataSheetExpansion<TItem>(
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
function toggleExpandAll(): void {
|
|
41
|
-
if (!props.itemChildren) return;
|
|
42
|
-
const indexMap = originalIndexMap();
|
|
43
|
-
const allExpandable = collectAllExpandable(
|
|
44
|
-
pagedItems(),
|
|
45
|
-
props.itemChildren,
|
|
46
|
-
(item) => indexMap.get(item) ?? -1,
|
|
47
|
-
);
|
|
48
|
-
const isAllCurrentlyExpanded = allExpandable.every((item) =>
|
|
49
|
-
expandedItems().includes(item),
|
|
50
|
-
);
|
|
51
|
-
setExpandedItems(isAllCurrentlyExpanded ? [] : allExpandable);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
40
|
const flatItems = createMemo((): FlatItem<TItem>[] => {
|
|
55
41
|
const indexMap = originalIndexMap();
|
|
56
42
|
return flattenTree(
|
|
@@ -61,16 +47,29 @@ export function createDataSheetExpansion<TItem>(
|
|
|
61
47
|
);
|
|
62
48
|
});
|
|
63
49
|
|
|
64
|
-
const
|
|
65
|
-
if (!props.itemChildren) return
|
|
50
|
+
const allExpandable = createMemo(() => {
|
|
51
|
+
if (!props.itemChildren) return [];
|
|
66
52
|
const indexMap = originalIndexMap();
|
|
67
|
-
|
|
53
|
+
return collectAllExpandable(
|
|
68
54
|
pagedItems(),
|
|
69
55
|
props.itemChildren,
|
|
70
56
|
(item) => indexMap.get(item) ?? -1,
|
|
71
57
|
);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
function toggleExpandAll(): void {
|
|
61
|
+
const items = allExpandable();
|
|
62
|
+
if (items.length === 0) return;
|
|
63
|
+
const isAllCurrentlyExpanded = items.every((item) =>
|
|
64
|
+
expandedItems().includes(item),
|
|
65
|
+
);
|
|
66
|
+
setExpandedItems(isAllCurrentlyExpanded ? [] : items);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const isAllExpanded = createMemo(() => {
|
|
70
|
+
const items = allExpandable();
|
|
72
71
|
return (
|
|
73
|
-
|
|
72
|
+
items.length > 0 && items.every((item) => expandedItems().includes(item))
|
|
74
73
|
);
|
|
75
74
|
});
|
|
76
75
|
|
|
@@ -37,11 +37,13 @@ export function createDataSheetReorder<TItem>(
|
|
|
37
37
|
const tableEl = target.closest("table")!;
|
|
38
38
|
const tbody = tableEl.querySelector("tbody")!;
|
|
39
39
|
const rows = Array.from(tbody.rows);
|
|
40
|
+
const scrollContainer = tableEl.closest<HTMLElement>("[data-sheet-scroll]");
|
|
40
41
|
|
|
41
42
|
setDragState({ draggingItem: item, targetItem: null, position: null });
|
|
42
43
|
|
|
43
44
|
startPointerDrag(target, e.pointerId, {
|
|
44
45
|
onMove(ev) {
|
|
46
|
+
const items = displayItems();
|
|
45
47
|
let foundTarget: TItem | null = null;
|
|
46
48
|
let foundPosition: "before" | "after" | "inside" | null = null;
|
|
47
49
|
|
|
@@ -50,8 +52,8 @@ export function createDataSheetReorder<TItem>(
|
|
|
50
52
|
const rect = row.getBoundingClientRect();
|
|
51
53
|
if (ev.clientY < rect.top || ev.clientY > rect.bottom) continue;
|
|
52
54
|
|
|
53
|
-
if (i >=
|
|
54
|
-
const flat =
|
|
55
|
+
if (i >= items.length) break;
|
|
56
|
+
const flat = items[i];
|
|
55
57
|
if (flat.item === item) break;
|
|
56
58
|
|
|
57
59
|
// Cannot drop to child items of self
|
|
@@ -82,8 +84,8 @@ export function createDataSheetReorder<TItem>(
|
|
|
82
84
|
rows[i].removeAttribute("data-dragging");
|
|
83
85
|
rows[i].removeAttribute("data-drag-over");
|
|
84
86
|
|
|
85
|
-
if (i <
|
|
86
|
-
const flat =
|
|
87
|
+
if (i < items.length) {
|
|
88
|
+
const flat = items[i];
|
|
87
89
|
if (flat.item === item) {
|
|
88
90
|
rows[i].setAttribute("data-dragging", "");
|
|
89
91
|
}
|
|
@@ -94,22 +96,20 @@ export function createDataSheetReorder<TItem>(
|
|
|
94
96
|
}
|
|
95
97
|
|
|
96
98
|
// before/after indicator
|
|
97
|
-
const indicatorEl =
|
|
98
|
-
.closest("[data-sheet-scroll]")
|
|
99
|
+
const indicatorEl = scrollContainer
|
|
99
100
|
?.querySelector("[data-reorder-indicator]") as HTMLElement | null;
|
|
100
101
|
if (indicatorEl) {
|
|
101
102
|
if (foundTarget != null && foundPosition != null && foundPosition !== "inside") {
|
|
102
|
-
const targetIdx =
|
|
103
|
+
const targetIdx = items.findIndex((f) => f.item === foundTarget);
|
|
103
104
|
if (targetIdx >= 0) {
|
|
104
105
|
const targetRow = rows[targetIdx];
|
|
105
|
-
const containerRect =
|
|
106
|
+
const containerRect = scrollContainer!.getBoundingClientRect();
|
|
106
107
|
const rowRect = targetRow.getBoundingClientRect();
|
|
107
|
-
const scrollEl = tableEl.closest("[data-sheet-scroll]") as HTMLElement;
|
|
108
108
|
|
|
109
109
|
const top =
|
|
110
110
|
foundPosition === "before"
|
|
111
|
-
? rowRect.top - containerRect.top +
|
|
112
|
-
: rowRect.bottom - containerRect.top +
|
|
111
|
+
? rowRect.top - containerRect.top + scrollContainer!.scrollTop
|
|
112
|
+
: rowRect.bottom - containerRect.top + scrollContainer!.scrollTop;
|
|
113
113
|
|
|
114
114
|
indicatorEl.style.display = "block";
|
|
115
115
|
indicatorEl.style.top = `${top}px`;
|
|
@@ -134,8 +134,7 @@ export function createDataSheetReorder<TItem>(
|
|
|
134
134
|
row.removeAttribute("data-dragging");
|
|
135
135
|
row.removeAttribute("data-drag-over");
|
|
136
136
|
}
|
|
137
|
-
const indicatorEl =
|
|
138
|
-
.closest("[data-sheet-scroll]")
|
|
137
|
+
const indicatorEl = scrollContainer
|
|
139
138
|
?.querySelector("[data-reorder-indicator]") as HTMLElement | null;
|
|
140
139
|
if (indicatorEl) {
|
|
141
140
|
indicatorEl.style.display = "none";
|