@warkypublic/svelix 0.1.0

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.
Files changed (212) hide show
  1. package/LICENSE +73 -0
  2. package/README.md +3 -0
  3. package/dist/actions/index.d.ts +1 -0
  4. package/dist/actions/index.js +2 -0
  5. package/dist/components/BetterMenu/BetterMenu.stories.js +68 -0
  6. package/dist/components/BetterMenu/BetterMenu.svelte +38 -0
  7. package/dist/components/BetterMenu/BetterMenu.svelte.d.ts +14 -0
  8. package/dist/components/BetterMenu/BetterMenuAsyncButton.svelte +34 -0
  9. package/dist/components/BetterMenu/BetterMenuAsyncButton.svelte.d.ts +8 -0
  10. package/dist/components/BetterMenu/BetterMenuPreview.svelte +43 -0
  11. package/dist/components/BetterMenu/BetterMenuPreview.svelte.d.ts +7 -0
  12. package/dist/components/BetterMenu/MenuRenderer.svelte +75 -0
  13. package/dist/components/BetterMenu/MenuRenderer.svelte.d.ts +6 -0
  14. package/dist/components/BetterMenu/Plan.mdx +155 -0
  15. package/dist/components/BetterMenu/index.d.ts +4 -0
  16. package/dist/components/BetterMenu/index.js +4 -0
  17. package/dist/components/BetterMenu/store.d.ts +10 -0
  18. package/dist/components/BetterMenu/store.js +48 -0
  19. package/dist/components/BetterMenu/types.d.ts +24 -0
  20. package/dist/components/BetterMenu/types.js +1 -0
  21. package/dist/components/Boxer/Boxer.stories.d.ts +19 -0
  22. package/dist/components/Boxer/Boxer.stories.js +102 -0
  23. package/dist/components/Boxer/Boxer.svelte +411 -0
  24. package/dist/components/Boxer/Boxer.svelte.d.ts +11 -0
  25. package/dist/components/Boxer/BoxerTarget.svelte +88 -0
  26. package/dist/components/Boxer/BoxerTarget.svelte.d.ts +20 -0
  27. package/dist/components/Boxer/Plan.mdx +140 -0
  28. package/dist/components/Boxer/features.mdx +81 -0
  29. package/dist/components/Boxer/index.d.ts +4 -0
  30. package/dist/components/Boxer/index.js +4 -0
  31. package/dist/components/Boxer/store.d.ts +26 -0
  32. package/dist/components/Boxer/store.js +103 -0
  33. package/dist/components/Boxer/types.d.ts +46 -0
  34. package/dist/components/Boxer/types.js +1 -0
  35. package/dist/components/Button.stories.d.ts +11 -0
  36. package/dist/components/Button.stories.js +109 -0
  37. package/dist/components/Button.svelte +50 -0
  38. package/dist/components/Button.svelte.d.ts +12 -0
  39. package/dist/components/ButtonPreview.svelte +14 -0
  40. package/dist/components/ButtonPreview.svelte.d.ts +4 -0
  41. package/dist/components/ErrorBoundary/ErrorBoundary.stories.js +17 -0
  42. package/dist/components/ErrorBoundary/ErrorBoundary.svelte +127 -0
  43. package/dist/components/ErrorBoundary/ErrorBoundary.svelte.d.ts +13 -0
  44. package/dist/components/ErrorBoundary/ErrorBoundaryPreview.svelte +28 -0
  45. package/dist/components/ErrorBoundary/ErrorBoundaryPreview.svelte.d.ts +6 -0
  46. package/dist/components/ErrorBoundary/ErrorManager.d.ts +15 -0
  47. package/dist/components/ErrorBoundary/ErrorManager.js +158 -0
  48. package/dist/components/ErrorBoundary/Plan.mdx +182 -0
  49. package/dist/components/ErrorBoundary/index.d.ts +3 -0
  50. package/dist/components/ErrorBoundary/index.js +3 -0
  51. package/dist/components/ErrorBoundary/types.d.ts +43 -0
  52. package/dist/components/ErrorBoundary/types.js +1 -0
  53. package/dist/components/Former/Former.stories.js +228 -0
  54. package/dist/components/Former/Former.svelte +405 -0
  55. package/dist/components/Former/Former.svelte.d.ts +33 -0
  56. package/dist/components/Former/FormerButtonArea.svelte +93 -0
  57. package/dist/components/Former/FormerButtonArea.svelte.d.ts +15 -0
  58. package/dist/components/Former/FormerDrawer.svelte +115 -0
  59. package/dist/components/Former/FormerDrawer.svelte.d.ts +19 -0
  60. package/dist/components/Former/FormerDrawerPreview.svelte +226 -0
  61. package/dist/components/Former/FormerDrawerPreview.svelte.d.ts +7 -0
  62. package/dist/components/Former/FormerModal.svelte +108 -0
  63. package/dist/components/Former/FormerModal.svelte.d.ts +14 -0
  64. package/dist/components/Former/FormerModalPreview.svelte +226 -0
  65. package/dist/components/Former/FormerModalPreview.svelte.d.ts +7 -0
  66. package/dist/components/Former/FormerPreview.svelte +238 -0
  67. package/dist/components/Former/FormerPreview.svelte.d.ts +8 -0
  68. package/dist/components/Former/FormerResolveSpecAPI.d.ts +26 -0
  69. package/dist/components/Former/FormerResolveSpecAPI.js +44 -0
  70. package/dist/components/Former/FormerRestApiPreview.svelte +198 -0
  71. package/dist/components/Former/FormerRestApiPreview.svelte.d.ts +3 -0
  72. package/dist/components/Former/FormerRestHeadSpecAPI.d.ts +8 -0
  73. package/dist/components/Former/FormerRestHeadSpecAPI.js +38 -0
  74. package/dist/components/Former/Plan.mdx +115 -0
  75. package/dist/components/Former/formerState.svelte.d.ts +21 -0
  76. package/dist/components/Former/formerState.svelte.js +57 -0
  77. package/dist/components/Former/index.d.ts +8 -0
  78. package/dist/components/Former/index.js +8 -0
  79. package/dist/components/Former/types.d.ts +61 -0
  80. package/dist/components/Former/types.js +1 -0
  81. package/dist/components/FormerControllers/ButtonCtrl.stories.js +102 -0
  82. package/dist/components/FormerControllers/ButtonCtrl.svelte +65 -0
  83. package/dist/components/FormerControllers/ButtonCtrl.svelte.d.ts +14 -0
  84. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrl.stories.js +73 -0
  85. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrl.svelte +630 -0
  86. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrl.svelte.d.ts +54 -0
  87. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrl.utils.d.ts +40 -0
  88. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrl.utils.js +688 -0
  89. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrlCalendar.svelte +193 -0
  90. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrlCalendar.svelte.d.ts +13 -0
  91. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrlPickerPanel.svelte +119 -0
  92. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrlPickerPanel.svelte.d.ts +39 -0
  93. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrlTimeFields.svelte +343 -0
  94. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrlTimeFields.svelte.d.ts +27 -0
  95. package/dist/components/FormerControllers/DateTimeCtrl/index.d.ts +2 -0
  96. package/dist/components/FormerControllers/DateTimeCtrl/index.js +1 -0
  97. package/dist/components/FormerControllers/FormerControllers.stories.js +76 -0
  98. package/dist/components/FormerControllers/IconButtonCtrl.stories.js +77 -0
  99. package/dist/components/FormerControllers/IconButtonCtrl.svelte +64 -0
  100. package/dist/components/FormerControllers/IconButtonCtrl.svelte.d.ts +13 -0
  101. package/dist/components/FormerControllers/InlineWrapper.stories.js +133 -0
  102. package/dist/components/FormerControllers/InlineWrapper.svelte +85 -0
  103. package/dist/components/FormerControllers/InlineWrapper.svelte.d.ts +16 -0
  104. package/dist/components/FormerControllers/InlineWrapperPreview.svelte +41 -0
  105. package/dist/components/FormerControllers/InlineWrapperPreview.svelte.d.ts +13 -0
  106. package/dist/components/FormerControllers/NativeSelectCtrl.stories.js +79 -0
  107. package/dist/components/FormerControllers/NativeSelectCtrl.svelte +61 -0
  108. package/dist/components/FormerControllers/NativeSelectCtrl.svelte.d.ts +13 -0
  109. package/dist/components/FormerControllers/NumberInputCtrl.stories.js +77 -0
  110. package/dist/components/FormerControllers/NumberInputCtrl.svelte +61 -0
  111. package/dist/components/FormerControllers/NumberInputCtrl.svelte.d.ts +11 -0
  112. package/dist/components/FormerControllers/PasswordInputCtrl.stories.js +79 -0
  113. package/dist/components/FormerControllers/PasswordInputCtrl.svelte +57 -0
  114. package/dist/components/FormerControllers/PasswordInputCtrl.svelte.d.ts +8 -0
  115. package/dist/components/FormerControllers/Plan.mdx +129 -0
  116. package/dist/components/FormerControllers/SwitchCtrl.stories.js +73 -0
  117. package/dist/components/FormerControllers/SwitchCtrl.svelte +38 -0
  118. package/dist/components/FormerControllers/SwitchCtrl.svelte.d.ts +8 -0
  119. package/dist/components/FormerControllers/TextAreaCtrl.stories.js +71 -0
  120. package/dist/components/FormerControllers/TextAreaCtrl.svelte +47 -0
  121. package/dist/components/FormerControllers/TextAreaCtrl.svelte.d.ts +9 -0
  122. package/dist/components/FormerControllers/TextInputCtrl.svelte +47 -0
  123. package/dist/components/FormerControllers/TextInputCtrl.svelte.d.ts +9 -0
  124. package/dist/components/FormerControllers/index.d.ts +12 -0
  125. package/dist/components/FormerControllers/index.js +11 -0
  126. package/dist/components/FormerControllers/types.d.ts +10 -0
  127. package/dist/components/FormerControllers/types.js +1 -0
  128. package/dist/components/GlobalStateStore/GlobalStateStore.d.ts +19 -0
  129. package/dist/components/GlobalStateStore/GlobalStateStore.js +349 -0
  130. package/dist/components/GlobalStateStore/GlobalStateStore.types.d.ts +127 -0
  131. package/dist/components/GlobalStateStore/GlobalStateStore.types.js +2 -0
  132. package/dist/components/GlobalStateStore/GlobalStateStore.utils.d.ts +4 -0
  133. package/dist/components/GlobalStateStore/GlobalStateStore.utils.js +92 -0
  134. package/dist/components/GlobalStateStore/GlobalStateStoreContext.d.ts +10 -0
  135. package/dist/components/GlobalStateStore/GlobalStateStoreContext.js +10 -0
  136. package/dist/components/GlobalStateStore/GlobalStateStoreProvider.svelte +113 -0
  137. package/dist/components/GlobalStateStore/GlobalStateStoreProvider.svelte.d.ts +16 -0
  138. package/dist/components/GlobalStateStore/index.d.ts +5 -0
  139. package/dist/components/GlobalStateStore/index.js +3 -0
  140. package/dist/components/Gridler/CellEditor.svelte +126 -0
  141. package/dist/components/Gridler/CellEditor.svelte.d.ts +15 -0
  142. package/dist/components/Gridler/Gridler.stories.d.ts +56 -0
  143. package/dist/components/Gridler/Gridler.stories.js +262 -0
  144. package/dist/components/Gridler/Gridler.svelte +778 -0
  145. package/dist/components/Gridler/Gridler.svelte.d.ts +11 -0
  146. package/dist/components/Gridler/GridlerHeader.svelte +179 -0
  147. package/dist/components/Gridler/GridlerHeader.svelte.d.ts +13 -0
  148. package/dist/components/Gridler/Plan.mdx +692 -0
  149. package/dist/components/Gridler/index.d.ts +6 -0
  150. package/dist/components/Gridler/index.js +6 -0
  151. package/dist/components/Gridler/types.d.ts +84 -0
  152. package/dist/components/Gridler/types.js +16 -0
  153. package/dist/components/Gridler/utils/rendering.d.ts +16 -0
  154. package/dist/components/Gridler/utils/rendering.js +202 -0
  155. package/dist/components/Gridler/utils/scrolling.d.ts +12 -0
  156. package/dist/components/Gridler/utils/scrolling.js +97 -0
  157. package/dist/components/Portal/Portal.mdx +125 -0
  158. package/dist/components/Portal/Portal.svelte +47 -0
  159. package/dist/components/Portal/Portal.svelte.d.ts +18 -0
  160. package/dist/components/Screenshot/Screenshot.stories.d.ts +16 -0
  161. package/dist/components/Screenshot/Screenshot.stories.js +15 -0
  162. package/dist/components/Screenshot/Screenshot.svelte +54 -0
  163. package/dist/components/Screenshot/Screenshot.svelte.d.ts +3 -0
  164. package/dist/components/Screenshot/Screenshot.util.d.ts +1 -0
  165. package/dist/components/Screenshot/Screenshot.util.js +49 -0
  166. package/dist/components/Screenshot/index.d.ts +2 -0
  167. package/dist/components/Screenshot/index.js +2 -0
  168. package/dist/components/Svark/Svark.stories.js +659 -0
  169. package/dist/components/Svark/Svark.svelte +691 -0
  170. package/dist/components/Svark/Svark.svelte.d.ts +26 -0
  171. package/dist/components/Svark/SvarkResolveSpecAdapter.d.ts +16 -0
  172. package/dist/components/Svark/SvarkResolveSpecAdapter.js +68 -0
  173. package/dist/components/Svark/SvarkSelectionDemo.svelte +59 -0
  174. package/dist/components/Svark/SvarkSelectionDemo.svelte.d.ts +4 -0
  175. package/dist/components/Svark/index.d.ts +3 -0
  176. package/dist/components/Svark/index.js +3 -0
  177. package/dist/components/Svark/types.d.ts +63 -0
  178. package/dist/components/Svark/types.js +1 -0
  179. package/dist/components/VTree/VTree.models.d.ts +12 -0
  180. package/dist/components/VTree/VTree.models.js +1 -0
  181. package/dist/components/VTree/VTree.stories.d.ts +40 -0
  182. package/dist/components/VTree/VTree.stories.js +112 -0
  183. package/dist/components/VTree/VTree.svelte +471 -0
  184. package/dist/components/VTree/VTree.svelte.d.ts +5 -0
  185. package/dist/components/VTree/VTreeContextMenu.svelte +40 -0
  186. package/dist/components/VTree/VTreeContextMenu.svelte.d.ts +11 -0
  187. package/dist/components/VTree/VTreeEventsDemo.svelte +88 -0
  188. package/dist/components/VTree/VTreeEventsDemo.svelte.d.ts +3 -0
  189. package/dist/components/VTree/VTreeResolveSpecAdapter.d.ts +14 -0
  190. package/dist/components/VTree/VTreeResolveSpecAdapter.js +103 -0
  191. package/dist/components/VTree/VTreeRow.svelte +136 -0
  192. package/dist/components/VTree/VTreeRow.svelte.d.ts +37 -0
  193. package/dist/components/VTree/VTreeSearch.svelte +25 -0
  194. package/dist/components/VTree/VTreeSearch.svelte.d.ts +8 -0
  195. package/dist/components/VTree/VTreeVirtualViewport.svelte +154 -0
  196. package/dist/components/VTree/VTreeVirtualViewport.svelte.d.ts +45 -0
  197. package/dist/components/VTree/index.d.ts +3 -0
  198. package/dist/components/VTree/index.js +3 -0
  199. package/dist/components/VTree/types.d.ts +83 -0
  200. package/dist/components/VTree/types.js +1 -0
  201. package/dist/components/index.d.ts +11 -0
  202. package/dist/components/index.js +11 -0
  203. package/dist/index.d.ts +16 -0
  204. package/dist/index.js +20 -0
  205. package/dist/stores/index.d.ts +1 -0
  206. package/dist/stores/index.js +1 -0
  207. package/dist/themes/svelix_orange.css +205 -0
  208. package/dist/utils/PropsWithChildren.d.ts +5 -0
  209. package/dist/utils/PropsWithChildren.js +1 -0
  210. package/dist/utils/index.d.ts +1 -0
  211. package/dist/utils/index.js +2 -0
  212. package/package.json +85 -0
