@simplysm/solid 13.0.58 → 13.0.60
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-sheet/CrudSheet.d.ts.map +1 -1
- package/dist/components/data/crud-sheet/CrudSheet.js +8 -11
- package/dist/components/data/crud-sheet/CrudSheet.js.map +2 -2
- package/dist/components/data/crud-sheet/types.d.ts +1 -1
- 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/disclosure/Dialog.d.ts.map +1 -1
- package/dist/components/disclosure/Dialog.js +3 -4
- 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/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 +8 -2
- 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-sheet/CrudSheet.tsx +8 -11
- package/src/components/data/crud-sheet/types.ts +1 -1
- 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/disclosure/Dialog.tsx +3 -6
- package/src/components/disclosure/Dropdown.tsx +7 -20
- 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/docs/hooks.md
CHANGED
|
@@ -30,13 +30,22 @@ Local-only persistent storage hook. Always uses `localStorage` regardless of `sy
|
|
|
30
30
|
```tsx
|
|
31
31
|
import { useLocalStorage } from "@simplysm/solid";
|
|
32
32
|
|
|
33
|
-
const [token, setToken] = useLocalStorage<string
|
|
33
|
+
const [token, setToken] = useLocalStorage<string>("auth-token");
|
|
34
|
+
|
|
35
|
+
// Set value
|
|
36
|
+
setToken("abc123");
|
|
37
|
+
|
|
38
|
+
// Remove value
|
|
39
|
+
setToken(undefined);
|
|
40
|
+
|
|
41
|
+
// Functional update
|
|
42
|
+
setToken((prev) => prev + "-refreshed");
|
|
34
43
|
```
|
|
35
44
|
|
|
36
45
|
| Return value | Type | Description |
|
|
37
46
|
|--------------|------|-------------|
|
|
38
|
-
| `[0]` | `Accessor<T>` | Value getter |
|
|
39
|
-
| `[1]` | `
|
|
47
|
+
| `[0]` | `Accessor<T \| undefined>` | Value getter |
|
|
48
|
+
| `[1]` | `StorageSetter<T>` | Value setter (accepts value, `undefined` to remove, or updater function) |
|
|
40
49
|
|
|
41
50
|
---
|
|
42
51
|
|
|
@@ -66,21 +75,24 @@ const [theme, setTheme, ready] = useSyncConfig("theme", "light");
|
|
|
66
75
|
Logging hook. If `LoggerProvider` is present, logs are sent to the adapter only. Otherwise, logs fall back to `consola`.
|
|
67
76
|
|
|
68
77
|
```tsx
|
|
69
|
-
import { useLogger } from "@simplysm/solid";
|
|
78
|
+
import { useLogger, type Logger } from "@simplysm/solid";
|
|
70
79
|
|
|
71
|
-
const logger = useLogger();
|
|
80
|
+
const logger: Logger = useLogger();
|
|
72
81
|
logger.log("user action", { userId: 123 });
|
|
73
82
|
logger.info("app started");
|
|
74
83
|
logger.error("something failed", errorObj);
|
|
75
84
|
logger.warn("deprecation notice");
|
|
76
85
|
```
|
|
77
86
|
|
|
87
|
+
**Logger interface:**
|
|
88
|
+
|
|
78
89
|
| Method | Signature | Description |
|
|
79
90
|
|--------|-----------|-------------|
|
|
80
91
|
| `log` | `(...args: unknown[]) => void` | Log message (general) |
|
|
81
92
|
| `info` | `(...args: unknown[]) => void` | Log message (informational) |
|
|
82
93
|
| `warn` | `(...args: unknown[]) => void` | Log message (warning) |
|
|
83
94
|
| `error` | `(...args: unknown[]) => void` | Log message (error) |
|
|
95
|
+
| `configure` | `(fn: (origin: LogAdapter) => LogAdapter) => void` | Set or wrap the log adapter (inside `LoggerProvider` only) |
|
|
84
96
|
|
|
85
97
|
**Configuring a custom adapter (decorator pattern):**
|
|
86
98
|
|
|
@@ -222,13 +234,44 @@ const { mounted, animating, unmount } = createMountTransition(() => open());
|
|
|
222
234
|
|
|
223
235
|
## createIMEHandler
|
|
224
236
|
|
|
225
|
-
Hook that delays `
|
|
237
|
+
Hook that delays `setValue` calls during IME (Korean, CJK, etc.) composition to prevent interrupted input. Use inside contenteditable or custom input components.
|
|
238
|
+
|
|
239
|
+
```tsx
|
|
240
|
+
import { createIMEHandler } from "@simplysm/solid";
|
|
241
|
+
|
|
242
|
+
const {
|
|
243
|
+
composingValue,
|
|
244
|
+
handleCompositionStart,
|
|
245
|
+
handleInput,
|
|
246
|
+
handleCompositionEnd,
|
|
247
|
+
flushComposition,
|
|
248
|
+
} = createIMEHandler((value) => {
|
|
249
|
+
// called only when composition is complete
|
|
250
|
+
setMyValue(value);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// Wire up to DOM element events:
|
|
254
|
+
<div
|
|
255
|
+
contentEditable
|
|
256
|
+
onCompositionStart={handleCompositionStart}
|
|
257
|
+
onCompositionEnd={(e) => handleCompositionEnd(e.currentTarget.textContent ?? "")}
|
|
258
|
+
onInput={(e) => handleInput(e.currentTarget.textContent ?? "", e.isComposing)}
|
|
259
|
+
/>
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
| Return value | Type | Description |
|
|
263
|
+
|--------------|------|-------------|
|
|
264
|
+
| `composingValue` | `Accessor<string \| null>` | Current composing value (for display); `null` when not composing |
|
|
265
|
+
| `handleCompositionStart` | `() => void` | Call on `compositionstart` event |
|
|
266
|
+
| `handleInput` | `(value: string, isComposing: boolean) => void` | Call on `input` event |
|
|
267
|
+
| `handleCompositionEnd` | `(value: string) => void` | Call on `compositionend` event |
|
|
268
|
+
| `flushComposition` | `() => void` | Immediately commit any pending composition |
|
|
226
269
|
|
|
227
270
|
---
|
|
228
271
|
|
|
229
272
|
## useRouterLink
|
|
230
273
|
|
|
231
|
-
`@solidjs/router`-based navigation hook. Automatically handles Ctrl/Alt + click (new tab), Shift + click (new window).
|
|
274
|
+
`@solidjs/router`-based navigation hook. Automatically handles Ctrl/Alt + click (new tab), Shift + click (new window), and regular click (SPA routing).
|
|
232
275
|
|
|
233
276
|
```tsx
|
|
234
277
|
import { useRouterLink } from "@simplysm/solid";
|
|
@@ -239,112 +282,146 @@ const navigate = useRouterLink();
|
|
|
239
282
|
Dashboard
|
|
240
283
|
</List.Item>
|
|
241
284
|
|
|
242
|
-
// Pass state
|
|
285
|
+
// Pass state (not visible in URL)
|
|
243
286
|
<List.Item onClick={navigate({ href: "/users/123", state: { from: "list" } })}>
|
|
244
287
|
User
|
|
245
288
|
</List.Item>
|
|
289
|
+
|
|
290
|
+
// Custom new window size on Shift+click
|
|
291
|
+
<List.Item onClick={navigate({ href: "/reports/pdf", window: { width: 1200, height: 900 } })}>
|
|
292
|
+
Report
|
|
293
|
+
</List.Item>
|
|
246
294
|
```
|
|
247
295
|
|
|
296
|
+
**Options:**
|
|
297
|
+
|
|
298
|
+
| Option | Type | Default | Description |
|
|
299
|
+
|--------|------|---------|-------------|
|
|
300
|
+
| `href` | `string` | (required) | Navigation path (fully-formed URL, e.g., `"/home/dashboard?tab=1"`) |
|
|
301
|
+
| `state` | `Record<string, unknown>` | - | State to pass to the route (not exposed in URL) |
|
|
302
|
+
| `window.width` | `number` | `800` | New window width (Shift+click) |
|
|
303
|
+
| `window.height` | `number` | `800` | New window height (Shift+click) |
|
|
304
|
+
|
|
248
305
|
---
|
|
249
306
|
|
|
250
307
|
## createAppStructure
|
|
251
308
|
|
|
252
|
-
Utility for declaratively defining app structure (routing, menus, permissions). Takes a
|
|
309
|
+
Utility for declaratively defining app structure (routing, menus, permissions). Takes a factory function and returns `{ AppStructureProvider, useAppStructure }` for Context-based access with full `InferPerms` type preservation.
|
|
253
310
|
|
|
254
311
|
```tsx
|
|
255
|
-
|
|
312
|
+
// appStructure.ts
|
|
313
|
+
import { createAppStructure } from "@simplysm/solid";
|
|
256
314
|
import { IconHome, IconUsers } from "@tabler/icons-solidjs";
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
315
|
+
import { useAuth } from "./AuthProvider";
|
|
316
|
+
|
|
317
|
+
export const { AppStructureProvider, useAppStructure } = createAppStructure(() => {
|
|
318
|
+
const auth = useAuth();
|
|
319
|
+
return {
|
|
320
|
+
items: [
|
|
321
|
+
{
|
|
322
|
+
code: "home",
|
|
323
|
+
title: "Home",
|
|
324
|
+
icon: IconHome,
|
|
325
|
+
component: HomePage,
|
|
326
|
+
perms: ["use"],
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
code: "admin",
|
|
330
|
+
title: "Admin",
|
|
331
|
+
icon: IconUsers,
|
|
332
|
+
children: [
|
|
333
|
+
{ code: "users", title: "User Management", component: UsersPage, perms: ["use", "edit"] },
|
|
334
|
+
{ code: "roles", title: "Role Management", component: RolesPage, perms: ["use"], isNotMenu: true },
|
|
335
|
+
],
|
|
336
|
+
},
|
|
273
337
|
],
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const structure = createAppStructure({
|
|
278
|
-
items,
|
|
279
|
-
usableModules: () => activeModules(), // optional: filter by active modules
|
|
280
|
-
permRecord: () => userPermissions(), // optional: Record<string, boolean> permission state
|
|
338
|
+
usableModules: () => activeModules(), // optional: filter by active modules
|
|
339
|
+
permRecord: () => auth.authInfo()?.permissions, // optional: Record<string, boolean>
|
|
340
|
+
};
|
|
281
341
|
});
|
|
282
|
-
|
|
283
|
-
// structure.allRoutes -- AppRoute[] - all routes with permCode + module info (static)
|
|
284
|
-
// structure.usableMenus() -- Accessor<AppMenu[]> - filtered menu array for Sidebar.Menu
|
|
285
|
-
// structure.usableFlatMenus() -- Accessor<AppFlatMenu[]> - flat filtered menu list
|
|
286
|
-
// structure.usablePerms() -- Accessor<AppPerm[]> - filtered permission tree
|
|
287
|
-
// structure.allFlatPerms -- AppFlatPerm[] - all flat perm entries (static)
|
|
288
|
-
// structure.checkRouteAccess(r) -- boolean - check if route is accessible
|
|
289
342
|
```
|
|
290
343
|
|
|
291
|
-
|
|
344
|
+
The factory function (`getOpts`) is called inside `AppStructureProvider` at render time, so context hooks like `useAuth()` can be used. The `const TItems` generic preserves item literal types for full `InferPerms` type inference.
|
|
345
|
+
|
|
346
|
+
**Provider setup:**
|
|
292
347
|
|
|
293
348
|
```tsx
|
|
294
|
-
|
|
295
|
-
import {
|
|
296
|
-
import { appStructure } from "./appStructure";
|
|
349
|
+
// main.tsx or App.tsx
|
|
350
|
+
import { AppStructureProvider } from "./appStructure";
|
|
297
351
|
|
|
298
352
|
render(
|
|
299
353
|
() => (
|
|
300
|
-
<
|
|
301
|
-
<
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
<Route
|
|
306
|
-
path={r.path}
|
|
307
|
-
component={() => {
|
|
308
|
-
if (!appStructure.checkRouteAccess(r)) {
|
|
309
|
-
return <Navigate href="/login" />;
|
|
310
|
-
}
|
|
311
|
-
return <r.component />;
|
|
312
|
-
}}
|
|
313
|
-
/>
|
|
314
|
-
))}
|
|
315
|
-
<Route path="/*" component={NotFoundPage} />
|
|
316
|
-
</Route>
|
|
317
|
-
<Route path="/" component={() => <Navigate href="/home" />} />
|
|
318
|
-
</Route>
|
|
319
|
-
</HashRouter>
|
|
354
|
+
<AppStructureProvider>
|
|
355
|
+
<HashRouter>
|
|
356
|
+
{/* ... */}
|
|
357
|
+
</HashRouter>
|
|
358
|
+
</AppStructureProvider>
|
|
320
359
|
),
|
|
321
360
|
document.getElementById("root")!,
|
|
322
361
|
);
|
|
323
362
|
```
|
|
324
363
|
|
|
325
|
-
|
|
364
|
+
**Using the hook in components:**
|
|
365
|
+
|
|
366
|
+
```tsx
|
|
367
|
+
import { useAppStructure } from "./appStructure";
|
|
368
|
+
|
|
369
|
+
function Home(props: RouteSectionProps) {
|
|
370
|
+
const appStructure = useAppStructure();
|
|
371
|
+
|
|
372
|
+
// appStructure.usableRoutes() -- Accessor<AppRoute[]>
|
|
373
|
+
// appStructure.usableMenus() -- Accessor<AppMenu[]>
|
|
374
|
+
// appStructure.usableFlatMenus() -- Accessor<AppFlatMenu[]>
|
|
375
|
+
// appStructure.usablePerms() -- Accessor<AppPerm[]>
|
|
376
|
+
// appStructure.allFlatPerms -- AppFlatPerm[]
|
|
377
|
+
// appStructure.getTitleChainByHref(href) -- string[]
|
|
378
|
+
// appStructure.perms -- typed permission accessor
|
|
379
|
+
|
|
380
|
+
return <Sidebar.Menu menus={appStructure.usableMenus()} />;
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
**Routing integration with `@solidjs/router`:**
|
|
385
|
+
|
|
386
|
+
```tsx
|
|
387
|
+
import { useAppStructure } from "./appStructure";
|
|
388
|
+
|
|
389
|
+
function HomeRoutes() {
|
|
390
|
+
const appStructure = useAppStructure();
|
|
391
|
+
return (
|
|
392
|
+
<For each={appStructure.usableRoutes()}>
|
|
393
|
+
{(r) => <Route path={r.path} component={r.component} />}
|
|
394
|
+
</For>
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// In your router setup (inside AppStructureProvider):
|
|
399
|
+
<Route path="/home" component={Home}>
|
|
400
|
+
<Route path="/" component={() => <Navigate href="/home/main" />} />
|
|
401
|
+
<HomeRoutes />
|
|
402
|
+
<Route path="/*" component={NotFoundPage} />
|
|
403
|
+
</Route>
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
`usableRoutes()` is a reactive accessor returning routes filtered by `usableModules` and `permRecord`. Items with `perms: ["use"]` are excluded from routes when the user lacks the `use` permission.
|
|
326
407
|
|
|
327
408
|
**AppStructure return type:**
|
|
328
409
|
|
|
329
410
|
```typescript
|
|
330
411
|
interface AppStructure<TModule> {
|
|
331
412
|
items: AppStructureItem<TModule>[];
|
|
332
|
-
|
|
413
|
+
usableRoutes: Accessor<AppRoute[]>; // reactive, filtered by modules + permRecord
|
|
333
414
|
usableMenus: Accessor<AppMenu[]>; // reactive, filtered by modules + permRecord
|
|
334
415
|
usableFlatMenus: Accessor<AppFlatMenu[]>; // reactive, flat version of usableMenus
|
|
335
416
|
usablePerms: Accessor<AppPerm<TModule>[]>; // reactive, filtered permission tree
|
|
336
|
-
allFlatPerms: AppFlatPerm<TModule>[];
|
|
337
|
-
checkRouteAccess(route: AppRoute<TModule>): boolean; // reactive access check
|
|
417
|
+
allFlatPerms: AppFlatPerm<TModule>[]; // static, all perm entries (not reactive)
|
|
338
418
|
getTitleChainByHref(href: string): string[];
|
|
339
419
|
perms: InferPerms<TItems>; // typed permission accessor (getter-based reactive booleans)
|
|
340
420
|
}
|
|
341
421
|
|
|
342
|
-
interface AppRoute
|
|
422
|
+
interface AppRoute {
|
|
343
423
|
path: string;
|
|
344
424
|
component: Component;
|
|
345
|
-
permCode?: string;
|
|
346
|
-
modulesChain: TModule[][];
|
|
347
|
-
requiredModulesChain: TModule[][];
|
|
348
425
|
}
|
|
349
426
|
|
|
350
427
|
interface AppMenu {
|
|
@@ -418,18 +495,18 @@ type AppStructureItem<TModule> = AppStructureGroupItem<TModule> | AppStructureLe
|
|
|
418
495
|
Retrieves the breadcrumb title chain for a given href path. Works on raw items (including `isNotMenu` items).
|
|
419
496
|
|
|
420
497
|
```tsx
|
|
421
|
-
import {
|
|
422
|
-
|
|
423
|
-
const appStructure = createAppStructure({ items });
|
|
498
|
+
import { useLocation } from "@solidjs/router";
|
|
499
|
+
import { useAppStructure } from "./appStructure";
|
|
424
500
|
|
|
425
|
-
|
|
426
|
-
const
|
|
501
|
+
function Breadcrumb() {
|
|
502
|
+
const appStructure = useAppStructure();
|
|
503
|
+
const location = useLocation();
|
|
427
504
|
|
|
428
|
-
//
|
|
429
|
-
|
|
505
|
+
// Returns ["Sales", "Invoice"] for /home/sales/invoice
|
|
506
|
+
const breadcrumb = () => appStructure.getTitleChainByHref(location.pathname);
|
|
430
507
|
|
|
431
|
-
|
|
432
|
-
|
|
508
|
+
return <span>{breadcrumb().join(" > ")}</span>;
|
|
509
|
+
}
|
|
433
510
|
```
|
|
434
511
|
|
|
435
512
|
#### perms
|
|
@@ -439,7 +516,10 @@ Typed permission accessor providing dot-notation access with TypeScript autocomp
|
|
|
439
516
|
**Important:** For type inference to work, pass items inline to `createAppStructure`. Declaring items as a separate variable with explicit `AppStructureItem[]` type annotation widens literal types, losing autocompletion.
|
|
440
517
|
|
|
441
518
|
```typescript
|
|
442
|
-
|
|
519
|
+
// appStructure.ts
|
|
520
|
+
import { createAppStructure } from "@simplysm/solid";
|
|
521
|
+
|
|
522
|
+
export const { AppStructureProvider, useAppStructure } = createAppStructure(() => ({
|
|
443
523
|
items: [
|
|
444
524
|
{
|
|
445
525
|
code: "home",
|
|
@@ -457,7 +537,10 @@ const appStructure = createAppStructure({
|
|
|
457
537
|
},
|
|
458
538
|
],
|
|
459
539
|
permRecord: () => userPermissions(),
|
|
460
|
-
});
|
|
540
|
+
}));
|
|
541
|
+
|
|
542
|
+
// In a component (inside AppStructureProvider):
|
|
543
|
+
const appStructure = useAppStructure();
|
|
461
544
|
|
|
462
545
|
// Typed access with autocompletion:
|
|
463
546
|
appStructure.perms.home.user.use // boolean (reactive)
|
package/docs/styling.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simplysm/solid",
|
|
3
|
-
"version": "13.0.
|
|
3
|
+
"version": "13.0.60",
|
|
4
4
|
"description": "심플리즘 패키지 - SolidJS 라이브러리",
|
|
5
5
|
"author": "김석래",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"solid-tiptap": "^0.8.0",
|
|
51
51
|
"tailwind-merge": "^3.5.0",
|
|
52
52
|
"tailwindcss": "^3.4.19",
|
|
53
|
-
"@simplysm/core-browser": "13.0.
|
|
54
|
-
"@simplysm/core-common": "13.0.
|
|
53
|
+
"@simplysm/core-browser": "13.0.60",
|
|
54
|
+
"@simplysm/core-common": "13.0.60"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@solidjs/testing-library": "^0.8.10"
|
|
@@ -168,8 +168,8 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
168
168
|
setLastFilter(() => objClone(filter));
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
function handleRefresh() {
|
|
172
|
-
|
|
171
|
+
async function handleRefresh() {
|
|
172
|
+
await doRefresh();
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
// -- Inline Edit --
|
|
@@ -309,14 +309,14 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
309
309
|
}
|
|
310
310
|
|
|
311
311
|
// -- Keyboard Shortcuts --
|
|
312
|
-
createEventListener(document, "keydown", (e: KeyboardEvent) => {
|
|
312
|
+
createEventListener(document, "keydown", async (e: KeyboardEvent) => {
|
|
313
313
|
if (e.ctrlKey && e.key === "s" && !isSelectMode()) {
|
|
314
314
|
e.preventDefault();
|
|
315
315
|
formRef?.requestSubmit();
|
|
316
316
|
}
|
|
317
317
|
if (e.ctrlKey && e.altKey && e.key === "l") {
|
|
318
318
|
e.preventDefault();
|
|
319
|
-
|
|
319
|
+
await doRefresh();
|
|
320
320
|
}
|
|
321
321
|
});
|
|
322
322
|
|
|
@@ -356,8 +356,7 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
356
356
|
},
|
|
357
357
|
save: handleSave,
|
|
358
358
|
refresh: async () => {
|
|
359
|
-
|
|
360
|
-
await Promise.resolve();
|
|
359
|
+
await doRefresh();
|
|
361
360
|
},
|
|
362
361
|
addItem: handleAddRow,
|
|
363
362
|
setPage,
|
|
@@ -501,9 +500,7 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
501
500
|
onSortsChange={setSorts}
|
|
502
501
|
selectMode={
|
|
503
502
|
isSelectMode()
|
|
504
|
-
? local.selectMode
|
|
505
|
-
? "multiple"
|
|
506
|
-
: "single"
|
|
503
|
+
? local.selectMode
|
|
507
504
|
: local.modalEdit?.deleteItems != null
|
|
508
505
|
? "multiple"
|
|
509
506
|
: undefined
|
|
@@ -605,10 +602,10 @@ const CrudSheetBase = <TItem, TFilter extends Record<string, any>>(
|
|
|
605
602
|
<div class="flex-1" />
|
|
606
603
|
<Show when={selectedItems().length > 0}>
|
|
607
604
|
<Button size="sm" theme="danger" onClick={handleSelectCancel}>
|
|
608
|
-
{local.selectMode === "
|
|
605
|
+
{local.selectMode === "multiple" ? "모두" : "선택"} 해제
|
|
609
606
|
</Button>
|
|
610
607
|
</Show>
|
|
611
|
-
<Show when={local.selectMode === "
|
|
608
|
+
<Show when={local.selectMode === "multiple"}>
|
|
612
609
|
<Button size="sm" theme="primary" onClick={handleSelectConfirm}>
|
|
613
610
|
확인({selectedItems().length})
|
|
614
611
|
</Button>
|
|
@@ -85,7 +85,7 @@ interface CrudSheetBaseProps<TItem, TFilter extends Record<string, any>> {
|
|
|
85
85
|
items?: TItem[];
|
|
86
86
|
onItemsChange?: (items: TItem[]) => void;
|
|
87
87
|
excel?: ExcelConfig<TItem>;
|
|
88
|
-
selectMode?: "single" | "
|
|
88
|
+
selectMode?: "single" | "multiple";
|
|
89
89
|
onSelect?: (result: SelectResult<TItem>) => void;
|
|
90
90
|
hideAutoTools?: boolean;
|
|
91
91
|
class?: string;
|
|
@@ -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);
|
|
@@ -16,6 +16,7 @@ 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";
|
|
@@ -25,8 +26,6 @@ import { DialogDefaultsContext } from "./DialogContext";
|
|
|
25
26
|
import { bringToFront, registerDialog, unregisterDialog } from "./dialogZIndex";
|
|
26
27
|
import { Button } from "../form-control/Button";
|
|
27
28
|
|
|
28
|
-
type SlotAccessor = (() => JSX.Element) | undefined;
|
|
29
|
-
|
|
30
29
|
interface DialogSlotsContextValue {
|
|
31
30
|
setHeader: (content: SlotAccessor) => void;
|
|
32
31
|
setAction: (content: SlotAccessor) => void;
|
|
@@ -189,10 +188,8 @@ export const Dialog: DialogComponent = (props) => {
|
|
|
189
188
|
|
|
190
189
|
const headerId = "dialog-header-" + createUniqueId();
|
|
191
190
|
|
|
192
|
-
const [header,
|
|
193
|
-
const
|
|
194
|
-
const [action, _setAction] = createSignal<SlotAccessor>();
|
|
195
|
-
const setAction = (content: SlotAccessor) => _setAction(() => content);
|
|
191
|
+
const [header, setHeader] = createSlotSignal();
|
|
192
|
+
const [action, setAction] = createSlotSignal();
|
|
196
193
|
const hasHeader = () => header() !== undefined;
|
|
197
194
|
|
|
198
195
|
const [open, setOpen] = createControllableSignal({
|
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
splitProps,
|
|
11
11
|
} from "solid-js";
|
|
12
12
|
import { createResizeObserver } from "@solid-primitives/resize-observer";
|
|
13
|
+
import { createControllableSignal } from "../../hooks/createControllableSignal";
|
|
14
|
+
import { createSlotSignal, type SlotAccessor } from "../../hooks/createSlotSignal";
|
|
13
15
|
import { createMountTransition } from "../../hooks/createMountTransition";
|
|
14
16
|
import { Portal } from "solid-js/web";
|
|
15
17
|
import clsx from "clsx";
|
|
@@ -19,8 +21,6 @@ import { borderSubtle } from "../../styles/tokens.styles";
|
|
|
19
21
|
|
|
20
22
|
// --- DropdownContext (internal) ---
|
|
21
23
|
|
|
22
|
-
type SlotAccessor = (() => JSX.Element) | undefined;
|
|
23
|
-
|
|
24
24
|
interface DropdownContextValue {
|
|
25
25
|
toggle: () => void;
|
|
26
26
|
setTrigger: (content: SlotAccessor) => void;
|
|
@@ -154,22 +154,11 @@ export const Dropdown: DropdownComponent = ((props: DropdownProps) => {
|
|
|
154
154
|
"children",
|
|
155
155
|
]);
|
|
156
156
|
|
|
157
|
-
const [open,
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
createEffect(() => {
|
|
161
|
-
const propOpen = local.open;
|
|
162
|
-
if (propOpen !== undefined) {
|
|
163
|
-
setOpenInternal(propOpen);
|
|
164
|
-
}
|
|
157
|
+
const [open, setOpen] = createControllableSignal({
|
|
158
|
+
value: () => local.open ?? false,
|
|
159
|
+
onChange: () => local.onOpenChange,
|
|
165
160
|
});
|
|
166
161
|
|
|
167
|
-
// 콜백 포함 setter
|
|
168
|
-
const setOpen = (value: boolean) => {
|
|
169
|
-
setOpenInternal(value);
|
|
170
|
-
local.onOpenChange?.(value);
|
|
171
|
-
};
|
|
172
|
-
|
|
173
162
|
// toggle 함수 (disabled 체크 포함)
|
|
174
163
|
const toggle = () => {
|
|
175
164
|
if (local.disabled) return;
|
|
@@ -177,10 +166,8 @@ export const Dropdown: DropdownComponent = ((props: DropdownProps) => {
|
|
|
177
166
|
};
|
|
178
167
|
|
|
179
168
|
// 슬롯 등록 시그널
|
|
180
|
-
const [triggerSlot,
|
|
181
|
-
const
|
|
182
|
-
const [contentSlot, _setContentSlot] = createSignal<SlotAccessor>();
|
|
183
|
-
const setContent = (content: SlotAccessor) => _setContentSlot(() => content);
|
|
169
|
+
const [triggerSlot, setTrigger] = createSlotSignal();
|
|
170
|
+
const [contentSlot, setContent] = createSlotSignal();
|
|
184
171
|
|
|
185
172
|
// Trigger wrapper ref (위치 계산에 필요)
|
|
186
173
|
let triggerRef: HTMLDivElement | undefined;
|