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,339 @@
1
+ // utils/syntaxHighlight.js
2
+
3
+ // ========== FLETBOX COLORS ==========
4
+ const fletboxColors = {
5
+ primary: "#6366f1",
6
+ secondary: "#8b5cf6",
7
+ success: "#10b981",
8
+ warning: "#f59e0b",
9
+ danger: "#ef4444",
10
+ info: "#3b82f6",
11
+ background: "#ffffff",
12
+ surface: "#f8fafc",
13
+ text: "#0f172a",
14
+ textSecondary: "#64748b",
15
+ border: "#e2e8f0",
16
+ };
17
+
18
+ // ========== FLETBOX KEYWORDS ==========
19
+ const fletboxKeywords = [
20
+ "Container",
21
+ "Row",
22
+ "Column",
23
+ "Stack",
24
+ "ListView",
25
+ "GridView",
26
+ "Text",
27
+ "Button",
28
+ "Icon",
29
+ "Image",
30
+ "Avatar",
31
+ "Card",
32
+ "ListTile",
33
+ "Input",
34
+ "Checkbox",
35
+ "Radio",
36
+ "Switch",
37
+ "Slider",
38
+ "Dropdown",
39
+ "ProgressBar",
40
+ "Rating",
41
+ "Chip",
42
+ "Badge",
43
+ "Divider",
44
+ "Accordion",
45
+ "Modal",
46
+ "BottomSheet",
47
+ "AlertDialog",
48
+ "SnackBar",
49
+ "Scaffold",
50
+ "AppBar",
51
+ "Drawer",
52
+ "BottomNavigation",
53
+ "Tabs",
54
+ "DraggBox",
55
+ "DroppBox",
56
+ "AnimatedBox",
57
+ "AnimatedText",
58
+ "colors",
59
+ "runApp",
60
+ "goTo",
61
+ "useState",
62
+ "httpGet",
63
+ "httpPost",
64
+ ];
65
+
66
+ // ========== JAVASCRIPT KEYWORDS ==========
67
+ const jsKeywords = [
68
+ "const",
69
+ "let",
70
+ "var",
71
+ "function",
72
+ "return",
73
+ "if",
74
+ "else",
75
+ "for",
76
+ "while",
77
+ "do",
78
+ "switch",
79
+ "case",
80
+ "break",
81
+ "continue",
82
+ "try",
83
+ "catch",
84
+ "finally",
85
+ "throw",
86
+ "new",
87
+ "this",
88
+ "typeof",
89
+ "instanceof",
90
+ "delete",
91
+ "in",
92
+ "of",
93
+ "class",
94
+ "extends",
95
+ "super",
96
+ "import",
97
+ "export",
98
+ "default",
99
+ "from",
100
+ "as",
101
+ "async",
102
+ "await",
103
+ "true",
104
+ "false",
105
+ "null",
106
+ "undefined",
107
+ ];
108
+
109
+ // ========== TOKEN TYPES ==========
110
+ const TokenType = {
111
+ KEYWORD: "keyword",
112
+ STRING: "string",
113
+ NUMBER: "number",
114
+ COMMENT: "comment",
115
+ PROPERTY: "property",
116
+ FUNCTION: "function",
117
+ BRACKET: "bracket",
118
+ PUNCTUATION: "punctuation",
119
+ FLETBOX_COLOR: "fletbox-color",
120
+ FLETBOX_WIDGET: "fletbox-widget",
121
+ };
122
+
123
+ // ========== COLORS PER TOKEN ==========
124
+ export const highlightColors = {
125
+ [TokenType.KEYWORD]: "#c678dd", // purple
126
+ [TokenType.STRING]: "#98c379", // green
127
+ [TokenType.NUMBER]: "#d19a66", // orange
128
+ [TokenType.COMMENT]: "#5c6370", // gray
129
+ [TokenType.PROPERTY]: "#e06c75", // red
130
+ [TokenType.FUNCTION]: "#61afef", // blue
131
+ [TokenType.BRACKET]: "#abb2bf", // light gray
132
+ [TokenType.PUNCTUATION]: "#abb2bf", // light gray
133
+ [TokenType.FLETBOX_COLOR]: "#56b6c2", // cyan
134
+ [TokenType.FLETBOX_WIDGET]: "#e5c07b", // yellow
135
+ };
136
+
137
+ // ========== DETECT FLETBOX COLORS ==========
138
+ /**
139
+ * Returns `true` when the given word represents a FletBox color token.
140
+ * Matches palette keys, `colors.<name>` expressions, hex literals, and `rgb`/`rgba` values.
141
+ * @param {string} word - Token text to test.
142
+ * @returns {boolean}
143
+ * @private
144
+ */
145
+ const isFletboxColor = (word) => {
146
+ return (
147
+ fletboxColors.hasOwnProperty(word) ||
148
+ word.match(/^colors\.\w+$/) ||
149
+ word.match(/^#[0-9A-Fa-f]{3,6}$/) ||
150
+ word.match(/^rgba?\(/)
151
+ );
152
+ };
153
+
154
+ // ========== DETECT FLETBOX WIDGETS ==========
155
+ /**
156
+ * Returns `true` when the given word is a known FletBox widget or utility identifier.
157
+ * @param {string} word - Token text to test.
158
+ * @returns {boolean}
159
+ * @private
160
+ */
161
+ const isFletboxWidget = (word) => {
162
+ return fletboxKeywords.includes(word);
163
+ };
164
+
165
+ // ========== MAIN TOKENIZER ==========
166
+ /**
167
+ * Tokenizes a JavaScript/FletBox source string into an array of typed tokens.
168
+ *
169
+ * Token types: `keyword`, `string`, `number`, `comment`, `property`,
170
+ * `function`, `bracket`, `punctuation`, `fletbox-color`, `fletbox-widget`,
171
+ * `whitespace`, `text`.
172
+ *
173
+ * @param {string} code - Source code to tokenize.
174
+ * @returns {Array<{type: string, value: string}>} Ordered token array.
175
+ * @example
176
+ * tokenize('const x = 42;')
177
+ * // [
178
+ * // { type: 'keyword', value: 'const' },
179
+ * // { type: 'whitespace', value: ' ' },
180
+ * // { type: 'property', value: 'x' },
181
+ * // ...
182
+ * // ]
183
+ */
184
+ export const tokenize = (code) => {
185
+ const tokens = [];
186
+ let i = 0;
187
+ const len = code.length;
188
+
189
+ while (i < len) {
190
+ const char = code[i];
191
+
192
+ // ========== STRINGS ==========
193
+ if (char === '"' || char === "'" || char === "`") {
194
+ const quote = char;
195
+ let start = i;
196
+ i++;
197
+ while (i < len && code[i] !== quote) {
198
+ if (code[i] === "\\" && i + 1 < len) i++;
199
+ i++;
200
+ }
201
+ i++;
202
+ tokens.push({
203
+ type: TokenType.STRING,
204
+ value: code.substring(start, i),
205
+ });
206
+ continue;
207
+ }
208
+
209
+ // ========== COMMENTS ==========
210
+ if (char === "/" && code[i + 1] === "/") {
211
+ let start = i;
212
+ while (i < len && code[i] !== "\n") i++;
213
+ tokens.push({
214
+ type: TokenType.COMMENT,
215
+ value: code.substring(start, i),
216
+ });
217
+ continue;
218
+ }
219
+
220
+ if (char === "/" && code[i + 1] === "*") {
221
+ let start = i;
222
+ i += 2;
223
+ while (i < len && !(code[i - 1] === "*" && code[i] === "/")) i++;
224
+ i++;
225
+ tokens.push({
226
+ type: TokenType.COMMENT,
227
+ value: code.substring(start, i),
228
+ });
229
+ continue;
230
+ }
231
+
232
+ // ========== NUMBERS ==========
233
+ if (/[0-9]/.test(char)) {
234
+ let start = i;
235
+ while (i < len && /[0-9.]/.test(code[i])) i++;
236
+ tokens.push({
237
+ type: TokenType.NUMBER,
238
+ value: code.substring(start, i),
239
+ });
240
+ continue;
241
+ }
242
+
243
+ // ========== WORDS (identifiers) ==========
244
+ if (/[a-zA-Z_$]/.test(char)) {
245
+ let start = i;
246
+ while (i < len && /[a-zA-Z0-9_$]/.test(code[i])) i++;
247
+ const word = code.substring(start, i);
248
+
249
+ // Detect word type
250
+ if (isFletboxWidget(word)) {
251
+ tokens.push({ type: TokenType.FLETBOX_WIDGET, value: word });
252
+ } else if (isFletboxColor(word)) {
253
+ tokens.push({ type: TokenType.FLETBOX_COLOR, value: word });
254
+ } else if (jsKeywords.includes(word)) {
255
+ tokens.push({ type: TokenType.KEYWORD, value: word });
256
+ } else if (word.match(/^[a-z][a-zA-Z0-9]*$/)) {
257
+ // Possible property or function
258
+ tokens.push({ type: TokenType.PROPERTY, value: word });
259
+ } else {
260
+ tokens.push({ type: TokenType.PROPERTY, value: word });
261
+ }
262
+ continue;
263
+ }
264
+
265
+ // ========== BRACKETS AND PUNCTUATION ==========
266
+ if ("{}[]()".includes(char)) {
267
+ tokens.push({ type: TokenType.BRACKET, value: char });
268
+ i++;
269
+ continue;
270
+ }
271
+
272
+ if (",;:.".includes(char)) {
273
+ tokens.push({ type: TokenType.PUNCTUATION, value: char });
274
+ i++;
275
+ continue;
276
+ }
277
+
278
+ // ========== WHITESPACE (preserved for formatting) ==========
279
+ if (/\s/.test(char)) {
280
+ let start = i;
281
+ while (i < len && /\s/.test(code[i])) i++;
282
+ tokens.push({ type: "whitespace", value: code.substring(start, i) });
283
+ continue;
284
+ }
285
+
286
+ // ========== ANY OTHER CHARACTER ==========
287
+ tokens.push({ type: "text", value: char });
288
+ i++;
289
+ }
290
+
291
+ return tokens;
292
+ };
293
+
294
+ // ========== GENERATE HTML WITH COLORS ==========
295
+ /**
296
+ * Tokenizes `code` and wraps each token in a `<span>` with an inline `color`
297
+ * style matching {@link highlightColors}. Whitespace and unrecognized tokens
298
+ * are emitted without a wrapper.
299
+ * @param {string} code - Source code to highlight.
300
+ * @returns {string} HTML string safe for insertion into `innerHTML`.
301
+ */
302
+ export const generateHighlightedHtml = (code) => {
303
+ const tokens = tokenize(code);
304
+ let html = "";
305
+
306
+ for (const token of tokens) {
307
+ if (token.type === "whitespace") {
308
+ html += token.value;
309
+ } else {
310
+ const color = highlightColors[token.type];
311
+ if (color) {
312
+ html += `<span style="color: ${color};">${escapeHtml(token.value)}</span>`;
313
+ } else {
314
+ html += escapeHtml(token.value);
315
+ }
316
+ }
317
+ }
318
+
319
+ return html;
320
+ };
321
+
322
+ // ========== ESCAPE HTML ==========
323
+ /**
324
+ * Escapes HTML special characters so that token values can be safely inserted
325
+ * into a `<span>` element's content.
326
+ * @param {string} text - Raw token text.
327
+ * @returns {string} HTML-safe string.
328
+ * @private
329
+ */
330
+ const escapeHtml = (text) => {
331
+ return text
332
+ .replace(/&/g, "&amp;")
333
+ .replace(/</g, "&lt;")
334
+ .replace(/>/g, "&gt;")
335
+ .replace(/"/g, "&quot;")
336
+ .replace(/'/g, "&#39;");
337
+ };
338
+
339
+ export default { tokenize, generateHighlightedHtml, highlightColors };
@@ -0,0 +1,347 @@
1
+ // src/components/flet-box/utils/themes.js
2
+
3
+ // ========== DEFAULT COLORS (static, safe) ==========
4
+ const DEFAULT_COLORS = {
5
+ primary: "#6366f1",
6
+ secondary: "#8b5cf6",
7
+ success: "#10b981",
8
+ warning: "#f59e0b",
9
+ danger: "#ef4444",
10
+ info: "#3b82f6",
11
+ background: "#ffffff",
12
+ surface: "#f8fafc",
13
+ card: "#ffffff",
14
+ text: "#0f172a",
15
+ textSecondary: "#64748b",
16
+ textDisabled: "#94a3b8",
17
+ border: "#e2e8f0",
18
+ gray50: "#f8fafc",
19
+ gray100: "#f1f5f9",
20
+ gray200: "#e2e8f0",
21
+ gray300: "#cbd5e1",
22
+ gray400: "#94a3b8",
23
+ gray500: "#64748b",
24
+ gray600: "#475569",
25
+ gray700: "#334155",
26
+ gray800: "#1e293b",
27
+ gray900: "#0f172a",
28
+ overlay: "rgba(0, 0, 0, 0.4)",
29
+ shadow: "rgba(0, 0, 0, 0.1)",
30
+ divider: "#eeeeee",
31
+ error: "#ff3b30",
32
+ errorBg: "#ffe5e5",
33
+ successBg: "#e5f5e5",
34
+ warningBg: "#fff5e5",
35
+ infoBg: "#e5f5ff",
36
+ };
37
+
38
+ // ========== EXPORT colors (always defined) ==========
39
+ export let colors = { ...DEFAULT_COLORS };
40
+
41
+ // ========== PALETTES ==========
42
+ export const palettes = {
43
+ light: {
44
+ primary: "#00A5FF",
45
+ secondary: "#8b5cf6",
46
+ success: "#10b981",
47
+ warning: "#f59e0b",
48
+ danger: "#ef4444",
49
+ info: "#3b82f6",
50
+ background: "#ffffff",
51
+ surface: "#f8fafc",
52
+ card: "#ffffff",
53
+ text: "#0f172a",
54
+ textSecondary: "#64748b",
55
+ textDisabled: "#94a3b8",
56
+ border: "#e2e8f0",
57
+ gray50: "#f8fafc",
58
+ gray100: "#f1f5f9",
59
+ gray200: "#e2e8f0",
60
+ gray300: "#cbd5e1",
61
+ gray400: "#94a3b8",
62
+ gray500: "#64748b",
63
+ gray600: "#475569",
64
+ gray700: "#334155",
65
+ gray800: "#1e293b",
66
+ gray900: "#0f172a",
67
+ overlay: "rgba(0, 0, 0, 0.4)",
68
+ shadow: "rgba(0, 0, 0, 0.1)",
69
+ },
70
+ dark: {
71
+ primary: "#00AFBC",
72
+ secondary: "#a78bfa",
73
+ success: "#10b981",
74
+ warning: "#fbbf24",
75
+ danger: "#f87171",
76
+ info: "#60a5fa",
77
+ background: "#0f172a",
78
+ surface: "#1e293b",
79
+ card: "#1e293b",
80
+ text: "#f1f5f9",
81
+ textSecondary: "#94a3b8",
82
+ textDisabled: "#64748b",
83
+ border: "#334155",
84
+ gray50: "#1e293b",
85
+ gray100: "#334155",
86
+ gray200: "#475569",
87
+ gray300: "#64748b",
88
+ gray400: "#94a3b8",
89
+ gray500: "#cbd5e1",
90
+ gray600: "#e2e8f0",
91
+ gray700: "#f1f5f9",
92
+ gray800: "#f8fafc",
93
+ gray900: "#ffffff",
94
+ overlay: "rgba(0, 0, 0, 0.6)",
95
+ shadow: "rgba(0, 0, 0, 0.3)",
96
+ },
97
+ };
98
+
99
+ // ========== SUBSCRIBERS ==========
100
+ const themeSubscribers = [];
101
+
102
+ /**
103
+ * Subscribes a callback to theme change events.
104
+ * @param {function(Object): void} callback - Function called with the new colors object on every theme change.
105
+ * @returns {function(): void} Unsubscribe function — call it to remove the listener.
106
+ * @example
107
+ * const unsub = subscribeTheme((colors) => console.log(colors.primary));
108
+ * unsub(); // stop listening
109
+ */
110
+ export const subscribeTheme = (callback) => {
111
+ if (typeof callback === "function") {
112
+ themeSubscribers.push(callback);
113
+ }
114
+ return () => {
115
+ const index = themeSubscribers.indexOf(callback);
116
+ if (index > -1) themeSubscribers.splice(index, 1);
117
+ };
118
+ };
119
+
120
+ /**
121
+ * Notifies all registered theme subscribers of a color change.
122
+ * Errors thrown by individual subscribers are caught and warned.
123
+ * @private
124
+ */
125
+ const notifyThemeChange = () => {
126
+ themeSubscribers.forEach((cb) => {
127
+ try {
128
+ cb(colors);
129
+ } catch (e) {
130
+ console.warn("Theme subscriber error:", e);
131
+ }
132
+ });
133
+ };
134
+
135
+ // ========== THEME FUNCTIONS ==========
136
+ /**
137
+ * Applies a named theme palette to the global `colors` object, updates all
138
+ * CSS custom properties on `:root`, persists the choice to `localStorage`,
139
+ * and notifies all subscribers.
140
+ * @param {string} themeName - Name of the palette to apply (e.g. `"light"` or `"dark"`).
141
+ * @returns {Object} The updated `colors` object after the theme is applied.
142
+ */
143
+ export const setTheme = (themeName) => {
144
+ const palette = palettes[themeName];
145
+ if (palette) {
146
+ Object.keys(palette).forEach((key) => {
147
+ if (colors.hasOwnProperty(key)) {
148
+ colors[key] = palette[key];
149
+ }
150
+ });
151
+ }
152
+
153
+ // Update CSS variables
154
+ try {
155
+ const root = document.documentElement;
156
+ Object.keys(colors).forEach((key) => {
157
+ root.style.setProperty(`--color-${key}`, colors[key]);
158
+ });
159
+ } catch (e) {
160
+ // Silently fail in non-browser environments
161
+ }
162
+
163
+ // Save preference
164
+ try {
165
+ localStorage.setItem("fletbox-theme", themeName);
166
+ } catch (e) {
167
+ // Silently fail
168
+ }
169
+
170
+ notifyThemeChange();
171
+ return colors;
172
+ };
173
+
174
+ /**
175
+ * Returns the currently active theme name.
176
+ * Reads from `localStorage` first; falls back to the OS
177
+ * `prefers-color-scheme` media query; defaults to `"light"`.
178
+ * @returns {"light"|"dark"} The active theme name.
179
+ */
180
+ export const getTheme = () => {
181
+ try {
182
+ const savedTheme = localStorage.getItem("fletbox-theme");
183
+ if (savedTheme && (savedTheme === "light" || savedTheme === "dark")) {
184
+ return savedTheme;
185
+ }
186
+ return window.matchMedia("(prefers-color-scheme: dark)").matches
187
+ ? "dark"
188
+ : "light";
189
+ } catch (e) {
190
+ return "light";
191
+ }
192
+ };
193
+
194
+ /**
195
+ * Switches between `"light"` and `"dark"` themes.
196
+ * @returns {"light"|"dark"} The new theme name after toggling.
197
+ */
198
+ export const toggleTheme = () => {
199
+ const currentTheme = getTheme();
200
+ const newTheme = currentTheme === "light" ? "dark" : "light";
201
+ setTheme(newTheme);
202
+ return newTheme;
203
+ };
204
+
205
+ /**
206
+ * Applies the OS-level color scheme preference only when no theme has been
207
+ * explicitly saved to `localStorage`.
208
+ * @returns {"light"|"dark"} The theme that was applied or already saved.
209
+ */
210
+ export const applySystemTheme = () => {
211
+ try {
212
+ const savedTheme = localStorage.getItem("fletbox-theme");
213
+ if (!savedTheme) {
214
+ const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
215
+ .matches
216
+ ? "dark"
217
+ : "light";
218
+ setTheme(systemTheme);
219
+ return systemTheme;
220
+ }
221
+ return savedTheme;
222
+ } catch (e) {
223
+ return "light";
224
+ }
225
+ };
226
+
227
+ /**
228
+ * Registers a listener on the OS `prefers-color-scheme` media query and
229
+ * automatically calls {@link setTheme} whenever the system preference changes,
230
+ * but only when no theme has been explicitly saved to `localStorage`.
231
+ * @returns {function(): void} Cleanup function that removes the media-query listener.
232
+ */
233
+ export const watchSystemTheme = () => {
234
+ try {
235
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
236
+ const handler = (e) => {
237
+ const savedTheme = localStorage.getItem("fletbox-theme");
238
+ if (!savedTheme) {
239
+ const theme = e.matches ? "dark" : "light";
240
+ setTheme(theme);
241
+ }
242
+ };
243
+ mediaQuery.addEventListener("change", handler);
244
+ return () => mediaQuery.removeEventListener("change", handler);
245
+ } catch (e) {
246
+ return () => {};
247
+ }
248
+ };
249
+
250
+ /**
251
+ * Resolves a color by name from the active palette and optionally applies an
252
+ * alpha channel to hex values.
253
+ * @param {string} colorName - Key in the `colors` map (e.g. `"primary"`, `"border"`) or a raw CSS color string.
254
+ * @param {number} [opacity=1] - Alpha value between 0 and 1. Ignored for non-hex values.
255
+ * @returns {string} CSS color string (hex or `rgba(...)`).
256
+ * @example
257
+ * getColor("primary") // "#6366f1"
258
+ * getColor("primary", 0.5) // "rgba(99, 102, 241, 0.5)"
259
+ */
260
+ export const getColor = (colorName, opacity = 1) => {
261
+ const color = colors[colorName] || colorName;
262
+ if (opacity === 1) return color;
263
+
264
+ if (typeof color === "string" && color.startsWith("#")) {
265
+ const r = parseInt(color.slice(1, 3), 16);
266
+ const g = parseInt(color.slice(3, 5), 16);
267
+ const b = parseInt(color.slice(5, 7), 16);
268
+ return `rgba(${r}, ${g}, ${b}, ${opacity})`;
269
+ }
270
+ return color;
271
+ };
272
+
273
+ // ========== COMPONENTS ==========
274
+ /**
275
+ * Thin wrapper component that applies a theme on render.
276
+ * Pass `theme` to force a specific palette; omit it to leave the active theme unchanged.
277
+ * @param {Object} props
278
+ * @param {*} props.children - Child content to render.
279
+ * @param {string|null} [props.theme=null] - Theme name to apply (`"light"` or `"dark"`).
280
+ * @returns {*} The `children` value, unchanged.
281
+ */
282
+ export const ThemeProvider = ({ children, theme = null }) => {
283
+ if (theme) setTheme(theme);
284
+ return children;
285
+ };
286
+
287
+ /**
288
+ * Returns a hook-like object containing the full theme API.
289
+ * Useful for accessing the active colors and theme utilities from any component.
290
+ * @returns {{
291
+ * colors: Object,
292
+ * setTheme: function,
293
+ * getTheme: function,
294
+ * toggleTheme: function,
295
+ * getColor: function,
296
+ * applySystemTheme: function
297
+ * }}
298
+ */
299
+ export const useTheme = () => {
300
+ return {
301
+ colors,
302
+ setTheme,
303
+ getTheme,
304
+ toggleTheme,
305
+ getColor,
306
+ applySystemTheme,
307
+ };
308
+ };
309
+
310
+ // ========== INITIALIZATION (safe) ==========
311
+ const initTheme = () => {
312
+ try {
313
+ const savedTheme = localStorage.getItem("fletbox-theme");
314
+ if (savedTheme && (savedTheme === "light" || savedTheme === "dark")) {
315
+ setTheme(savedTheme);
316
+ } else {
317
+ const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
318
+ .matches
319
+ ? "dark"
320
+ : "light";
321
+ setTheme(systemTheme);
322
+ }
323
+ watchSystemTheme();
324
+ } catch (e) {
325
+ // If it fails, use default values
326
+ console.warn("Theme initialization failed, using defaults");
327
+ }
328
+ };
329
+
330
+ // Initialize only in browser environment
331
+ if (typeof window !== "undefined" && typeof document !== "undefined") {
332
+ initTheme();
333
+ }
334
+
335
+ export default {
336
+ colors,
337
+ palettes,
338
+ setTheme,
339
+ getTheme,
340
+ toggleTheme,
341
+ getColor,
342
+ subscribeTheme,
343
+ applySystemTheme,
344
+ watchSystemTheme,
345
+ ThemeProvider,
346
+ useTheme,
347
+ };