@@ -0,0 +1,659 @@
1
+ import { expect, within, userEvent, fn } from '@storybook/test';
2
+ import Svark from './Svark.svelte';
3
+ import SvarkSelectionDemo from './SvarkSelectionDemo.svelte';
4
+ const meta = {
5
+ title: 'Components/Svark',
6
+ component: Svark,
7
+ tags: ['autodocs'],
8
+ argTypes: {
9
+ baseUrl: {
10
+ control: { type: 'text' },
11
+ description: 'ResolveSpec API base URL'
12
+ },
13
+ token: {
14
+ control: { type: 'text' },
15
+ description: 'Bearer auth token'
16
+ },
17
+ schema: {
18
+ control: { type: 'text' },
19
+ description: 'Database schema name'
20
+ },
21
+ entity: {
22
+ control: { type: 'text' },
23
+ description: 'Entity / table name'
24
+ },
25
+ clientType: {
26
+ control: { type: 'select' },
27
+ options: ['body', 'header'],
28
+ description: 'REST client protocol — body JSON or HTTP headers'
29
+ },
30
+ pageSize: {
31
+ control: { type: 'number' },
32
+ description: 'Number of rows to fetch per page'
33
+ },
34
+ serverSide: {
35
+ control: { type: 'boolean' },
36
+ description: 'Enable server-side sort and filter'
37
+ },
38
+ height: {
39
+ control: { type: 'text' },
40
+ description: 'Grid container height (CSS string or px number)'
41
+ },
42
+ select: {
43
+ control: { type: 'boolean' },
44
+ description: 'Enable row selection'
45
+ },
46
+ multiselect: {
47
+ control: { type: 'boolean' },
48
+ description: 'Allow multi-row selection'
49
+ },
50
+ virtualScroll: {
51
+ control: { type: 'boolean' },
52
+ description: 'Enable virtual (windowed) scrolling'
53
+ },
54
+ infiniteScroll: {
55
+ control: { type: 'boolean' },
56
+ description: 'Enable infinite scroll (append pages on scroll)'
57
+ }
58
+ },
59
+ args: {
60
+ onchange: fn(),
61
+ onerror: fn(),
62
+ onselect: fn()
63
+ }
64
+ };
65
+ export default meta;
66
+ // ── Shared mock data ──────────────────────────────────────────────────────────
67
+ const mockUsers = Array.from({ length: 50 }, (_, i) => ({
68
+ id: i + 1,
69
+ name: ['Alice Smith', 'Bob Jones', 'Carol White', 'Dave Brown', 'Eve Wilson'][i % 5],
70
+ email: `user${i + 1}@example.com`,
71
+ role: ['Admin', 'Editor', 'Viewer'][i % 3],
72
+ status: ['Active', 'Inactive', 'Pending'][i % 3],
73
+ department: ['Engineering', 'Marketing', 'Sales', 'HR', 'Finance'][i % 5]
74
+ }));
75
+ // Large dataset for virtual / infinite scroll demos (5 000 rows)
76
+ const mockLargeUsers = Array.from({ length: 5000 }, (_, i) => ({
77
+ id: i + 1,
78
+ name: ['Alice Smith', 'Bob Jones', 'Carol White', 'Dave Brown', 'Eve Wilson'][i % 5],
79
+ email: `user${i + 1}@example.com`,
80
+ role: ['Admin', 'Editor', 'Viewer'][i % 3],
81
+ status: ['Active', 'Inactive', 'Pending'][i % 3],
82
+ department: ['Engineering', 'Marketing', 'Sales', 'HR', 'Finance'][i % 5]
83
+ }));
84
+ // Paginated mock: slices mockLargeUsers by limit/offset from the request body
85
+ function mockFetchPaginated(dataset) {
86
+ return async (_url, init) => {
87
+ const body = init?.body ? JSON.parse(init.body) : {};
88
+ const offset = body.options?.offset ?? 0;
89
+ const limit = body.options?.limit ?? 50;
90
+ const page = dataset.slice(offset, offset + limit);
91
+ return new Response(JSON.stringify({
92
+ success: true,
93
+ data: page,
94
+ metadata: {
95
+ total: dataset.length,
96
+ count: page.length,
97
+ filtered: dataset.length,
98
+ limit,
99
+ offset
100
+ }
101
+ }), { status: 200, headers: { 'Content-Type': 'application/json' } });
102
+ };
103
+ }
104
+ function mockApiResponse(data, total) {
105
+ return new Response(JSON.stringify({
106
+ success: true,
107
+ data,
108
+ metadata: { total: total ?? data.length, count: data.length, filtered: data.length, limit: 100, offset: 0 }
109
+ }), { status: 200, headers: { 'Content-Type': 'application/json' } });
110
+ }
111
+ function mockFetch(data, delayMs = 0) {
112
+ return async () => {
113
+ if (delayMs > 0)
114
+ await new Promise((r) => setTimeout(r, delayMs));
115
+ return mockApiResponse(data);
116
+ };
117
+ }
118
+ function mockFetchError(message = 'Internal Server Error', status = 500) {
119
+ return async () => new Response(JSON.stringify({ success: false, error: { code: 'ERR', message } }), {
120
+ status,
121
+ headers: { 'Content-Type': 'application/json' }
122
+ });
123
+ }
124
+ // ── Shared columns ────────────────────────────────────────────────────────────
125
+ const userColumns = [
126
+ { id: 'id', header: 'ID', width: 60 },
127
+ { id: 'name', header: 'Name', width: 180, sort: true },
128
+ { id: 'email', header: 'Email', width: 220 },
129
+ { id: 'role', header: 'Role', width: 100 },
130
+ { id: 'status', header: 'Status', width: 100 },
131
+ { id: 'department', header: 'Department', width: 140 }
132
+ ];
133
+ // ── Stories ───────────────────────────────────────────────────────────────────
134
+ export const Basic = {
135
+ decorators: [
136
+ (Story) => {
137
+ globalThis.fetch = mockFetch(mockUsers);
138
+ return Story();
139
+ }
140
+ ],
141
+ args: {
142
+ baseUrl: 'http://localhost:3000',
143
+ schema: 'public',
144
+ entity: 'users',
145
+ columns: userColumns,
146
+ clientType: 'body',
147
+ pageSize: 50
148
+ }
149
+ };
150
+ export const HeaderClient = {
151
+ decorators: [
152
+ (Story) => {
153
+ globalThis.fetch = mockFetch(mockUsers);
154
+ return Story();
155
+ }
156
+ ],
157
+ args: {
158
+ baseUrl: 'http://localhost:3000',
159
+ schema: 'public',
160
+ entity: 'users',
161
+ columns: userColumns,
162
+ clientType: 'header',
163
+ pageSize: 50
164
+ }
165
+ };
166
+ export const WithToken = {
167
+ decorators: [
168
+ (Story) => {
169
+ globalThis.fetch = async (url, init) => {
170
+ const hasAuth = init?.headers?.['Authorization'];
171
+ return mockApiResponse(hasAuth ? mockUsers : []);
172
+ };
173
+ return Story();
174
+ }
175
+ ],
176
+ args: {
177
+ baseUrl: 'http://localhost:3000',
178
+ schema: 'public',
179
+ entity: 'users',
180
+ token: 'demo-bearer-token',
181
+ columns: userColumns,
182
+ clientType: 'body'
183
+ }
184
+ };
185
+ export const ServerSideSort = {
186
+ decorators: [
187
+ (Story) => {
188
+ globalThis.fetch = async (url, init) => {
189
+ const body = init?.body ? JSON.parse(init.body) : {};
190
+ const sort = body.options?.sort ?? [];
191
+ const limit = body.options?.limit ?? 50;
192
+ const offset = body.options?.offset ?? 0;
193
+ const data = [...mockUsers];
194
+ if (sort.length > 0) {
195
+ const { column, direction } = sort[0];
196
+ data.sort((a, b) => {
197
+ const av = String(a[column] ?? '');
198
+ const bv = String(b[column] ?? '');
199
+ return direction === 'desc' ? bv.localeCompare(av) : av.localeCompare(bv);
200
+ });
201
+ }
202
+ return mockApiResponse(data.slice(offset, offset + limit), data.length);
203
+ };
204
+ return Story();
205
+ }
206
+ ],
207
+ args: {
208
+ baseUrl: 'http://localhost:3000',
209
+ schema: 'public',
210
+ entity: 'users',
211
+ columns: userColumns,
212
+ clientType: 'body',
213
+ serverSide: true,
214
+ pageSize: 10
215
+ }
216
+ };
217
+ export const ServerSideFilter = {
218
+ decorators: [
219
+ (Story) => {
220
+ globalThis.fetch = async (url, init) => {
221
+ const body = init?.body ? JSON.parse(init.body) : {};
222
+ const filters = body.options?.filters ?? [];
223
+ const limit = body.options?.limit ?? 50;
224
+ const offset = body.options?.offset ?? 0;
225
+ let data = [...mockUsers];
226
+ for (const f of filters) {
227
+ const val = String(f.value ?? '').toLowerCase();
228
+ data = data.filter((row) => String(row[f.column] ?? '').toLowerCase().includes(val));
229
+ }
230
+ return mockApiResponse(data.slice(offset, offset + limit), data.length);
231
+ };
232
+ return Story();
233
+ }
234
+ ],
235
+ args: {
236
+ baseUrl: 'http://localhost:3000',
237
+ schema: 'public',
238
+ entity: 'users',
239
+ columns: userColumns.map((c) => c.id === 'name' ? { ...c, header: { text: 'Name', filter: 'text' } } : c),
240
+ clientType: 'body',
241
+ serverSide: true,
242
+ pageSize: 10
243
+ }
244
+ };
245
+ export const InitialQuery = {
246
+ decorators: [
247
+ (Story) => {
248
+ globalThis.fetch = mockFetch(mockUsers.filter((u) => u.role === 'Admin'));
249
+ return Story();
250
+ }
251
+ ],
252
+ args: {
253
+ baseUrl: 'http://localhost:3000',
254
+ schema: 'public',
255
+ entity: 'users',
256
+ columns: userColumns,
257
+ clientType: 'body',
258
+ query: {
259
+ filters: [{ column: 'role', operator: 'eq', value: 'Admin' }],
260
+ sort: [{ column: 'name', direction: 'asc' }],
261
+ limit: 20
262
+ }
263
+ }
264
+ };
265
+ export const LoadingState = {
266
+ decorators: [
267
+ (Story) => {
268
+ globalThis.fetch = mockFetch(mockUsers, 2000);
269
+ return Story();
270
+ }
271
+ ],
272
+ args: {
273
+ baseUrl: 'http://localhost:3000',
274
+ schema: 'public',
275
+ entity: 'users',
276
+ columns: userColumns,
277
+ clientType: 'body'
278
+ }
279
+ };
280
+ export const ErrorState = {
281
+ decorators: [
282
+ (Story) => {
283
+ globalThis.fetch = mockFetchError('Connection refused');
284
+ return Story();
285
+ }
286
+ ],
287
+ args: {
288
+ baseUrl: 'http://localhost:3000',
289
+ schema: 'public',
290
+ entity: 'users',
291
+ columns: userColumns,
292
+ clientType: 'body',
293
+ onerror: (msg) => console.error('Svark error:', msg)
294
+ },
295
+ play: async ({ canvasElement }) => {
296
+ const canvas = within(canvasElement);
297
+ const alert = await canvas.findByRole('alert', {}, { timeout: 3000 });
298
+ await expect(alert).toBeInTheDocument();
299
+ }
300
+ };
301
+ export const EmptyData = {
302
+ decorators: [
303
+ (Story) => {
304
+ globalThis.fetch = mockFetch([]);
305
+ return Story();
306
+ }
307
+ ],
308
+ args: {
309
+ baseUrl: 'http://localhost:3000',
310
+ schema: 'public',
311
+ entity: 'users',
312
+ columns: userColumns,
313
+ clientType: 'body',
314
+ config: { autoConfig: true }
315
+ }
316
+ };
317
+ export const WithGridConfig = {
318
+ decorators: [
319
+ (Story) => {
320
+ globalThis.fetch = mockFetch(mockUsers);
321
+ return Story();
322
+ }
323
+ ],
324
+ args: {
325
+ baseUrl: 'http://localhost:3000',
326
+ schema: 'public',
327
+ entity: 'users',
328
+ columns: userColumns,
329
+ clientType: 'body',
330
+ config: {
331
+ select: true,
332
+ reorder: true,
333
+ sizes: { rowHeight: 40, headerHeight: 42 }
334
+ }
335
+ }
336
+ };
337
+ // ── Row selection stories ──────────────────────────────────────────────────────
338
+ export const SingleRowSelection = {
339
+ decorators: [
340
+ (Story) => {
341
+ globalThis.fetch = mockFetch(mockUsers);
342
+ return Story();
343
+ }
344
+ ],
345
+ render: (args) => ({ Component: SvarkSelectionDemo, props: args }),
346
+ args: {
347
+ baseUrl: 'http://localhost:3000',
348
+ schema: 'public',
349
+ entity: 'users',
350
+ columns: userColumns,
351
+ clientType: 'body',
352
+ select: true,
353
+ multiselect: false
354
+ },
355
+ play: async ({ canvasElement }) => {
356
+ // Wait for SVAR to render rows after fetch
357
+ await new Promise((r) => setTimeout(r, 600));
358
+ const rows = canvasElement.querySelectorAll('[role="row"][data-id]');
359
+ if (rows.length > 0) {
360
+ await userEvent.click(rows[0]);
361
+ }
362
+ }
363
+ };
364
+ export const MultiRowSelection = {
365
+ decorators: [
366
+ (Story) => {
367
+ globalThis.fetch = mockFetch(mockUsers);
368
+ return Story();
369
+ }
370
+ ],
371
+ render: (args) => ({ Component: SvarkSelectionDemo, props: args }),
372
+ args: {
373
+ baseUrl: 'http://localhost:3000',
374
+ schema: 'public',
375
+ entity: 'users',
376
+ columns: userColumns,
377
+ clientType: 'body',
378
+ select: true,
379
+ multiselect: true
380
+ },
381
+ play: async ({ canvasElement }) => {
382
+ await new Promise((r) => setTimeout(r, 600));
383
+ const rows = canvasElement.querySelectorAll('[role="row"][data-id]');
384
+ if (rows.length >= 3) {
385
+ await userEvent.click(rows[0]);
386
+ await userEvent.click(rows[1], { ctrlKey: true });
387
+ await userEvent.click(rows[2], { ctrlKey: true });
388
+ }
389
+ }
390
+ };
391
+ export const PreSelectedRows = {
392
+ decorators: [
393
+ (Story) => {
394
+ globalThis.fetch = mockFetch(mockUsers);
395
+ return Story();
396
+ }
397
+ ],
398
+ render: (args) => ({ Component: SvarkSelectionDemo, props: args }),
399
+ args: {
400
+ baseUrl: 'http://localhost:3000',
401
+ schema: 'public',
402
+ entity: 'users',
403
+ columns: userColumns,
404
+ clientType: 'body',
405
+ select: true,
406
+ multiselect: true,
407
+ // Pass initial selection via Grid's selectedRows prop through config spread
408
+ config: { selectedRows: [1, 3, 5] }
409
+ },
410
+ play: async ({ canvasElement }) => {
411
+ await new Promise((r) => setTimeout(r, 600));
412
+ // Verify the pre-selected rows have .wx-selected class
413
+ const selected = canvasElement.querySelectorAll('[role="row"].wx-selected');
414
+ await expect(selected.length).toBeGreaterThan(0);
415
+ }
416
+ };
417
+ // ── Virtual & Infinite scroll stories ────────────────────────────────────────
418
+ export const VirtualScrolling = {
419
+ decorators: [
420
+ (Story) => {
421
+ // Paginated fetch: Svark sends limit=1 first (metadata), then
422
+ // request-data drives subsequent window fetches.
423
+ globalThis.fetch = mockFetchPaginated(mockLargeUsers);
424
+ return Story();
425
+ }
426
+ ],
427
+ args: {
428
+ baseUrl: 'http://localhost:3000',
429
+ schema: 'public',
430
+ entity: 'users',
431
+ columns: userColumns,
432
+ clientType: 'body',
433
+ pageSize: 50,
434
+ virtualScroll: true,
435
+ height: '500px'
436
+ }
437
+ };
438
+ export const InfiniteScrolling = {
439
+ decorators: [
440
+ (Story) => {
441
+ // Paginated fetch: Svark appends pages as the user scrolls.
442
+ globalThis.fetch = mockFetchPaginated(mockLargeUsers);
443
+ return Story();
444
+ }
445
+ ],
446
+ args: {
447
+ baseUrl: 'http://localhost:3000',
448
+ schema: 'public',
449
+ entity: 'users',
450
+ columns: userColumns,
451
+ clientType: 'body',
452
+ pageSize: 50,
453
+ infiniteScroll: true,
454
+ height: '500px'
455
+ }
456
+ };
457
+ // ── HeaderSpec (header client) stories ───────────────────────────────────────
458
+ //
459
+ // HeaderSpecClient sends GET requests with options encoded in HTTP headers:
460
+ // X-Limit / X-Offset — pagination
461
+ // X-Sort — sort: "+col" asc, "-col" desc
462
+ // X-SearchOp-ilike-col — case-insensitive filter value for a column
463
+ //
464
+ // The server must return the raw data array as JSON body and signal the total
465
+ // via the "content-range: {offset}-{end}/{total}" response header.
466
+ function mockFetchHeader(dataset, delayMs = 0) {
467
+ return async (_url, init) => {
468
+ if (delayMs > 0)
469
+ await new Promise((r) => setTimeout(r, delayMs));
470
+ const hdrs = (init?.headers ?? {});
471
+ const limit = Number(hdrs['X-Limit'] ?? 50);
472
+ const offset = Number(hdrs['X-Offset'] ?? 0);
473
+ let data = [...dataset];
474
+ // Apply ilike filters: X-SearchOp-ilike-{column}: value
475
+ for (const [key, val] of Object.entries(hdrs)) {
476
+ const m = key.match(/^X-SearchOp-ilike-(.+)$/i);
477
+ if (m) {
478
+ const col = m[1];
479
+ const search = String(val).toLowerCase();
480
+ data = data.filter((row) => String(row[col] ?? '').toLowerCase().includes(search));
481
+ }
482
+ }
483
+ // Apply sort: X-Sort: +col,-col2
484
+ const sortHeader = hdrs['X-Sort'];
485
+ if (sortHeader) {
486
+ for (const part of sortHeader.split(',').reverse()) {
487
+ const desc = part.startsWith('-');
488
+ const col = part.slice(1);
489
+ data.sort((a, b) => {
490
+ const av = String(a[col] ?? '');
491
+ const bv = String(b[col] ?? '');
492
+ return desc ? bv.localeCompare(av) : av.localeCompare(bv);
493
+ });
494
+ }
495
+ }
496
+ const total = data.length;
497
+ const page = data.slice(offset, offset + limit);
498
+ const end = offset + page.length - 1;
499
+ return new Response(JSON.stringify(page), {
500
+ status: 200,
501
+ headers: {
502
+ 'Content-Type': 'application/json',
503
+ 'content-range': `${offset}-${Math.max(end, 0)}/${total}`,
504
+ 'x-limit': String(limit)
505
+ }
506
+ });
507
+ };
508
+ }
509
+ const userColumnsWithSort = [
510
+ { id: 'id', header: 'ID', width: 60 },
511
+ { id: 'name', header: 'Name', width: 180, sort: true },
512
+ { id: 'email', header: 'Email', width: 220, sort: true },
513
+ { id: 'role', header: 'Role', width: 100, sort: true },
514
+ { id: 'status', header: 'Status', width: 100, sort: true },
515
+ { id: 'department', header: 'Department', width: 140, sort: true }
516
+ ];
517
+ const userColumnsWithFilter = [
518
+ { id: 'id', header: 'ID', width: 60 },
519
+ { id: 'name', header: { text: 'Name', filter: 'text' }, width: 180, sort: true },
520
+ { id: 'email', header: { text: 'Email', filter: 'text' }, width: 220 },
521
+ { id: 'role', header: { text: 'Role', filter: 'text' }, width: 100 },
522
+ { id: 'status', header: { text: 'Status', filter: 'text' }, width: 100 },
523
+ { id: 'department', header: { text: 'Department', filter: 'text' }, width: 140 }
524
+ ];
525
+ export const HeaderInfiniteScroll = {
526
+ decorators: [
527
+ (Story) => {
528
+ // 300ms delay so the loading indicator is visible on each page append
529
+ globalThis.fetch = mockFetchHeader(mockLargeUsers, 300);
530
+ return Story();
531
+ }
532
+ ],
533
+ args: {
534
+ baseUrl: 'http://localhost:3000',
535
+ schema: 'public',
536
+ entity: 'users',
537
+ columns: userColumns,
538
+ clientType: 'header',
539
+ pageSize: 50,
540
+ infiniteScroll: true,
541
+ height: '500px'
542
+ }
543
+ };
544
+ export const HeaderSorting = {
545
+ decorators: [
546
+ (Story) => {
547
+ globalThis.fetch = mockFetchHeader(mockLargeUsers);
548
+ return Story();
549
+ }
550
+ ],
551
+ args: {
552
+ baseUrl: 'http://localhost:3000',
553
+ schema: 'public',
554
+ entity: 'users',
555
+ columns: userColumnsWithSort,
556
+ clientType: 'header',
557
+ pageSize: 100,
558
+ serverSide: true,
559
+ height: '500px'
560
+ }
561
+ };
562
+ export const HeaderFiltering = {
563
+ decorators: [
564
+ (Story) => {
565
+ globalThis.fetch = mockFetchHeader(mockLargeUsers);
566
+ return Story();
567
+ }
568
+ ],
569
+ args: {
570
+ baseUrl: 'http://localhost:3000',
571
+ schema: 'public',
572
+ entity: 'users',
573
+ columns: userColumnsWithFilter,
574
+ clientType: 'header',
575
+ pageSize: 100,
576
+ serverSide: true,
577
+ height: '500px'
578
+ }
579
+ };
580
+ // ── Live HeaderSpec stories (real backend, no mock) ───────────────────────────
581
+ // Set baseUrl, schema, entity and optionally token in the Controls panel.
582
+ export const LiveHeaderInfiniteScroll = {
583
+ args: {
584
+ baseUrl: "https://utils.btsys.tech/api",
585
+ token: "773EB99C-F625-4E99-9DB9-CDDA7CA17639",
586
+ schema: 'public',
587
+ entity: "synclog",
588
+ columns: [{
589
+ "id": "id",
590
+ "header": "ID",
591
+ "width": 120
592
+ }, {
593
+ "id": "logmessage",
594
+ "header": {
595
+ "text": "Name",
596
+ "filter": "text"
597
+ },
598
+ "width": 280,
599
+ "sort": true
600
+ }, {
601
+ "id": "logtype",
602
+ "header": {
603
+ "text": "Test",
604
+ "filter": "text"
605
+ },
606
+ "width": 140
607
+ }],
608
+ clientType: 'header',
609
+ serverSide: true,
610
+ pageSize: 50,
611
+ infiniteScroll: true,
612
+ height: "400px"
613
+ }
614
+ };
615
+ export const LiveHeaderSorting = {
616
+ args: {
617
+ baseUrl: "https://utils.btsys.tech/api/",
618
+ token: "773EB99C-F625-4E99-9DB9-CDDA7CA17639",
619
+ schema: 'public',
620
+ entity: 'users',
621
+ columns: userColumnsWithSort,
622
+ clientType: 'header',
623
+ pageSize: 10,
624
+ serverSide: true,
625
+ height: '500px'
626
+ }
627
+ };
628
+ export const LiveHeaderFiltering = {
629
+ args: {
630
+ baseUrl: "https://utils.btsys.tech/api",
631
+ token: "773EB99C-F625-4E99-9DB9-CDDA7CA17639",
632
+ schema: 'public',
633
+ entity: "synclog",
634
+ columns: [{
635
+ "id": "id",
636
+ "header": "ID",
637
+ "width": 120
638
+ }, {
639
+ "id": "logmessage",
640
+ "header": {
641
+ "text": "Name",
642
+ "filter": "text"
643
+ },
644
+ "width": 280,
645
+ "sort": true
646
+ }, {
647
+ "id": "logtype",
648
+ "header": {
649
+ "text": "Test",
650
+ "filter": "text"
651
+ },
652
+ "width": 140
653
+ }],
654
+ clientType: 'header',
655
+ pageSize: 100,
656
+ serverSide: true,
657
+ height: '500px'
658
+ }
659
+ };