@simplysm/solid 13.0.97 → 13.0.99

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.
@@ -0,0 +1,206 @@
1
+ # Helpers
2
+
3
+ Source: `src/helpers/**`
4
+
5
+ ## `mergeStyles`
6
+
7
+ Utility function that merges CSS styles. Supports string, object, and mixed formats. Converts kebab-case CSS strings to camelCase object properties. Later values take precedence.
8
+
9
+ ```typescript
10
+ export function mergeStyles(
11
+ ...styles: (JSX.CSSProperties | string | undefined)[]
12
+ ): JSX.CSSProperties;
13
+ ```
14
+
15
+ ---
16
+
17
+ ## `createAppStructure`
18
+
19
+ Factory function for defining app navigation structure with type-safe permission inference. Generates routes, menus, and permission trees from a declarative item definition.
20
+
21
+ ```typescript
22
+ export function createAppStructure<TModule, const TItems extends AppStructureItem<TModule>[]>(
23
+ getOpts: () => {
24
+ items: TItems;
25
+ usableModules?: Accessor<TModule[] | undefined>;
26
+ permRecord?: Accessor<Record<string, boolean> | undefined>;
27
+ },
28
+ ): {
29
+ AppStructureProvider: ParentComponent;
30
+ useAppStructure: () => AppStructure<TModule> & { perms: InferPerms<TItems> };
31
+ };
32
+ ```
33
+
34
+ ### `AppStructure`
35
+
36
+ ```typescript
37
+ export interface AppStructure<TModule> {
38
+ items: AppStructureItem<TModule>[];
39
+ usableRoutes: Accessor<AppRoute[]>;
40
+ usableMenus: Accessor<AppMenu[]>;
41
+ usableFlatMenus: Accessor<AppFlatMenu[]>;
42
+ usablePerms: Accessor<AppPerm<TModule>[]>;
43
+ allFlatPerms: AppFlatPerm<TModule>[];
44
+ getTitleChainByHref(href: string): string[];
45
+ }
46
+ ```
47
+
48
+ | Field | Type | Description |
49
+ |-------|------|-------------|
50
+ | `usableRoutes` | `Accessor<AppRoute[]>` | Routes filtered by module and permission |
51
+ | `usableMenus` | `Accessor<AppMenu[]>` | Menu tree filtered by module and permission |
52
+ | `usableFlatMenus` | `Accessor<AppFlatMenu[]>` | Flat menu list with title chains |
53
+ | `usablePerms` | `Accessor<AppPerm[]>` | Permission tree filtered by module |
54
+ | `allFlatPerms` | `AppFlatPerm[]` | All permissions flattened (static) |
55
+ | `getTitleChainByHref` | `(href) => string[]` | Get breadcrumb title chain for URL |
56
+
57
+ ### Input Types
58
+
59
+ ```typescript
60
+ export type AppStructureItem<TModule> =
61
+ | AppStructureGroupItem<TModule>
62
+ | AppStructureLeafItem<TModule>;
63
+
64
+ export interface AppStructureGroupItem<TModule> {
65
+ code: string;
66
+ title: string;
67
+ icon?: Component<IconProps>;
68
+ modules?: TModule[];
69
+ requiredModules?: TModule[];
70
+ children: AppStructureItem<TModule>[];
71
+ }
72
+
73
+ export interface AppStructureLeafItem<TModule> {
74
+ code: string;
75
+ title: string;
76
+ icon?: Component<IconProps>;
77
+ modules?: TModule[];
78
+ requiredModules?: TModule[];
79
+ component?: Component;
80
+ perms?: ("use" | "edit")[];
81
+ subPerms?: AppStructureSubPerm<TModule>[];
82
+ isNotMenu?: boolean;
83
+ }
84
+
85
+ export interface AppStructureSubPerm<TModule> {
86
+ code: string;
87
+ title: string;
88
+ modules?: TModule[];
89
+ requiredModules?: TModule[];
90
+ perms: ("use" | "edit")[];
91
+ }
92
+ ```
93
+
94
+ | Field | Type | Description |
95
+ |-------|------|-------------|
96
+ | `code` | `string` | Unique item identifier |
97
+ | `title` | `string` | Display title |
98
+ | `icon` | `Component<IconProps>` | Navigation icon |
99
+ | `modules` | `TModule[]` | Required modules (any match) |
100
+ | `requiredModules` | `TModule[]` | Strictly required modules (all match) |
101
+ | `children` | `AppStructureItem[]` | Child items (group only) |
102
+ | `component` | `Component` | Page component (leaf only) |
103
+ | `perms` | `("use" \| "edit")[]` | Permission types (leaf only) |
104
+ | `subPerms` | `AppStructureSubPerm[]` | Sub-permission definitions (leaf only) |
105
+ | `isNotMenu` | `boolean` | Exclude from menu (leaf only) |
106
+
107
+ Discriminated union: items with `children` are groups; items without are leaves.
108
+
109
+ ### Output Types
110
+
111
+ ```typescript
112
+ export interface AppMenu {
113
+ title: string;
114
+ href?: string;
115
+ icon?: Component<IconProps>;
116
+ children?: AppMenu[];
117
+ }
118
+
119
+ export interface AppRoute {
120
+ path: string;
121
+ component: Component;
122
+ }
123
+
124
+ export interface AppPerm<TModule = string> {
125
+ title: string;
126
+ href?: string;
127
+ modules?: TModule[];
128
+ perms?: string[];
129
+ children?: AppPerm<TModule>[];
130
+ }
131
+
132
+ export interface AppFlatPerm<TModule = string> {
133
+ titleChain: string[];
134
+ code: string;
135
+ modulesChain: TModule[][];
136
+ requiredModulesChain: TModule[][];
137
+ }
138
+
139
+ export interface AppFlatMenu {
140
+ titleChain: string[];
141
+ href: string;
142
+ }
143
+ ```
144
+
145
+ ---
146
+
147
+ ## `createSlot`
148
+
149
+ Creates a single-occupancy slot pattern for compound components.
150
+
151
+ ```typescript
152
+ export function createSlot<TItem>(): [
153
+ SlotComponent: (props: TItem) => null,
154
+ createSlotAccessor: () => [Accessor<TItem | undefined>, ParentComponent],
155
+ ];
156
+ ```
157
+
158
+ Returns a tuple of `[SlotComponent, createSlotAccessor]`:
159
+ - **`SlotComponent`** -- Rendered inside provider to register slot content (throws if slot already occupied)
160
+ - **`createSlotAccessor`** -- Returns `[accessor, Provider]` for reading and providing the slot
161
+
162
+ ---
163
+
164
+ ## `createSlots`
165
+
166
+ Creates a multi-occupancy slot pattern for compound components (multiple items).
167
+
168
+ ```typescript
169
+ export function createSlots<TItem>(): [
170
+ SlotComponent: (props: TItem) => null,
171
+ createSlotsAccessor: () => [Accessor<TItem[]>, ParentComponent],
172
+ ];
173
+ ```
174
+
175
+ ### `SlotRegistrar`
176
+
177
+ ```typescript
178
+ export interface SlotRegistrar<TItem> {
179
+ add: (item: TItem) => void;
180
+ remove: (item: TItem) => void;
181
+ }
182
+ ```
183
+
184
+ ---
185
+
186
+ ## `startPointerDrag`
187
+
188
+ Sets up pointer capture and manages pointermove/pointerup lifecycle for drag operations.
189
+
190
+ ```typescript
191
+ export function startPointerDrag(
192
+ target: HTMLElement,
193
+ pointerId: number,
194
+ options: {
195
+ onMove: (e: PointerEvent) => void;
196
+ onEnd: (e: PointerEvent) => void;
197
+ },
198
+ ): void;
199
+ ```
200
+
201
+ | Parameter | Type | Description |
202
+ |-----------|------|-------------|
203
+ | `target` | `HTMLElement` | Element to capture pointer on |
204
+ | `pointerId` | `number` | Pointer ID from the initiating PointerEvent |
205
+ | `options.onMove` | `(e: PointerEvent) => void` | Called on each pointermove |
206
+ | `options.onEnd` | `(e: PointerEvent) => void` | Called on pointerup or pointercancel |
package/docs/hooks.md ADDED
@@ -0,0 +1,142 @@
1
+ # Hooks
2
+
3
+ Source: `src/hooks/**`
4
+
5
+ ## `useLocalStorage`
6
+
7
+ LocalStorage-based storage hook. Always uses localStorage regardless of SyncStorage settings. Keys are prefixed with `clientName`.
8
+
9
+ ```typescript
10
+ export function useLocalStorage<TValue>(
11
+ key: string,
12
+ initialValue?: TValue,
13
+ ): [Accessor<TValue | undefined>, StorageSetter<TValue>];
14
+ ```
15
+
16
+ | Parameter | Type | Description |
17
+ |-----------|------|-------------|
18
+ | `key` | `string` | Storage key (auto-prefixed with `clientName`) |
19
+ | `initialValue` | `TValue` | Default value if nothing stored |
20
+
21
+ ---
22
+
23
+ ## `useSyncConfig`
24
+
25
+ Reactive signal that syncs to storage via SyncStorageProvider (falls back to localStorage). Designed for data that should persist and sync across devices.
26
+
27
+ ```typescript
28
+ export function useSyncConfig<TValue>(
29
+ key: string,
30
+ defaultValue: TValue,
31
+ ): [Accessor<TValue>, Setter<TValue>, Accessor<boolean>];
32
+ ```
33
+
34
+ | Parameter | Type | Description |
35
+ |-----------|------|-------------|
36
+ | `key` | `string` | Storage key (auto-prefixed with `clientName`) |
37
+ | `defaultValue` | `TValue` | Default value |
38
+
39
+ Returns `[value, setValue, ready]` where `ready()` is `true` after initial storage read completes.
40
+
41
+ ---
42
+
43
+ ## `useLogger`
44
+
45
+ Logging hook that delegates to LoggerProvider adapter (defaults to consola).
46
+
47
+ ```typescript
48
+ export function useLogger(): Logger;
49
+
50
+ export interface Logger {
51
+ log: (...args: unknown[]) => void;
52
+ info: (...args: unknown[]) => void;
53
+ warn: (...args: unknown[]) => void;
54
+ error: (...args: unknown[]) => void;
55
+ configure: (fn: (origin: LogAdapter) => LogAdapter) => void;
56
+ }
57
+ ```
58
+
59
+ ---
60
+
61
+ ## `createControllableSignal`
62
+
63
+ Signal hook supporting the controlled/uncontrolled pattern.
64
+
65
+ ```typescript
66
+ export function createControllableSignal<TValue>(options: {
67
+ value: Accessor<TValue>;
68
+ onChange: Accessor<((value: TValue) => void) | undefined>;
69
+ }): [Accessor<TValue>, (newValue: TValue | ((prev: TValue) => TValue)) => TValue];
70
+ ```
71
+
72
+ - When `onChange` is provided: controlled mode (value managed externally)
73
+ - When `onChange` is absent: uncontrolled mode (uses internal state)
74
+ - Supports functional setter: `setValue(prev => !prev)`
75
+
76
+ ---
77
+
78
+ ## `createControllableStore`
79
+
80
+ Store hook supporting the controlled/uncontrolled pattern. Supports all `SetStoreFunction` overloads (path-based, produce, reconcile).
81
+
82
+ ```typescript
83
+ export function createControllableStore<TValue extends object>(options: {
84
+ value: () => TValue;
85
+ onChange: () => ((value: TValue) => void) | undefined;
86
+ }): [TValue, SetStoreFunction<TValue>];
87
+ ```
88
+
89
+ ---
90
+
91
+ ## `createIMEHandler`
92
+
93
+ IME composition handling hook. Delays value changes during IME composition (e.g., Korean) to prevent DOM recreation.
94
+
95
+ ```typescript
96
+ export function createIMEHandler(setValue: (value: string) => void): {
97
+ composingValue: Accessor<string | null>;
98
+ handleCompositionStart: () => void;
99
+ handleInput: (value: string, isComposing: boolean) => void;
100
+ handleCompositionEnd: (value: string) => void;
101
+ flushComposition: () => void;
102
+ };
103
+ ```
104
+
105
+ ---
106
+
107
+ ## `createMountTransition`
108
+
109
+ Mount transition hook for open/close CSS animations. Returns `mounted` for DOM rendering and `animating` for CSS class toggling.
110
+
111
+ ```typescript
112
+ export function createMountTransition(open: () => boolean): {
113
+ mounted: () => boolean;
114
+ animating: () => boolean;
115
+ unmount: () => void;
116
+ };
117
+ ```
118
+
119
+ - `open=true`: `mounted=true` immediately, `animating=true` after double rAF
120
+ - `open=false`: `animating=false` immediately, `mounted=false` after transitionend or 200ms fallback
121
+
122
+ ---
123
+
124
+ ## `useRouterLink`
125
+
126
+ Router navigation hook with modifier key support.
127
+
128
+ ```typescript
129
+ export function useRouterLink(): (
130
+ options: RouterLinkOptions,
131
+ ) => (e: MouseEvent | KeyboardEvent) => void;
132
+
133
+ export interface RouterLinkOptions {
134
+ href: string;
135
+ state?: Record<string, unknown>;
136
+ window?: { width?: number; height?: number };
137
+ }
138
+ ```
139
+
140
+ - Normal click: SPA routing via `useNavigate`
141
+ - Ctrl/Alt + click: new tab
142
+ - Shift + click: new window with configurable size
package/docs/layout.md ADDED
@@ -0,0 +1,212 @@
1
+ # Layout Components
2
+
3
+ Source: `src/components/layout/**`
4
+
5
+ ## `FormGroup`
6
+
7
+ Vertical or inline form field grouping component.
8
+
9
+ ```typescript
10
+ export interface FormGroupProps extends JSX.HTMLAttributes<HTMLDivElement> {
11
+ inline?: boolean;
12
+ }
13
+ ```
14
+
15
+ | Prop | Type | Description |
16
+ |------|------|-------------|
17
+ | `inline` | `boolean` | Display items horizontally in a row |
18
+
19
+ ### Sub-components
20
+
21
+ - **`FormGroup.Item`** -- Individual form field with optional label
22
+
23
+ ```typescript
24
+ export interface FormGroupItemProps extends JSX.HTMLAttributes<HTMLDivElement> {
25
+ label?: JSX.Element;
26
+ }
27
+ ```
28
+
29
+ ---
30
+
31
+ ## `FormTable`
32
+
33
+ Table-based form layout with label-value pairs.
34
+
35
+ ```typescript
36
+ export interface FormTableProps extends JSX.HTMLAttributes<HTMLTableElement> {}
37
+ ```
38
+
39
+ ### Sub-components
40
+
41
+ - **`FormTable.Row`** -- Table row wrapper
42
+ - **`FormTable.Item`** -- Table cell with optional label (`th` + `td`)
43
+
44
+ ```typescript
45
+ export interface FormTableItemProps extends JSX.TdHTMLAttributes<HTMLTableCellElement> {
46
+ label?: JSX.Element;
47
+ }
48
+ ```
49
+
50
+ ---
51
+
52
+ ## `Sidebar`
53
+
54
+ Responsive sidebar navigation with user info and menu sections. Collapses on mobile with overlay backdrop.
55
+
56
+ ```typescript
57
+ export interface SidebarProps extends JSX.HTMLAttributes<HTMLElement> {
58
+ children: JSX.Element;
59
+ }
60
+ ```
61
+
62
+ ### Sub-components
63
+
64
+ - **`Sidebar.Container`** -- Root container providing sidebar context and responsive padding
65
+ - **`Sidebar.Menu`** -- Recursive menu list with pathname-based selection
66
+ - **`Sidebar.User`** -- User info section with optional dropdown menus
67
+
68
+ ### `Sidebar.Container`
69
+
70
+ ```typescript
71
+ export interface SidebarContainerProps extends JSX.HTMLAttributes<HTMLDivElement> {
72
+ children: JSX.Element;
73
+ }
74
+ ```
75
+
76
+ ### `Sidebar.Menu`
77
+
78
+ ```typescript
79
+ export interface SidebarMenuProps extends Omit<JSX.HTMLAttributes<HTMLDivElement>, "children"> {
80
+ menus: AppMenu[];
81
+ defaultOpen?: boolean;
82
+ }
83
+ ```
84
+
85
+ | Prop | Type | Description |
86
+ |------|------|-------------|
87
+ | `menus` | `AppMenu[]` | Menu items array |
88
+ | `defaultOpen` | `boolean` | Expand all nested menus initially. Default: `false` |
89
+
90
+ ### `Sidebar.User`
91
+
92
+ ```typescript
93
+ export interface SidebarUserProps extends Omit<JSX.HTMLAttributes<HTMLDivElement>, "onClick"> {
94
+ name: string;
95
+ icon?: Component<TablerIconProps>;
96
+ description?: string;
97
+ menus?: SidebarUserMenu[];
98
+ }
99
+ ```
100
+
101
+ | Prop | Type | Description |
102
+ |------|------|-------------|
103
+ | `name` | `string` | User name (required) |
104
+ | `icon` | `Component<TablerIconProps>` | Avatar icon. Default: user icon |
105
+ | `description` | `string` | Additional info (email, etc.) |
106
+ | `menus` | `SidebarUserMenu[]` | Dropdown menu items |
107
+
108
+ ### `SidebarUserMenu`
109
+
110
+ ```typescript
111
+ export interface SidebarUserMenu {
112
+ title: string;
113
+ onClick: () => void;
114
+ }
115
+ ```
116
+
117
+ ### `SidebarContextValue`
118
+
119
+ ```typescript
120
+ export interface SidebarContextValue {
121
+ toggle: Accessor<boolean>;
122
+ setToggle: Setter<boolean>;
123
+ }
124
+ ```
125
+
126
+ ### `useSidebar`
127
+
128
+ Hook to access sidebar toggle state. Also has `useSidebar.optional()` variant.
129
+
130
+ ### `SM_MEDIA_QUERY`
131
+
132
+ ```typescript
133
+ export const SM_MEDIA_QUERY = "(min-width: 640px)";
134
+ ```
135
+
136
+ ---
137
+
138
+ ## `Topbar`
139
+
140
+ Top navigation bar with sidebar toggle integration, dropdown menus, user section, and dynamic actions.
141
+
142
+ ```typescript
143
+ export interface TopbarProps extends JSX.HTMLAttributes<HTMLElement> {
144
+ children: JSX.Element;
145
+ }
146
+ ```
147
+
148
+ ### Sub-components
149
+
150
+ - **`Topbar.Container`** -- Layout container wrapping Topbar and content, provides actions context
151
+ - **`Topbar.Menu`** -- Dropdown navigation menu (responsive: desktop buttons / mobile hamburger)
152
+ - **`Topbar.User`** -- User information section with optional dropdown
153
+ - **`Topbar.Actions`** -- Renders actions set via `useTopbarActions()`
154
+
155
+ ### `Topbar.Container`
156
+
157
+ ```typescript
158
+ export interface TopbarContainerProps extends JSX.HTMLAttributes<HTMLDivElement> {
159
+ children: JSX.Element;
160
+ }
161
+ ```
162
+
163
+ ### `Topbar.Menu`
164
+
165
+ ```typescript
166
+ export interface TopbarMenuProps extends Omit<JSX.HTMLAttributes<HTMLElement>, "children"> {
167
+ menus: TopbarMenuItem[];
168
+ }
169
+ ```
170
+
171
+ ### `TopbarMenuItem`
172
+
173
+ ```typescript
174
+ export interface TopbarMenuItem {
175
+ title: string;
176
+ href?: string;
177
+ icon?: Component<IconProps>;
178
+ children?: TopbarMenuItem[];
179
+ }
180
+ ```
181
+
182
+ ### `Topbar.User`
183
+
184
+ ```typescript
185
+ export interface TopbarUserProps extends Omit<JSX.HTMLAttributes<HTMLDivElement>, "onClick"> {
186
+ menus?: TopbarUserMenu[];
187
+ children: JSX.Element;
188
+ }
189
+ ```
190
+
191
+ ### `TopbarUserMenu`
192
+
193
+ ```typescript
194
+ export interface TopbarUserMenu {
195
+ title: string;
196
+ onClick: () => void;
197
+ }
198
+ ```
199
+
200
+ ### `TopbarContextValue`
201
+
202
+ ```typescript
203
+ export interface TopbarContextValue {
204
+ actions: Accessor<JSX.Element | undefined>;
205
+ setActions: Setter<JSX.Element | undefined>;
206
+ }
207
+ ```
208
+
209
+ ### Hooks
210
+
211
+ - **`useTopbarActions(accessor: () => JSX.Element)`** -- Register dynamic actions for display in Topbar
212
+ - **`useTopbarActionsAccessor()`** -- Read registered actions