flet-box 0.0.0-stage → 1.0.2

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 (178) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +190 -2
  3. package/bin/cli.js +394 -0
  4. package/bin/commands/buildAndroid.js +200 -0
  5. package/bin/commands/buildIOS.js +237 -0
  6. package/bin/commands/create.js +406 -0
  7. package/bin/commands/createBundle.js +375 -0
  8. package/bin/commands/createComponent.js +115 -0
  9. package/bin/commands/createScreen.js +161 -0
  10. package/bin/commands/killServer.js +107 -0
  11. package/bin/commands/package.js +131 -0
  12. package/bin/commands/release.js +189 -0
  13. package/bin/commands/runBundle.js +62 -0
  14. package/bin/commands/runCloudflare.js +89 -0
  15. package/bin/commands/runNative.js +109 -0
  16. package/bin/commands/runServer.js +390 -0
  17. package/bin/ota/loader.js +361 -0
  18. package/bin/utils/bundleConfig.js +178 -0
  19. package/bin/utils/capacitor.js +137 -0
  20. package/bin/utils/colors.js +215 -0
  21. package/bin/utils/helpers.js +80 -0
  22. package/bin/utils/spinner.js +96 -0
  23. package/bin/utils/templates.js +1824 -0
  24. package/bin/utils/version.js +108 -0
  25. package/package.json +58 -4
  26. package/src/animations/AnimatedBox.js +607 -0
  27. package/src/animations/AnimatedText.js +105 -0
  28. package/src/animations/MatrixRain.js +220 -0
  29. package/src/animations/ParallaxBox.js +369 -0
  30. package/src/animations/animate.js +332 -0
  31. package/src/animations/index.js +25 -0
  32. package/src/app.js +41 -0
  33. package/src/assets/icon-1024-maskable.png +0 -0
  34. package/src/assets/icon-192-maskable.png +0 -0
  35. package/src/assets/icon-192.png +0 -0
  36. package/src/assets/icon-512-maskable.png +0 -0
  37. package/src/assets/icon-512.png +0 -0
  38. package/src/assets/logo.png +0 -0
  39. package/src/core/App.js +44 -0
  40. package/src/core/hmr-client.js +142 -0
  41. package/src/core/index.js +34 -0
  42. package/src/core/pwa.js +100 -0
  43. package/src/core/runApp.js +430 -0
  44. package/src/create-index.sh +237 -0
  45. package/src/fonts/MaterialIcons-Regular.woff2 +0 -0
  46. package/src/fonts/icons.css +23 -0
  47. package/src/index.d.ts +1930 -0
  48. package/src/index.js +288 -0
  49. package/src/navigations/AdaptiveScaffold.js +149 -0
  50. package/src/navigations/AppBar.js +331 -0
  51. package/src/navigations/BottomNavigation.js +298 -0
  52. package/src/navigations/CollapsibleSideBar.js +139 -0
  53. package/src/navigations/Drawer.js +333 -0
  54. package/src/navigations/DrawerItem.js +260 -0
  55. package/src/navigations/Router.js +475 -0
  56. package/src/navigations/Scaffold.js +537 -0
  57. package/src/navigations/Tabs.js +384 -0
  58. package/src/navigations/index.js +57 -0
  59. package/src/services/RamStore.js +213 -0
  60. package/src/services/Session.js +310 -0
  61. package/src/services/Storage.js +309 -0
  62. package/src/services/http.js +350 -0
  63. package/src/services/index.js +57 -0
  64. package/src/tools/animation.js +42 -0
  65. package/src/tools/array.js +95 -0
  66. package/src/tools/border.js +28 -0
  67. package/src/tools/clipboard.js +104 -0
  68. package/src/tools/color.js +329 -0
  69. package/src/tools/createList.js +215 -0
  70. package/src/tools/delay.js +55 -0
  71. package/src/tools/device.js +138 -0
  72. package/src/tools/dict.js +297 -0
  73. package/src/tools/dimensions.js +118 -0
  74. package/src/tools/filter.js +41 -0
  75. package/src/tools/flex.js +26 -0
  76. package/src/tools/gradient.js +34 -0
  77. package/src/tools/grid.js +57 -0
  78. package/src/tools/index.js +38 -0
  79. package/src/tools/mapList.js +107 -0
  80. package/src/tools/margin.js +93 -0
  81. package/src/tools/memo.js +60 -0
  82. package/src/tools/os.js +104 -0
  83. package/src/tools/padding.js +93 -0
  84. package/src/tools/print.js +69 -0
  85. package/src/tools/random.js +299 -0
  86. package/src/tools/ref.js +70 -0
  87. package/src/tools/rgba.js +4 -0
  88. package/src/tools/shadow.js +23 -0
  89. package/src/tools/string.js +85 -0
  90. package/src/tools/time.js +674 -0
  91. package/src/tools/transform.js +68 -0
  92. package/src/tools/transition.js +48 -0
  93. package/src/tools/useState.js +268 -0
  94. package/src/tools/uuid.js +42 -0
  95. package/src/utils/TextInputValidator.js +256 -0
  96. package/src/utils/composeUpdate.js +65 -0
  97. package/src/utils/getWidgetProps.js +35 -0
  98. package/src/utils/index.js +76 -0
  99. package/src/utils/markdownParser.js +368 -0
  100. package/src/utils/mediaTime.js +79 -0
  101. package/src/utils/navigation.js +263 -0
  102. package/src/utils/scriptInjector.js +73 -0
  103. package/src/utils/stackPosition.js +31 -0
  104. package/src/utils/stopWebRefresh.js +77 -0
  105. package/src/utils/styleInjector.js +38 -0
  106. package/src/utils/syntaxHighlight.js +339 -0
  107. package/src/utils/themes.js +347 -0
  108. package/src/utils/units.js +87 -0
  109. package/src/utils/visualEffects.js +169 -0
  110. package/src/widget-factory/addChildren.js +114 -0
  111. package/src/widget-factory/assignProps.js +256 -0
  112. package/src/widget-factory/createWidget.js +28 -0
  113. package/src/widget-factory/effects.js +132 -0
  114. package/src/widget-factory/index.js +7 -0
  115. package/src/widget-factory/lifecycle.js +210 -0
  116. package/src/widget-factory/makeParentable.js +89 -0
  117. package/src/widget-factory/pixTool.js +40 -0
  118. package/src/widget-factory/processProps.js +122 -0
  119. package/src/widget-factory/reactivity.js +98 -0
  120. package/src/widget-factory/remTool.js +34 -0
  121. package/src/widget-factory/stackPosition.js +87 -0
  122. package/src/widget-factory/stringTool.js +26 -0
  123. package/src/widget-factory/tools.js +249 -0
  124. package/src/widget-factory/translateProps.js +1049 -0
  125. package/src/widget-factory/warnings.js +284 -0
  126. package/src/widget-factory/widgetFactory.js +200 -0
  127. package/src/widgets/Accordion.js +562 -0
  128. package/src/widgets/AdMob.js +315 -0
  129. package/src/widgets/AdSense.js +268 -0
  130. package/src/widgets/AlertDialog.js +162 -0
  131. package/src/widgets/Audio.js +311 -0
  132. package/src/widgets/Avatar.js +210 -0
  133. package/src/widgets/Badge.js +189 -0
  134. package/src/widgets/BottomSheet.js +287 -0
  135. package/src/widgets/Button.js +247 -0
  136. package/src/widgets/Card.js +33 -0
  137. package/src/widgets/Carousel.js +301 -0
  138. package/src/widgets/Chart.js +632 -0
  139. package/src/widgets/Checkbox.js +106 -0
  140. package/src/widgets/Chip.js +153 -0
  141. package/src/widgets/CircularBar.js +352 -0
  142. package/src/widgets/CircularChart.js +549 -0
  143. package/src/widgets/CodeViewer.js +236 -0
  144. package/src/widgets/Column.js +29 -0
  145. package/src/widgets/Container.js +31 -0
  146. package/src/widgets/DataTable.js +257 -0
  147. package/src/widgets/Divider.js +93 -0
  148. package/src/widgets/DraggBox.js +115 -0
  149. package/src/widgets/Dropdown.js +505 -0
  150. package/src/widgets/DroppBox.js +180 -0
  151. package/src/widgets/FloatingActionButton.js +91 -0
  152. package/src/widgets/GridView.js +20 -0
  153. package/src/widgets/Icon.js +33 -0
  154. package/src/widgets/Image.js +15 -0
  155. package/src/widgets/Input.js +565 -0
  156. package/src/widgets/Inspector.js +254 -0
  157. package/src/widgets/InstallButton.js +110 -0
  158. package/src/widgets/ListTile.js +224 -0
  159. package/src/widgets/ListView.js +503 -0
  160. package/src/widgets/Markdown.js +1236 -0
  161. package/src/widgets/Modal.js +496 -0
  162. package/src/widgets/Pagination.js +195 -0
  163. package/src/widgets/ProgressBar.js +178 -0
  164. package/src/widgets/QRCode.js +467 -0
  165. package/src/widgets/Radio.js +132 -0
  166. package/src/widgets/Rating.js +261 -0
  167. package/src/widgets/Row.js +29 -0
  168. package/src/widgets/Skeleton.js +243 -0
  169. package/src/widgets/Slider.js +527 -0
  170. package/src/widgets/SnackBar.js +227 -0
  171. package/src/widgets/Stack.js +29 -0
  172. package/src/widgets/Stepper.js +296 -0
  173. package/src/widgets/Switch.js +102 -0
  174. package/src/widgets/Text.js +98 -0
  175. package/src/widgets/Tooltip.js +267 -0
  176. package/src/widgets/TreeView.js +370 -0
  177. package/src/widgets/Video.js +92 -0
  178. package/src/widgets/index.js +52 -0
