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,68 @@
|
|
|
1
|
+
// tools/transform.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generate CSS transform string
|
|
5
|
+
* @param {Object} options - Transform options
|
|
6
|
+
* @returns {string} CSS transform value
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* transform({ translate: [10, 20], rotate: 45, scale: 1.5 })
|
|
10
|
+
* // "translate(10px, 20px) rotate(45deg) scale(1.5)"
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* transform({ translateX: 10, translateY: 20, rotate: 45 })
|
|
14
|
+
* // "translateX(10px) translateY(20px) rotate(45deg)"
|
|
15
|
+
*/
|
|
16
|
+
export const transform = (options = {}) => {
|
|
17
|
+
const parts = [];
|
|
18
|
+
|
|
19
|
+
// Translate
|
|
20
|
+
if (options.translate) {
|
|
21
|
+
const [x, y] = options.translate;
|
|
22
|
+
parts.push(
|
|
23
|
+
`translate(${typeof x === "number" ? x + "px" : x}, ${typeof y === "number" ? y + "px" : y})`,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
if (options.translateX)
|
|
27
|
+
parts.push(
|
|
28
|
+
`translateX(${typeof options.translateX === "number" ? options.translateX + "px" : options.translateX})`,
|
|
29
|
+
);
|
|
30
|
+
if (options.translateY)
|
|
31
|
+
parts.push(
|
|
32
|
+
`translateY(${typeof options.translateY === "number" ? options.translateY + "px" : options.translateY})`,
|
|
33
|
+
);
|
|
34
|
+
if (options.translateZ)
|
|
35
|
+
parts.push(
|
|
36
|
+
`translateZ(${typeof options.translateZ === "number" ? options.translateZ + "px" : options.translateZ})`,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// Rotate
|
|
40
|
+
if (options.rotate) parts.push(`rotate(${options.rotate}deg)`);
|
|
41
|
+
if (options.rotateX) parts.push(`rotateX(${options.rotateX}deg)`);
|
|
42
|
+
if (options.rotateY) parts.push(`rotateY(${options.rotateY}deg)`);
|
|
43
|
+
if (options.rotateZ) parts.push(`rotateZ(${options.rotateZ}deg)`);
|
|
44
|
+
|
|
45
|
+
// Scale
|
|
46
|
+
if (options.scale) parts.push(`scale(${options.scale})`);
|
|
47
|
+
if (options.scaleX) parts.push(`scaleX(${options.scaleX})`);
|
|
48
|
+
if (options.scaleY) parts.push(`scaleY(${options.scaleY})`);
|
|
49
|
+
|
|
50
|
+
// Skew
|
|
51
|
+
if (options.skew) parts.push(`skew(${options.skew}deg)`);
|
|
52
|
+
if (options.skewX) parts.push(`skewX(${options.skewX}deg)`);
|
|
53
|
+
if (options.skewY) parts.push(`skewY(${options.skewY}deg)`);
|
|
54
|
+
|
|
55
|
+
// Matrix
|
|
56
|
+
if (options.matrix) parts.push(`matrix(${options.matrix.join(", ")})`);
|
|
57
|
+
if (options.matrix3d) parts.push(`matrix3d(${options.matrix3d.join(", ")})`);
|
|
58
|
+
|
|
59
|
+
// Perspective
|
|
60
|
+
if (options.perspective)
|
|
61
|
+
parts.push(
|
|
62
|
+
`perspective(${typeof options.perspective === "number" ? options.perspective + "px" : options.perspective})`,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
return parts.join(" ");
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export default transform;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// tools/transition.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generates a CSS `transition` shorthand string from a single options object or
|
|
5
|
+
* an array of options objects.
|
|
6
|
+
*
|
|
7
|
+
* Each options object can specify:
|
|
8
|
+
* - `property` — CSS property to animate (default: `"all"`).
|
|
9
|
+
* - `duration` — Duration in seconds (default: `0.3`).
|
|
10
|
+
* - `timing` — Timing function (default: `"ease"`).
|
|
11
|
+
* - `delay` — Delay in seconds before the transition starts (default: `0`).
|
|
12
|
+
*
|
|
13
|
+
* When an array is passed, each item is converted individually and the results
|
|
14
|
+
* are joined with `", "` to produce a multi-transition value.
|
|
15
|
+
*
|
|
16
|
+
* @param {Object|Array<Object>} options - Transition options or an array of them.
|
|
17
|
+
* @param {string} [options.property="all"] - CSS property to animate.
|
|
18
|
+
* @param {number} [options.duration=0.3] - Transition duration in seconds.
|
|
19
|
+
* @param {string} [options.timing="ease"] - CSS timing function.
|
|
20
|
+
* @param {number} [options.delay=0] - Delay before the transition starts, in seconds.
|
|
21
|
+
* @returns {string} CSS transition value.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* transition({ property: 'all', duration: 0.3, timing: 'ease', delay: 0.1 })
|
|
25
|
+
* // "all 0.3s ease 0.1s"
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* transition([
|
|
29
|
+
* { property: 'opacity', duration: 0.2 },
|
|
30
|
+
* { property: 'transform', duration: 0.3, timing: 'ease-out' }
|
|
31
|
+
* ])
|
|
32
|
+
* // "opacity 0.2s ease 0s, transform 0.3s ease-out 0s"
|
|
33
|
+
*/
|
|
34
|
+
export const transition = (options) => {
|
|
35
|
+
// Handle array of transitions — convert each and join
|
|
36
|
+
if (Array.isArray(options)) {
|
|
37
|
+
return options.map((opt) => transition(opt)).join(", ");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const property = options.property || "all";
|
|
41
|
+
const duration = options.duration || 0.3;
|
|
42
|
+
const timing = options.timing || "ease";
|
|
43
|
+
const delay = options.delay || 0;
|
|
44
|
+
|
|
45
|
+
return `${property} ${duration}s ${timing} ${delay}s`;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export default transition;
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// src/tools/useState.js
|
|
2
|
+
import { saveRam, getRam, subscribeRam } from "../services/RamStore.js";
|
|
3
|
+
|
|
4
|
+
// Store callbacks for each state key
|
|
5
|
+
const subscribers = new Map();
|
|
6
|
+
|
|
7
|
+
// 🔥 Store widgets by state key for automatic updates
|
|
8
|
+
const widgetsByKey = new Map();
|
|
9
|
+
|
|
10
|
+
// Global render function (set by runApp)
|
|
11
|
+
let globalRender = null;
|
|
12
|
+
let renderTimeout = null;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Sets the global render function used to trigger re-renders when state changes.
|
|
16
|
+
* Called by `runApp` to register the application's root render callback.
|
|
17
|
+
*
|
|
18
|
+
* @param {Function} renderFn - The root render function to invoke on state change.
|
|
19
|
+
* @returns {void}
|
|
20
|
+
*/
|
|
21
|
+
export const setGlobalRender = (renderFn) => {
|
|
22
|
+
globalRender = renderFn;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Schedules a debounced global re-render to prevent multiple renders firing in
|
|
27
|
+
* rapid succession within the same synchronous execution frame.
|
|
28
|
+
*
|
|
29
|
+
* @returns {void}
|
|
30
|
+
*/
|
|
31
|
+
const triggerRender = () => {
|
|
32
|
+
if (renderTimeout) clearTimeout(renderTimeout);
|
|
33
|
+
renderTimeout = setTimeout(() => {
|
|
34
|
+
if (globalRender) globalRender();
|
|
35
|
+
}, 0);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Registers a widget instance so it receives automatic prop updates whenever
|
|
40
|
+
* the state value associated with `key` changes.
|
|
41
|
+
*
|
|
42
|
+
* @param {string} key - The state key to watch.
|
|
43
|
+
* @param {Object} widget - The widget instance that exposes an `update` method.
|
|
44
|
+
* @param {string} propName - The prop name on the widget to update with the new value.
|
|
45
|
+
* @returns {void}
|
|
46
|
+
*/
|
|
47
|
+
export const registerWidget = (key, widget, propName) => {
|
|
48
|
+
if (!widgetsByKey.has(key)) {
|
|
49
|
+
widgetsByKey.set(key, new Set());
|
|
50
|
+
}
|
|
51
|
+
widgetsByKey.get(key).add({ widget, propName });
|
|
52
|
+
|
|
53
|
+
// Set initial value if the state already exists
|
|
54
|
+
const currentValue = getRam(key);
|
|
55
|
+
if (currentValue !== undefined && widget && widget.update) {
|
|
56
|
+
widget.update({ [propName]: currentValue });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Patch the widget's cleanup to also remove it from the registry when destroyed
|
|
60
|
+
const originalCleanup = widget._cleanup;
|
|
61
|
+
widget._cleanup = () => {
|
|
62
|
+
const widgets = widgetsByKey.get(key);
|
|
63
|
+
if (widgets) {
|
|
64
|
+
for (const item of widgets) {
|
|
65
|
+
if (item.widget === widget) {
|
|
66
|
+
widgets.delete(item);
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (originalCleanup) originalCleanup();
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Removes a specific widget from the automatic-update registry for a given key.
|
|
77
|
+
*
|
|
78
|
+
* @param {string} key - The state key the widget was registered under.
|
|
79
|
+
* @param {Object} widget - The widget instance to unregister.
|
|
80
|
+
* @returns {void}
|
|
81
|
+
*/
|
|
82
|
+
export const unregisterWidget = (key, widget) => {
|
|
83
|
+
const widgets = widgetsByKey.get(key);
|
|
84
|
+
if (widgets) {
|
|
85
|
+
for (const item of widgets) {
|
|
86
|
+
if (item.widget === widget) {
|
|
87
|
+
widgets.delete(item);
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* React-style state hook backed by the persistent RamStore.
|
|
96
|
+
*
|
|
97
|
+
* Creates (or reuses) a keyed state value that survives re-renders.
|
|
98
|
+
* Returns a reactive value object, a setter, and a subscribe function.
|
|
99
|
+
*
|
|
100
|
+
* @param {string} key - Unique key used to store and retrieve the state value.
|
|
101
|
+
* @param {*|Function} initialState - Initial value, or a factory function that returns it.
|
|
102
|
+
* @param {Object|null} [widget=null] - Optional widget to auto-update when state changes.
|
|
103
|
+
* @param {string|null} [propName=null] - The prop name on the widget to receive the new value.
|
|
104
|
+
* @returns {[Object, Function, Function]} Tuple of [reactiveValue, setState, subscribe].
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* const [count, setCount, onCount] = useState('counter', 0);
|
|
108
|
+
* setCount(count + 1);
|
|
109
|
+
* onCount((newVal) => console.log('Counter changed to', newVal));
|
|
110
|
+
*/
|
|
111
|
+
export const useState = (key, initialState, widget = null, propName = null) => {
|
|
112
|
+
// Initialize the state in RamStore if it does not exist yet
|
|
113
|
+
let currentValue = getRam(key);
|
|
114
|
+
if (currentValue === undefined || currentValue === null) {
|
|
115
|
+
currentValue =
|
|
116
|
+
typeof initialState === "function" ? initialState() : initialState;
|
|
117
|
+
saveRam(key, currentValue);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Set up a single RamStore subscriber per key
|
|
121
|
+
if (!subscribers.has(key)) {
|
|
122
|
+
subscribers.set(key, new Set());
|
|
123
|
+
|
|
124
|
+
// Listen to RamStore changes for this key
|
|
125
|
+
const unsubscribe = subscribeRam((changedKey, newValue, oldValue) => {
|
|
126
|
+
if (changedKey === key) {
|
|
127
|
+
// 🔥 Push the new value to all registered widgets for this key
|
|
128
|
+
const widgets = widgetsByKey.get(key);
|
|
129
|
+
if (widgets) {
|
|
130
|
+
widgets.forEach(({ widget, propName }) => {
|
|
131
|
+
if (widget && widget.update) {
|
|
132
|
+
widget.update({ [propName]: newValue });
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Notify all subscriber callbacks
|
|
138
|
+
const callbacks = subscribers.get(key);
|
|
139
|
+
callbacks.forEach((callback) => callback(newValue, oldValue));
|
|
140
|
+
|
|
141
|
+
// Trigger a global re-render
|
|
142
|
+
triggerRender();
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Track the unsubscribe handle for hot-reload cleanup
|
|
147
|
+
if (!window._useStateCleanups) window._useStateCleanups = [];
|
|
148
|
+
window._useStateCleanups.push(unsubscribe);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// 🔥 Register the widget for automatic updates if one was provided
|
|
152
|
+
if (widget && propName) {
|
|
153
|
+
registerWidget(key, widget, propName);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Updates the state value. Accepts either a new value directly or an updater
|
|
158
|
+
* function that receives the current value and returns the next value.
|
|
159
|
+
*
|
|
160
|
+
* @param {*|Function} newValueOrUpdater - New value or `(prevValue) => nextValue` updater.
|
|
161
|
+
* @returns {void}
|
|
162
|
+
*/
|
|
163
|
+
const setState = (newValueOrUpdater) => {
|
|
164
|
+
const oldValue = currentValue;
|
|
165
|
+
let newValue;
|
|
166
|
+
|
|
167
|
+
if (typeof newValueOrUpdater === "function") {
|
|
168
|
+
newValue = newValueOrUpdater(oldValue);
|
|
169
|
+
} else {
|
|
170
|
+
newValue = newValueOrUpdater;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (oldValue !== newValue) {
|
|
174
|
+
currentValue = newValue;
|
|
175
|
+
saveRam(key, currentValue);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Subscribes a callback to state changes for this key.
|
|
181
|
+
*
|
|
182
|
+
* @param {Function} callback - Called with `(newValue, oldValue)` on every change.
|
|
183
|
+
* @returns {Function} Unsubscribe function.
|
|
184
|
+
*/
|
|
185
|
+
const subscribe = (callback) => {
|
|
186
|
+
if (!subscribers.has(key)) {
|
|
187
|
+
subscribers.set(key, new Set());
|
|
188
|
+
}
|
|
189
|
+
subscribers.get(key).add(callback);
|
|
190
|
+
return () => {
|
|
191
|
+
if (subscribers.has(key)) {
|
|
192
|
+
subscribers.get(key).delete(callback);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// 🔥 Create a reactive value object so WidgetFactory can detect state bindings automatically
|
|
198
|
+
const reactiveValue = {
|
|
199
|
+
_isReactive: true,
|
|
200
|
+
_stateKey: key,
|
|
201
|
+
_value: currentValue,
|
|
202
|
+
valueOf() {
|
|
203
|
+
return this._value;
|
|
204
|
+
},
|
|
205
|
+
toString() {
|
|
206
|
+
return String(this._value);
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
// Return an array matching React's useState pattern: [value, setter, subscriber]
|
|
211
|
+
return [reactiveValue, setState, subscribe];
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Watches a state key and invokes the callback whenever its value changes.
|
|
216
|
+
* Useful for side effects that should not trigger a full re-render.
|
|
217
|
+
*
|
|
218
|
+
* @param {string} key - The state key to watch.
|
|
219
|
+
* @param {Function} callback - Called with `(newValue, oldValue)` on every change.
|
|
220
|
+
* @returns {Function} Unsubscribe function.
|
|
221
|
+
*/
|
|
222
|
+
export const useWatchState = (key, callback) => {
|
|
223
|
+
if (!subscribers.has(key)) {
|
|
224
|
+
subscribers.set(key, new Set());
|
|
225
|
+
}
|
|
226
|
+
subscribers.get(key).add(callback);
|
|
227
|
+
return () => subscribers.get(key).delete(callback);
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Cleans up all state subscriptions, widget registrations, and pending render
|
|
232
|
+
* timers. Should be called during hot-reload to reset the module to a clean state.
|
|
233
|
+
*
|
|
234
|
+
* @returns {void}
|
|
235
|
+
*/
|
|
236
|
+
export const cleanupUseState = () => {
|
|
237
|
+
if (window._useStateCleanups) {
|
|
238
|
+
window._useStateCleanups.forEach((cleanup) => {
|
|
239
|
+
if (typeof cleanup === "function") cleanup();
|
|
240
|
+
});
|
|
241
|
+
window._useStateCleanups = [];
|
|
242
|
+
}
|
|
243
|
+
subscribers.clear();
|
|
244
|
+
widgetsByKey.clear();
|
|
245
|
+
globalRender = null;
|
|
246
|
+
if (renderTimeout) clearTimeout(renderTimeout);
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Returns diagnostic statistics about the current state of the useState module.
|
|
251
|
+
* Useful for debugging subscription leaks or widget registration issues.
|
|
252
|
+
*
|
|
253
|
+
* @returns {{ subscribersCount: number, widgetsCount: number, globalRender: boolean }}
|
|
254
|
+
*/
|
|
255
|
+
export const getUseStateStats = () => {
|
|
256
|
+
return {
|
|
257
|
+
subscribersCount: subscribers.size,
|
|
258
|
+
widgetsCount: widgetsByKey.size,
|
|
259
|
+
globalRender: !!globalRender,
|
|
260
|
+
};
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// Re-export RamStore functions under convenience aliases
|
|
264
|
+
export {
|
|
265
|
+
saveRam as saveState,
|
|
266
|
+
getRam as getState,
|
|
267
|
+
subscribeRam as subscribeState,
|
|
268
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// tools/uuid.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generate a UUID v4
|
|
5
|
+
* @returns {string} UUID
|
|
6
|
+
*/
|
|
7
|
+
export const uuid = () => {
|
|
8
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
9
|
+
const r = (Math.random() * 16) | 0;
|
|
10
|
+
const v = c === "x" ? r : (r & 0x3) | 0x8;
|
|
11
|
+
return v.toString(16);
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generate a short ID (8 characters)
|
|
17
|
+
* @returns {string} Short ID
|
|
18
|
+
*/
|
|
19
|
+
export const shortId = () => {
|
|
20
|
+
return Math.random().toString(36).substring(2, 10);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Generate a numeric ID
|
|
25
|
+
* @param {number} length - Length of the ID
|
|
26
|
+
* @returns {string} Numeric ID
|
|
27
|
+
*/
|
|
28
|
+
export const numericId = (length = 6) => {
|
|
29
|
+
return Math.random()
|
|
30
|
+
.toString()
|
|
31
|
+
.slice(2, 2 + length);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Generate a timestamp-based ID
|
|
36
|
+
* @returns {string} Timestamp ID
|
|
37
|
+
*/
|
|
38
|
+
export const timestampId = () => {
|
|
39
|
+
return Date.now().toString(36) + Math.random().toString(36).substring(2, 6);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default { uuid, shortId, numericId, timestampId };
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
// src/modules/FletBox/core/TextInputValidator.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* TextInputValidator - Validates and sanitizes text inputs
|
|
5
|
+
*
|
|
6
|
+
* @description Provides functions to validate and sanitize inputs
|
|
7
|
+
* - onlyLetters() → only letters A-Z a-z (with accents and Ñ)
|
|
8
|
+
* - onlyNumbers() → only numbers 0-9
|
|
9
|
+
* - isEmail() → valid email format
|
|
10
|
+
* - onlyAlphanumeric() → letters and numbers
|
|
11
|
+
* - sanitize() → removes malicious code (XSS protection)
|
|
12
|
+
* - escapeHtml() → escapes HTML characters
|
|
13
|
+
* - safeText() → combines sanitize + escapeHtml
|
|
14
|
+
* - isSafe() → detects malicious patterns
|
|
15
|
+
* - filter() → custom pattern filter
|
|
16
|
+
* - limitLength() → limits text length
|
|
17
|
+
* - isOnlyLetters() → validates if only letters
|
|
18
|
+
* - isOnlyNumbers() → validates if only numbers
|
|
19
|
+
* - filterEmail() → filters email characters
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export const TextInputValidator = {
|
|
23
|
+
/**
|
|
24
|
+
* Filters only letters (A-Z, a-z, with accents and Ñ)
|
|
25
|
+
* @param {string} value - Text to filter
|
|
26
|
+
* @returns {string} Only letters
|
|
27
|
+
*/
|
|
28
|
+
onlyLetters: (value) => {
|
|
29
|
+
if (!value) return "";
|
|
30
|
+
return value.replace(/[^A-Za-záéíóúüñÁÉÍÓÚÜÑ\s]/g, "");
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Filters only numbers (0-9)
|
|
35
|
+
* @param {string} value - Text to filter
|
|
36
|
+
* @returns {string} Only numbers
|
|
37
|
+
*/
|
|
38
|
+
onlyNumbers: (value) => {
|
|
39
|
+
if (!value) return "";
|
|
40
|
+
return value.replace(/[^0-9]/g, "");
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Validates email format
|
|
45
|
+
* @param {string} value - Email to validate
|
|
46
|
+
* @returns {boolean} true if valid email
|
|
47
|
+
*/
|
|
48
|
+
isEmail: (value) => {
|
|
49
|
+
if (!value) return false;
|
|
50
|
+
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
|
51
|
+
return emailRegex.test(value);
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Filters email (only allowed characters in email)
|
|
56
|
+
* @param {string} value - Text to filter
|
|
57
|
+
* @returns {string} Filtered email
|
|
58
|
+
*/
|
|
59
|
+
filterEmail: (value) => {
|
|
60
|
+
if (!value) return "";
|
|
61
|
+
// Only allows: letters, numbers, @, ., _, -, +, %
|
|
62
|
+
return value.replace(/[^a-zA-Z0-9@._\-+%]/g, "");
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Filters only letters and numbers
|
|
67
|
+
* @param {string} value - Text to filter
|
|
68
|
+
* @returns {string} Only alphanumeric
|
|
69
|
+
*/
|
|
70
|
+
onlyAlphanumeric: (value) => {
|
|
71
|
+
if (!value) return "";
|
|
72
|
+
return value.replace(/[^a-zA-Z0-9]/g, "");
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Custom pattern filter
|
|
77
|
+
* @param {string} value - Text to filter
|
|
78
|
+
* @param {string} pattern - Allowed characters (without ^)
|
|
79
|
+
* @returns {string} Filtered text
|
|
80
|
+
*/
|
|
81
|
+
filter: (value, pattern) => {
|
|
82
|
+
if (!value) return "";
|
|
83
|
+
return value.replace(new RegExp(`[^${pattern}]`, "g"), "");
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Limits text length
|
|
88
|
+
* @param {string} value - Text to limit
|
|
89
|
+
* @param {number} maxLength - Maximum length
|
|
90
|
+
* @returns {string} Truncated text
|
|
91
|
+
*/
|
|
92
|
+
limitLength: (value, maxLength) => {
|
|
93
|
+
if (!value) return "";
|
|
94
|
+
return value.slice(0, maxLength);
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Validates if text contains only letters
|
|
99
|
+
* @param {string} value - Text to validate
|
|
100
|
+
* @returns {boolean} true if only letters
|
|
101
|
+
*/
|
|
102
|
+
isOnlyLetters: (value) => {
|
|
103
|
+
if (!value) return true;
|
|
104
|
+
return /^[A-Za-záéíóúüñÁÉÍÓÚÜÑ\s]+$/.test(value);
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Validates if text contains only numbers
|
|
109
|
+
* @param {string} value - Text to validate
|
|
110
|
+
* @returns {boolean} true if only numbers
|
|
111
|
+
*/
|
|
112
|
+
isOnlyNumbers: (value) => {
|
|
113
|
+
if (!value) return true;
|
|
114
|
+
return /^[0-9]+$/.test(value);
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Sanitizes text to prevent XSS and malicious code
|
|
119
|
+
* Removes/escapes: <script>, javascript:, onload=, onerror=, etc.
|
|
120
|
+
* @param {string} value - Text to sanitize
|
|
121
|
+
* @returns {string} Safe text
|
|
122
|
+
*/
|
|
123
|
+
sanitize: (value) => {
|
|
124
|
+
if (!value) return "";
|
|
125
|
+
|
|
126
|
+
let safe = String(value);
|
|
127
|
+
|
|
128
|
+
// Remove <script> tags and content
|
|
129
|
+
safe = safe.replace(
|
|
130
|
+
/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
|
|
131
|
+
"",
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
// Remove javascript: protocol
|
|
135
|
+
safe = safe.replace(/javascript:/gi, "");
|
|
136
|
+
|
|
137
|
+
// Remove onload=, onerror=, onclick=, etc.
|
|
138
|
+
safe = safe.replace(/on\w+\s*=/gi, "");
|
|
139
|
+
|
|
140
|
+
// Remove alert(), confirm(), prompt()
|
|
141
|
+
safe = safe.replace(/alert\s*\(/gi, "");
|
|
142
|
+
safe = safe.replace(/confirm\s*\(/gi, "");
|
|
143
|
+
safe = safe.replace(/prompt\s*\(/gi, "");
|
|
144
|
+
|
|
145
|
+
// Remove eval()
|
|
146
|
+
safe = safe.replace(/eval\s*\(/gi, "");
|
|
147
|
+
|
|
148
|
+
// Remove document.write
|
|
149
|
+
safe = safe.replace(/document\.write/gi, "");
|
|
150
|
+
|
|
151
|
+
// Remove <iframe>
|
|
152
|
+
safe = safe.replace(
|
|
153
|
+
/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi,
|
|
154
|
+
"",
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
// Remove <object>, <embed>
|
|
158
|
+
safe = safe.replace(/<\/?(object|embed)[^>]*>/gi, "");
|
|
159
|
+
|
|
160
|
+
// Remove <img> with onerror
|
|
161
|
+
safe = safe.replace(/<img[^>]+onerror\s*=[^>]*>/gi, "");
|
|
162
|
+
|
|
163
|
+
// Remove <link> with javascript
|
|
164
|
+
safe = safe.replace(
|
|
165
|
+
/<link[^>]+href\s*=\s*['"]javascript:[^'"]*['"][^>]*>/gi,
|
|
166
|
+
"",
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
// Remove <meta> with refresh or http-equiv
|
|
170
|
+
safe = safe.replace(/<meta[^>]+(refresh|http-equiv)[^>]*>/gi, "");
|
|
171
|
+
|
|
172
|
+
// Remove &{...} expressions (template injection)
|
|
173
|
+
safe = safe.replace(/&\{[^}]*\}/g, "");
|
|
174
|
+
|
|
175
|
+
// Remove {{...}} expressions (template injection)
|
|
176
|
+
safe = safe.replace(/\{\{[^}]*\}\}/g, "");
|
|
177
|
+
|
|
178
|
+
// Remove backticks with expressions
|
|
179
|
+
safe = safe.replace(/\${[^}]*}/g, "");
|
|
180
|
+
|
|
181
|
+
return safe;
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Escapes HTML special characters
|
|
186
|
+
* Converts < > & " ' to HTML entities
|
|
187
|
+
* @param {string} value - Text to escape
|
|
188
|
+
* @returns {string} Escaped text safe for innerHTML
|
|
189
|
+
*/
|
|
190
|
+
escapeHtml: (value) => {
|
|
191
|
+
if (!value) return "";
|
|
192
|
+
|
|
193
|
+
const htmlEntities = {
|
|
194
|
+
"&": "&",
|
|
195
|
+
"<": "<",
|
|
196
|
+
">": ">",
|
|
197
|
+
'"': """,
|
|
198
|
+
"'": "'",
|
|
199
|
+
"/": "/",
|
|
200
|
+
"`": "`",
|
|
201
|
+
"=": "=",
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
return String(value).replace(/[&<>"'/`=]/g, (char) => htmlEntities[char]);
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Complete sanitization for user input
|
|
209
|
+
* Combines sanitize() + escapeHtml()
|
|
210
|
+
* @param {string} value - Text to sanitize
|
|
211
|
+
* @returns {string} Completely safe text
|
|
212
|
+
*/
|
|
213
|
+
safeText: (value) => {
|
|
214
|
+
if (!value) return "";
|
|
215
|
+
let safe = TextInputValidator.sanitize(value);
|
|
216
|
+
safe = TextInputValidator.escapeHtml(safe);
|
|
217
|
+
return safe;
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Validates if text contains any malicious patterns
|
|
222
|
+
* @param {string} value - Text to check
|
|
223
|
+
* @returns {boolean} true if safe, false if contains malicious code
|
|
224
|
+
*/
|
|
225
|
+
isSafe: (value) => {
|
|
226
|
+
if (!value) return true;
|
|
227
|
+
|
|
228
|
+
const maliciousPatterns = [
|
|
229
|
+
/<script\b/i,
|
|
230
|
+
/javascript:/i,
|
|
231
|
+
/on\w+\s*=/i,
|
|
232
|
+
/alert\s*\(/i,
|
|
233
|
+
/confirm\s*\(/i,
|
|
234
|
+
/prompt\s*\(/i,
|
|
235
|
+
/eval\s*\(/i,
|
|
236
|
+
/document\.write/i,
|
|
237
|
+
/<iframe\b/i,
|
|
238
|
+
/<object\b/i,
|
|
239
|
+
/<embed\b/i,
|
|
240
|
+
/<link[^>]+javascript:/i,
|
|
241
|
+
/<meta[^>]+refresh/i,
|
|
242
|
+
/&\{[^}]*\}/,
|
|
243
|
+
/\{\{[^}]*\}\}/,
|
|
244
|
+
/\${[^}]*}/,
|
|
245
|
+
];
|
|
246
|
+
|
|
247
|
+
for (const pattern of maliciousPatterns) {
|
|
248
|
+
if (pattern.test(value)) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return true;
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
export default TextInputValidator;
|