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.
- package/LICENSE +21 -0
- package/README.md +190 -2
- package/bin/cli.js +394 -0
- package/bin/commands/buildAndroid.js +200 -0
- package/bin/commands/buildIOS.js +237 -0
- package/bin/commands/create.js +406 -0
- package/bin/commands/createBundle.js +375 -0
- package/bin/commands/createComponent.js +115 -0
- package/bin/commands/createScreen.js +161 -0
- package/bin/commands/killServer.js +107 -0
- package/bin/commands/package.js +131 -0
- package/bin/commands/release.js +189 -0
- package/bin/commands/runBundle.js +62 -0
- package/bin/commands/runCloudflare.js +89 -0
- package/bin/commands/runNative.js +109 -0
- package/bin/commands/runServer.js +390 -0
- package/bin/ota/loader.js +361 -0
- package/bin/utils/bundleConfig.js +178 -0
- package/bin/utils/capacitor.js +137 -0
- package/bin/utils/colors.js +215 -0
- package/bin/utils/helpers.js +80 -0
- package/bin/utils/spinner.js +96 -0
- package/bin/utils/templates.js +1824 -0
- package/bin/utils/version.js +108 -0
- package/package.json +58 -4
- package/src/animations/AnimatedBox.js +607 -0
- package/src/animations/AnimatedText.js +105 -0
- package/src/animations/MatrixRain.js +220 -0
- package/src/animations/ParallaxBox.js +369 -0
- package/src/animations/animate.js +332 -0
- package/src/animations/index.js +25 -0
- package/src/app.js +41 -0
- package/src/assets/icon-1024-maskable.png +0 -0
- package/src/assets/icon-192-maskable.png +0 -0
- package/src/assets/icon-192.png +0 -0
- package/src/assets/icon-512-maskable.png +0 -0
- package/src/assets/icon-512.png +0 -0
- package/src/assets/logo.png +0 -0
- package/src/core/App.js +44 -0
- package/src/core/hmr-client.js +142 -0
- package/src/core/index.js +34 -0
- package/src/core/pwa.js +100 -0
- package/src/core/runApp.js +430 -0
- package/src/create-index.sh +237 -0
- package/src/fonts/MaterialIcons-Regular.woff2 +0 -0
- package/src/fonts/icons.css +23 -0
- package/src/index.d.ts +1930 -0
- package/src/index.js +288 -0
- package/src/navigations/AdaptiveScaffold.js +149 -0
- package/src/navigations/AppBar.js +331 -0
- package/src/navigations/BottomNavigation.js +298 -0
- package/src/navigations/CollapsibleSideBar.js +139 -0
- package/src/navigations/Drawer.js +333 -0
- package/src/navigations/DrawerItem.js +260 -0
- package/src/navigations/Router.js +475 -0
- package/src/navigations/Scaffold.js +537 -0
- package/src/navigations/Tabs.js +384 -0
- package/src/navigations/index.js +57 -0
- package/src/services/RamStore.js +213 -0
- package/src/services/Session.js +310 -0
- package/src/services/Storage.js +309 -0
- package/src/services/http.js +350 -0
- package/src/services/index.js +57 -0
- package/src/tools/animation.js +42 -0
- package/src/tools/array.js +95 -0
- package/src/tools/border.js +28 -0
- package/src/tools/clipboard.js +104 -0
- package/src/tools/color.js +329 -0
- package/src/tools/createList.js +215 -0
- package/src/tools/delay.js +55 -0
- package/src/tools/device.js +138 -0
- package/src/tools/dict.js +297 -0
- package/src/tools/dimensions.js +118 -0
- package/src/tools/filter.js +41 -0
- package/src/tools/flex.js +26 -0
- package/src/tools/gradient.js +34 -0
- package/src/tools/grid.js +57 -0
- package/src/tools/index.js +38 -0
- package/src/tools/mapList.js +107 -0
- package/src/tools/margin.js +93 -0
- package/src/tools/memo.js +60 -0
- package/src/tools/os.js +104 -0
- package/src/tools/padding.js +93 -0
- package/src/tools/print.js +69 -0
- package/src/tools/random.js +299 -0
- package/src/tools/ref.js +70 -0
- package/src/tools/rgba.js +4 -0
- package/src/tools/shadow.js +23 -0
- package/src/tools/string.js +85 -0
- package/src/tools/time.js +674 -0
- package/src/tools/transform.js +68 -0
- package/src/tools/transition.js +48 -0
- package/src/tools/useState.js +268 -0
- package/src/tools/uuid.js +42 -0
- package/src/utils/TextInputValidator.js +256 -0
- package/src/utils/composeUpdate.js +65 -0
- package/src/utils/getWidgetProps.js +35 -0
- package/src/utils/index.js +76 -0
- package/src/utils/markdownParser.js +368 -0
- package/src/utils/mediaTime.js +79 -0
- package/src/utils/navigation.js +263 -0
- package/src/utils/scriptInjector.js +73 -0
- package/src/utils/stackPosition.js +31 -0
- package/src/utils/stopWebRefresh.js +77 -0
- package/src/utils/styleInjector.js +38 -0
- package/src/utils/syntaxHighlight.js +339 -0
- package/src/utils/themes.js +347 -0
- package/src/utils/units.js +87 -0
- package/src/utils/visualEffects.js +169 -0
- package/src/widget-factory/addChildren.js +114 -0
- package/src/widget-factory/assignProps.js +256 -0
- package/src/widget-factory/createWidget.js +28 -0
- package/src/widget-factory/effects.js +132 -0
- package/src/widget-factory/index.js +7 -0
- package/src/widget-factory/lifecycle.js +210 -0
- package/src/widget-factory/makeParentable.js +89 -0
- package/src/widget-factory/pixTool.js +40 -0
- package/src/widget-factory/processProps.js +122 -0
- package/src/widget-factory/reactivity.js +98 -0
- package/src/widget-factory/remTool.js +34 -0
- package/src/widget-factory/stackPosition.js +87 -0
- package/src/widget-factory/stringTool.js +26 -0
- package/src/widget-factory/tools.js +249 -0
- package/src/widget-factory/translateProps.js +1049 -0
- package/src/widget-factory/warnings.js +284 -0
- package/src/widget-factory/widgetFactory.js +200 -0
- package/src/widgets/Accordion.js +562 -0
- package/src/widgets/AdMob.js +315 -0
- package/src/widgets/AdSense.js +268 -0
- package/src/widgets/AlertDialog.js +162 -0
- package/src/widgets/Audio.js +311 -0
- package/src/widgets/Avatar.js +210 -0
- package/src/widgets/Badge.js +189 -0
- package/src/widgets/BottomSheet.js +287 -0
- package/src/widgets/Button.js +247 -0
- package/src/widgets/Card.js +33 -0
- package/src/widgets/Carousel.js +301 -0
- package/src/widgets/Chart.js +632 -0
- package/src/widgets/Checkbox.js +106 -0
- package/src/widgets/Chip.js +153 -0
- package/src/widgets/CircularBar.js +352 -0
- package/src/widgets/CircularChart.js +549 -0
- package/src/widgets/CodeViewer.js +236 -0
- package/src/widgets/Column.js +29 -0
- package/src/widgets/Container.js +31 -0
- package/src/widgets/DataTable.js +257 -0
- package/src/widgets/Divider.js +93 -0
- package/src/widgets/DraggBox.js +115 -0
- package/src/widgets/Dropdown.js +505 -0
- package/src/widgets/DroppBox.js +180 -0
- package/src/widgets/FloatingActionButton.js +91 -0
- package/src/widgets/GridView.js +20 -0
- package/src/widgets/Icon.js +33 -0
- package/src/widgets/Image.js +15 -0
- package/src/widgets/Input.js +565 -0
- package/src/widgets/Inspector.js +254 -0
- package/src/widgets/InstallButton.js +110 -0
- package/src/widgets/ListTile.js +224 -0
- package/src/widgets/ListView.js +503 -0
- package/src/widgets/Markdown.js +1236 -0
- package/src/widgets/Modal.js +496 -0
- package/src/widgets/Pagination.js +195 -0
- package/src/widgets/ProgressBar.js +178 -0
- package/src/widgets/QRCode.js +467 -0
- package/src/widgets/Radio.js +132 -0
- package/src/widgets/Rating.js +261 -0
- package/src/widgets/Row.js +29 -0
- package/src/widgets/Skeleton.js +243 -0
- package/src/widgets/Slider.js +527 -0
- package/src/widgets/SnackBar.js +227 -0
- package/src/widgets/Stack.js +29 -0
- package/src/widgets/Stepper.js +296 -0
- package/src/widgets/Switch.js +102 -0
- package/src/widgets/Text.js +98 -0
- package/src/widgets/Tooltip.js +267 -0
- package/src/widgets/TreeView.js +370 -0
- package/src/widgets/Video.js +92 -0
- 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, "&")
|
|
333
|
+
.replace(/</g, "<")
|
|
334
|
+
.replace(/>/g, ">")
|
|
335
|
+
.replace(/"/g, """)
|
|
336
|
+
.replace(/'/g, "'");
|
|
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
|
+
};
|