@@ -0,0 +1,350 @@
1
+ // FletBox/services/http.js
2
+
3
+ /*
4
+ * HTTP requests module - Python requests style
5
+ *
6
+ * @example
7
+ * // GET
8
+ * const users = await httpGet('https://api.example.com/users');
9
+ *
10
+ * // POST
11
+ * const newUser = await httpPost('https://api.example.com/users', {
12
+ * body: { name: 'Juan', email: 'juan@email.com' }
13
+ * });
14
+ *
15
+ * // PUT
16
+ * const updated = await httpPut('https://api.example.com/users/1', {
17
+ * body: { name: 'Juan Carlos' }
18
+ * });
19
+ *
20
+ * // DELETE
21
+ * await httpDelete('https://api.example.com/users/1');
22
+ *
23
+ * // With headers
24
+ * const data = await httpGet('https://api.example.com/protected', {
25
+ * headers: { 'Authorization': 'Bearer token123' }
26
+ * });
27
+ * // FletBox/services/http.js
28
+ *
29
+ * HTTP requests module - Python requests style
30
+ *
31
+ * @description
32
+ * Module for making HTTP requests with a simple and consistent syntax.
33
+ * Supports GET, POST, PUT, PATCH, DELETE with timeout, headers, and params.
34
+ *
35
+ * @example
36
+ * // 1. GET - Fetch data on start
37
+ * import { useEffect, useState } from 'react';
38
+ * import { Container, Text, httpGet } from 'fletbox';
39
+ *
40
+ * function UsersScreen() {
41
+ * const [users, setUsers] = useState([]);
42
+ *
43
+ * useEffect(() => {
44
+ * async function loadUsers() {
45
+ * const data = await httpGet('https://jsonplaceholder.typicode.com/users');
46
+ * setUsers(data);
47
+ * }
48
+ * loadUsers();
49
+ * }, []);
50
+ *
51
+ * return (
52
+ * <Container>
53
+ * <Text>Total users: {users.length}</Text>
54
+ * </Container>
55
+ * );
56
+ * }
57
+ *
58
+ * @example
59
+ * // 2. POST - Create data
60
+ * import { Button, Container, Text, httpPost } from 'fletbox';
61
+ *
62
+ * function CreateUserScreen() {
63
+ * const [result, setResult] = useState(null);
64
+ *
65
+ * const createUser = async () => {
66
+ * const newUser = await httpPost('https://jsonplaceholder.typicode.com/users', {
67
+ * body: { name: 'Juan', email: 'juan@email.com' }
68
+ * });
69
+ * setResult(newUser);
70
+ * };
71
+ *
72
+ * return (
73
+ * <Container>
74
+ * <Button label="Create User" onPress={createUser} />
75
+ * {result && <Text>User created ID: {result.id}</Text>}
76
+ * </Container>
77
+ * );
78
+ * }
79
+ *
80
+ * @example
81
+ * // 3. PUT - Update full data
82
+ * import { Button, Container, Text, httpPut } from 'fletbox';
83
+ *
84
+ * function UpdateUserScreen() {
85
+ * const [result, setResult] = useState(null);
86
+ *
87
+ * const updateUser = async () => {
88
+ * const updated = await httpPut('https://jsonplaceholder.typicode.com/users/1', {
89
+ * body: { name: 'Carlos', email: 'carlos@email.com' }
90
+ * });
91
+ * setResult(updated);
92
+ * };
93
+ *
94
+ * return (
95
+ * <Container>
96
+ * <Button label="Update User" onPress={updateUser} />
97
+ * {result && <Text>User updated: {result.name}</Text>}
98
+ * </Container>
99
+ * );
100
+ * }
101
+ *
102
+ * @example
103
+ * // 4. PATCH - Update partial data
104
+ * import { Button, Container, Text, httpPatch } from 'fletbox';
105
+ *
106
+ * function PatchUserScreen() {
107
+ * const [result, setResult] = useState(null);
108
+ *
109
+ * const patchUser = async () => {
110
+ * const patched = await httpPatch('https://jsonplaceholder.typicode.com/users/1', {
111
+ * body: { email: 'nuevo@email.com' }
112
+ * });
113
+ * setResult(patched);
114
+ * };
115
+ *
116
+ * return (
117
+ * <Container>
118
+ * <Button label="Update Email" onPress={patchUser} />
119
+ * {result && <Text>Email updated: {result.email}</Text>}
120
+ * </Container>
121
+ * );
122
+ * }
123
+ *
124
+ * @example
125
+ * // 5. DELETE - Delete data
126
+ * import { Button, Container, Text, httpDelete } from 'fletbox';
127
+ *
128
+ * function DeleteUserScreen() {
129
+ * const [deleted, setDeleted] = useState(false);
130
+ *
131
+ * const deleteUser = async () => {
132
+ * await httpDelete('https://jsonplaceholder.typicode.com/users/1');
133
+ * setDeleted(true);
134
+ * };
135
+ *
136
+ * return (
137
+ * <Container>
138
+ * <Button label="Delete User" onPress={deleteUser} />
139
+ * {deleted && <Text>User deleted</Text>}
140
+ * </Container>
141
+ * );
142
+ * }
143
+ *
144
+ * @example
145
+ * // 6. GET with headers and params
146
+ * import { useEffect, useState } from 'react';
147
+ * import { Container, Text, httpGet } from 'fletbox';
148
+ *
149
+ * function ProfileScreen() {
150
+ * const [profile, setProfile] = useState(null);
151
+ *
152
+ * useEffect(() => {
153
+ * async function loadProfile() {
154
+ * const data = await httpGet('https://api.example.com/profile', {
155
+ * headers: { 'Authorization': 'Bearer token123' },
156
+ * params: { userId: 1 }
157
+ * });
158
+ * setProfile(data);
159
+ * }
160
+ * loadProfile();
161
+ * }, []);
162
+ *
163
+ * return (
164
+ * <Container>
165
+ * <Text>Name: {profile?.name}</Text>
166
+ * <Text>Email: {profile?.email}</Text>
167
+ * </Container>
168
+ * );
169
+ * }
170
+ *
171
+ * @example
172
+ * // 7. Error handling
173
+ * import { useEffect, useState } from 'react';
174
+ * import { Container, Text, httpGet } from 'fletbox';
175
+ *
176
+ * function ErrorScreen() {
177
+ * const [data, setData] = useState(null);
178
+ * const [error, setError] = useState(null);
179
+ *
180
+ * useEffect(() => {
181
+ * async function loadData() {
182
+ * try {
183
+ * const result = await httpGet('https://api.example.com/data');
184
+ * setData(result);
185
+ * } catch (err) {
186
+ * setError(err.message);
187
+ * }
188
+ * }
189
+ * loadData();
190
+ * }, []);
191
+ *
192
+ * return (
193
+ * <Container>
194
+ * {error && <Text color="red">Error: {error}</Text>}
195
+ * {data && <Text>Data loaded</Text>}
196
+ * </Container>
197
+ * );
198
+ * }
199
+ *
200
+ * @example
201
+ * // 8. Loading state
202
+ * import { useEffect, useState } from 'react';
203
+ * import { Container, Text, ActivityIndicator, httpGet } from 'fletbox';
204
+ *
205
+ * function LoadingScreen() {
206
+ * const [users, setUsers] = useState([]);
207
+ * const [loading, setLoading] = useState(true);
208
+ *
209
+ * useEffect(() => {
210
+ * async function loadUsers() {
211
+ * const data = await httpGet('https://jsonplaceholder.typicode.com/users');
212
+ * setUsers(data);
213
+ * setLoading(false);
214
+ * }
215
+ * loadUsers();
216
+ * }, []);
217
+ *
218
+ * return (
219
+ * <Container>
220
+ * {loading ? (
221
+ * <ActivityIndicator size="large" />
222
+ * ) : (
223
+ * <Text>Users loaded: {users.length}</Text>
224
+ * )}
225
+ * </Container>
226
+ * );
227
+ * }
228
+ *
229
+ * @example
230
+ * // 9. POST with loading
231
+ * import { useState } from 'react';
232
+ * import { Button, Container, Text, ActivityIndicator, httpPost } from 'fletbox';
233
+ *
234
+ * function SubmitScreen() {
235
+ * const [loading, setLoading] = useState(false);
236
+ * const [result, setResult] = useState(null);
237
+ *
238
+ * const submit = async () => {
239
+ * setLoading(true);
240
+ * try {
241
+ * const data = await httpPost('https://api.example.com/submit', {
242
+ * body: { message: 'Hello world' }
243
+ * });
244
+ * setResult(data);
245
+ * } finally {
246
+ * setLoading(false);
247
+ * }
248
+ * };
249
+ *
250
+ * return (
251
+ * <Container>
252
+ * {loading ? (
253
+ * <ActivityIndicator size="large" />
254
+ * ) : (
255
+ * <Button label="Submit" onPress={submit} />
256
+ * )}
257
+ * {result && <Text>Submitted: {result.id}</Text>}
258
+ * </Container>
259
+ * );
260
+ * }
261
+ *
262
+ * @example
263
+ * // 10. Custom timeout
264
+ * import { httpGet } from 'fletbox';
265
+ *
266
+ * async function slowRequest() {
267
+ * try {
268
+ * const data = await httpGet('https://api.lenta.com/datos', {
269
+ * timeout: 5000 // 5 seconds max
270
+ * });
271
+ * console.log(data);
272
+ * } catch (error) {
273
+ * console.log('Timeout or error:', error.message);
274
+ * }
275
+ * }
276
+ */
277
+
278
+ async function httpRequest(method, url, options = {}) {
279
+ const { body, headers = {}, params = {}, timeout = 30000, ...rest } = options;
280
+
281
+ const queryString = new URLSearchParams(
282
+ Object.entries(params).filter(([, value]) => value !== null && value !== undefined),
283
+ ).toString();
284
+ const separator = url.includes("?") ? "&" : "?";
285
+ const finalUrl = queryString ? `${url}${separator}${queryString}` : url;
286
+
287
+ const config = {
288
+ method: method.toUpperCase(),
289
+ headers: {
290
+ "Content-Type": "application/json",
291
+ ...headers,
292
+ },
293
+ signal: AbortSignal.timeout(timeout),
294
+ ...rest,
295
+ };
296
+
297
+ if (body !== undefined) {
298
+ config.body = typeof body === "string" ? body : JSON.stringify(body);
299
+ }
300
+
301
+ try {
302
+ const response = await fetch(finalUrl, config);
303
+
304
+ let data;
305
+ const contentType = response.headers.get("content-type");
306
+ if (contentType?.includes("application/json")) {
307
+ data = await response.json();
308
+ } else {
309
+ data = await response.text();
310
+ }
311
+
312
+ if (!response.ok) {
313
+ const error = new Error(
314
+ `HTTP ${response.status}: ${response.statusText}`,
315
+ );
316
+ error.response = response;
317
+ error.data = data;
318
+ throw error;
319
+ }
320
+
321
+ return data;
322
+ } catch (error) {
323
+ if (error.name === "TimeoutError") {
324
+ throw new Error(`Request timeout after ${timeout}ms`);
325
+ }
326
+ throw error;
327
+ }
328
+ }
329
+
330
+ export async function httpGet(url, options = {}) {
331
+ return httpRequest("GET", url, options);
332
+ }
333
+
334
+ export async function httpPost(url, options = {}) {
335
+ return httpRequest("POST", url, options);
336
+ }
337
+
338
+ export async function httpPut(url, options = {}) {
339
+ return httpRequest("PUT", url, options);
340
+ }
341
+
342
+ export async function httpPatch(url, options = {}) {
343
+ return httpRequest("PATCH", url, options);
344
+ }
345
+
346
+ export async function httpDelete(url, options = {}) {
347
+ return httpRequest("DELETE", url, options);
348
+ }
349
+
350
+ export { httpRequest };
@@ -0,0 +1,57 @@
1
+ // src/services/index.js
2
+ export {
3
+ default as RamStore,
4
+ saveRam,
5
+ getRam,
6
+ getAllRam,
7
+ getAllRamKeys,
8
+ hasRam,
9
+ updateRam,
10
+ deleteRam,
11
+ clearAllRam,
12
+ subscribeRam,
13
+ getRamItemCount,
14
+ isRamAvailable,
15
+ } from "./RamStore.js";
16
+ export {
17
+ default as Session,
18
+ saveSession,
19
+ getSession,
20
+ getSessionSync,
21
+ updateSession,
22
+ deleteSession,
23
+ clearAllSession,
24
+ hasSession,
25
+ getAllSessionKeys,
26
+ getAllSessionData,
27
+ getSessionSize,
28
+ deleteSessionByPrefix,
29
+ deleteSessionBySuffix,
30
+ getSessionItemCount,
31
+ isSessionAvailable,
32
+ } from "./Session.js";
33
+ export {
34
+ default as Storage,
35
+ saveData,
36
+ getData,
37
+ getDataSync,
38
+ updateData,
39
+ deleteData,
40
+ clearAllData,
41
+ hasData,
42
+ getAllKeys,
43
+ getAllData,
44
+ getStorageSize,
45
+ deleteDataByPrefix,
46
+ deleteDataBySuffix,
47
+ getItemCount,
48
+ isStorageAvailable,
49
+ } from "./Storage.js";
50
+ export {
51
+ httpGet,
52
+ httpPost,
53
+ httpPut,
54
+ httpPatch,
55
+ httpDelete,
56
+ httpRequest,
57
+ } from "./http.js";
@@ -0,0 +1,42 @@
1
+ // tools/animation.js
2
+
3
+ /**
4
+ * Generate CSS animation string
5
+ * @param {string|Object} name - Animation name or options object
6
+ * @param {number} duration - Duration in seconds
7
+ * @param {string} timing - Timing function
8
+ * @param {string} iteration - Iteration count
9
+ * @returns {string} CSS animation value
10
+ *
11
+ * @example
12
+ * animation('fadeIn', 0.5, 'ease-in-out', 'infinite')
13
+ * // "fadeIn 0.5s ease-in-out infinite"
14
+ *
15
+ * @example
16
+ * animation({ name: 'slideIn', duration: 0.3, timing: 'ease', delay: 0.1, fillMode: 'forwards' })
17
+ * // "slideIn 0.3s ease 0.1s forwards"
18
+ */
19
+ export const animation = (
20
+ name,
21
+ duration = 0.3,
22
+ timing = "ease",
23
+ iteration = "1",
24
+ ) => {
25
+ if (typeof name === "object") {
26
+ const n = name.name;
27
+ const d = name.duration ?? 0.3;
28
+ const t = name.timing ?? "ease";
29
+ const dl = name.delay ?? 0;
30
+ const i = name.iteration ?? "1";
31
+ const dir = name.direction ?? "normal";
32
+ const fill = name.fillMode ?? "none";
33
+
34
+ let result = `${n} ${d}s ${t} ${dl}s ${i} ${dir}`;
35
+ if (fill !== "none") result += ` ${fill}`;
36
+ return result;
37
+ }
38
+
39
+ return `${name} ${duration}s ${timing} ${iteration}`;
40
+ };
41
+
42
+ export default animation;
@@ -0,0 +1,95 @@
1
+ // tools/array.js
2
+
3
+ /**
4
+ * Shuffle an array (Fisher-Yates)
5
+ * @param {Array} arr - Input array
6
+ * @returns {Array} New shuffled array
7
+ * @example shuffle([1,2,3,4,5]) // [3,1,5,2,4]
8
+ */
9
+ export const shuffle = (arr) => {
10
+ const result = [...arr];
11
+ for (let i = result.length - 1; i > 0; i--) {
12
+ const j = Math.floor(Math.random() * (i + 1));
13
+ [result[i], result[j]] = [result[j], result[i]];
14
+ }
15
+ return result;
16
+ };
17
+
18
+ /**
19
+ * Reverse an array
20
+ * @param {Array} arr - Input array
21
+ * @returns {Array} New reversed array
22
+ * @example reverse([1,2,3,4]) // [4,3,2,1]
23
+ */
24
+ export const reverse = (arr) => {
25
+ return [...arr].reverse();
26
+ };
27
+
28
+ /**
29
+ * Sort array by key or custom function
30
+ * @param {Array} arr - Input array
31
+ * @param {string|Function} by - Key to sort by or sort function
32
+ * @param {string} order - 'asc' or 'desc' (default: 'asc')
33
+ * @returns {Array} New sorted array
34
+ * @example sort(users, 'name')
35
+ * @example sort(users, (a,b) => a.age - b.age)
36
+ */
37
+ export const sort = (arr, by, order = "asc") => {
38
+ const result = [...arr];
39
+
40
+ if (typeof by === "function") {
41
+ result.sort(by);
42
+ } else if (typeof by === "string") {
43
+ result.sort((a, b) => {
44
+ const aVal = a[by];
45
+ const bVal = b[by];
46
+ if (typeof aVal === "number") {
47
+ return order === "asc" ? aVal - bVal : bVal - aVal;
48
+ }
49
+ const cmp = String(aVal).localeCompare(String(bVal));
50
+ return order === "asc" ? cmp : -cmp;
51
+ });
52
+ } else {
53
+ result.sort();
54
+ }
55
+
56
+ return order === "desc" && typeof by !== "function"
57
+ ? result.reverse()
58
+ : result;
59
+ };
60
+
61
+ /**
62
+ * Get unique values from array
63
+ * @param {Array} arr - Input array
64
+ * @param {string} key - Optional key for objects
65
+ * @returns {Array} Array with unique values
66
+ */
67
+ export const unique = (arr, key = null) => {
68
+ if (key) {
69
+ const seen = new Set();
70
+ return arr.filter((item) => {
71
+ const value = item[key];
72
+ if (seen.has(value)) return false;
73
+ seen.add(value);
74
+ return true;
75
+ });
76
+ }
77
+ return [...new Set(arr)];
78
+ };
79
+
80
+ /**
81
+ * Chunk array into smaller arrays
82
+ * @param {Array} arr - Input array
83
+ * @param {number} size - Chunk size
84
+ * @returns {Array} Array of chunks
85
+ * @example chunk([1,2,3,4,5], 2) // [[1,2], [3,4], [5]]
86
+ */
87
+ export const chunk = (arr, size) => {
88
+ const result = [];
89
+ for (let i = 0; i < arr.length; i += size) {
90
+ result.push(arr.slice(i, i + size));
91
+ }
92
+ return result;
93
+ };
94
+
95
+ export default { shuffle, reverse, sort, unique, chunk };
@@ -0,0 +1,28 @@
1
+ // tools/border.js
2
+
3
+ /**
4
+ * Generate a CSS border shorthand string.
5
+ *
6
+ * Converts individual border parameters into the `border` CSS shorthand so
7
+ * callers don't have to concatenate strings manually. Accepts either a number
8
+ * (auto-suffixed with `"px"`) or a pre-formatted string for `width`.
9
+ *
10
+ * @param {number|string} [width=1] - Border width. Numbers are treated as pixels;
11
+ * strings are used as-is (e.g. `"0.5rem"`, `"thin"`).
12
+ * @param {string} [style="solid"] - Border style — any valid CSS border-style keyword
13
+ * (`"solid"`, `"dashed"`, `"dotted"`, `"double"`, `"none"`, etc.)
14
+ * @param {string} color - Border color — any valid CSS color value.
15
+ * Required; there is no default because a colorless border is invisible.
16
+ * @returns {string} CSS border shorthand value
17
+ *
18
+ * @example
19
+ * border(1, 'solid', '#ccc') // "1px solid #ccc"
20
+ * border(2, 'dashed', 'red') // "2px dashed red"
21
+ * border('0.5rem', 'dotted', 'var(--outline)') // "0.5rem dotted var(--outline)"
22
+ */
23
+ export const border = (width = 1, style = "solid", color) => {
24
+ const widthStr = typeof width === "number" ? `${width}px` : width;
25
+ return `${widthStr} ${style} ${color}`;
26
+ };
27
+
28
+ export default border;
@@ -0,0 +1,104 @@
1
+ // src/components/flet-box/tools/clipboard.js
2
+
3
+ /**
4
+ * Clipboard utility — provides a synchronous-feeling API for reading from and
5
+ * writing to the system clipboard.
6
+ *
7
+ * `copy` and `copyWithFeedback` are intentionally synchronous from the caller's
8
+ * perspective: the underlying async Clipboard API is handled internally so
9
+ * callers do not need to `await` them. `read` is genuinely async and must be
10
+ * awaited.
11
+ */
12
+ export const clipboard = {
13
+ /**
14
+ * Copies text to the clipboard.
15
+ *
16
+ * Uses the modern async `navigator.clipboard.writeText` API when available,
17
+ * falling back to a hidden `<textarea>` + `execCommand("copy")` for older
18
+ * browsers. Errors from the async path are caught and silently rerouted to
19
+ * the fallback.
20
+ *
21
+ * @param {string} text - Text to copy.
22
+ * @returns {void}
23
+ */
24
+ copy: (text) => {
25
+ // Modern path — async internally but fire-and-forget for the caller
26
+ if (navigator.clipboard && navigator.clipboard.writeText) {
27
+ navigator.clipboard.writeText(text).catch((err) => {
28
+ console.error("Clipboard failed:", err);
29
+ clipboard._fallbackCopy(text);
30
+ });
31
+ } else {
32
+ // Fallback for browsers that do not support the Clipboard API
33
+ clipboard._fallbackCopy(text);
34
+ }
35
+ },
36
+
37
+ /**
38
+ * Legacy synchronous fallback that copies text using a hidden `<textarea>`
39
+ * and the deprecated `execCommand("copy")`.
40
+ *
41
+ * @private
42
+ * @param {string} text - Text to copy.
43
+ * @returns {void}
44
+ */
45
+ _fallbackCopy: (text) => {
46
+ const textarea = document.createElement("textarea");
47
+ textarea.value = text;
48
+ textarea.style.position = "fixed";
49
+ textarea.style.left = "-9999px";
50
+ textarea.style.top = "-9999px";
51
+ document.body.appendChild(textarea);
52
+ textarea.select();
53
+ document.execCommand("copy");
54
+ document.body.removeChild(textarea);
55
+ },
56
+
57
+ /**
58
+ * Copies text to the clipboard and briefly updates a DOM element to show
59
+ * visual confirmation (a checkmark for 1 second).
60
+ *
61
+ * @param {string} text - Text to copy.
62
+ * @param {HTMLElement} element - Element whose content is temporarily replaced
63
+ * with a `"✓"` and whose background turns green as feedback.
64
+ * @returns {void}
65
+ */
66
+ copyWithFeedback: (text, element) => {
67
+ clipboard.copy(text);
68
+
69
+ if (element) {
70
+ const originalText = element.textContent;
71
+ const originalBg = element.style.backgroundColor;
72
+
73
+ element.textContent = "✓";
74
+ element.style.backgroundColor = "#27ae60";
75
+
76
+ setTimeout(() => {
77
+ element.textContent = originalText;
78
+ element.style.backgroundColor = originalBg;
79
+ }, 1000);
80
+ }
81
+ },
82
+
83
+ /**
84
+ * Reads the current text content of the clipboard.
85
+ *
86
+ * Unlike `copy`, this method IS async and must be awaited. It returns an
87
+ * empty string on failure (e.g. when permission is denied).
88
+ *
89
+ * @returns {Promise<string>} The clipboard text, or `""` on error.
90
+ *
91
+ * @example
92
+ * const text = await clipboard.read();
93
+ */
94
+ read: async () => {
95
+ try {
96
+ return await navigator.clipboard.readText();
97
+ } catch (error) {
98
+ console.error("Failed to read:", error);
99
+ return "";
100
+ }
101
+ },
102
+ };
103
+
104
+ export default clipboard;