flet-box 1.0.4 → 1.0.5
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/README.md +10 -0
- package/bin/cli.js +15 -0
- package/bin/commands/checkApi.js +761 -0
- package/bin/commands/create.js +77 -5
- package/bin/utils/dtsProps.js +191 -0
- package/bin/utils/moduleExports.js +165 -0
- package/bin/utils/neutralise.js +64 -0
- package/bin/utils/templates.js +366 -27
- package/docs/CONTRIBUTING.md +156 -0
- package/docs/README.md +68 -0
- package/docs/arquitectura.md +164 -0
- package/docs/cli/README.md +542 -0
- package/docs/guides/README.md +33 -0
- package/docs/guides/app-templates.md +197 -0
- package/docs/guides/compatibility.md +95 -0
- package/docs/guides/embedding.md +284 -0
- package/docs/guides/first-app.md +208 -0
- package/docs/guides/frontend-services.md +171 -0
- package/docs/guides/minification-and-protection.md +260 -0
- package/docs/guides/mobile-and-platforms.md +439 -0
- package/docs/guides/online-updates.md +196 -0
- package/docs/guides/router.md +282 -0
- package/docs/guides/state.md +283 -0
- package/docs/guides/syntax-philosophy.md +150 -0
- package/docs/guides/utilities.md +241 -0
- package/docs/guides/widget-structure.md +163 -0
- package/docs/server/README.md +68 -0
- package/docs/server/START_HERE.md +121 -0
- package/docs/server/api.md +225 -0
- package/docs/server/data-services.md +300 -0
- package/docs/server/deployment.md +113 -0
- package/docs/server/security.md +196 -0
- package/docs/services/README.md +79 -0
- package/docs/services/RamStore.md +84 -0
- package/docs/services/Session.md +84 -0
- package/docs/services/Storage.md +88 -0
- package/docs/services/http.md +86 -0
- package/docs/tools/README.md +138 -0
- package/docs/tools/animation.md +87 -0
- package/docs/tools/async-ids-log.md +80 -0
- package/docs/tools/device.md +132 -0
- package/docs/tools/introspection.md +67 -0
- package/docs/tools/lists.md +96 -0
- package/docs/tools/markdown.md +79 -0
- package/docs/tools/reactivity.md +288 -0
- package/docs/tools/state.md +103 -0
- package/docs/tools/styling.md +92 -0
- package/docs/tools/text-and-time.md +89 -0
- package/docs/tools/theme.md +102 -0
- package/docs/tools/validation.md +75 -0
- package/docs/tools/visual-effects.md +84 -0
- package/docs/widget/Accordion.md +144 -0
- package/docs/widget/AdMob.md +144 -0
- package/docs/widget/AdSense.md +133 -0
- package/docs/widget/AdaptiveScaffold.md +105 -0
- package/docs/widget/AlertDialog.md +103 -0
- package/docs/widget/AnimatedBox.md +130 -0
- package/docs/widget/AnimatedText.md +123 -0
- package/docs/widget/AppBar.md +118 -0
- package/docs/widget/Audio.md +135 -0
- package/docs/widget/Avatar.md +119 -0
- package/docs/widget/Badge.md +128 -0
- package/docs/widget/BottomNavigation.md +116 -0
- package/docs/widget/BottomSheet.md +125 -0
- package/docs/widget/Button.md +137 -0
- package/docs/widget/COMMON_PROPS.md +150 -0
- package/docs/widget/Card.md +125 -0
- package/docs/widget/Carousel.md +151 -0
- package/docs/widget/Chart.md +151 -0
- package/docs/widget/Checkbox.md +118 -0
- package/docs/widget/Chip.md +126 -0
- package/docs/widget/CircularBar.md +168 -0
- package/docs/widget/CircularChart.md +164 -0
- package/docs/widget/CodeViewer.md +130 -0
- package/docs/widget/CollapsibleSideBar.md +112 -0
- package/docs/widget/Column.md +129 -0
- package/docs/widget/Container.md +136 -0
- package/docs/widget/DataTable.md +150 -0
- package/docs/widget/Divider.md +109 -0
- package/docs/widget/DraggBox.md +129 -0
- package/docs/widget/Drawer.md +135 -0
- package/docs/widget/DrawerItem.md +98 -0
- package/docs/widget/Dropdown.md +139 -0
- package/docs/widget/DroppBox.md +152 -0
- package/docs/widget/FloatingActionButton.md +120 -0
- package/docs/widget/GridView.md +140 -0
- package/docs/widget/Icon.md +117 -0
- package/docs/widget/Image.md +125 -0
- package/docs/widget/Input.md +151 -0
- package/docs/widget/Inspector.md +92 -0
- package/docs/widget/InstallButton.md +128 -0
- package/docs/widget/ListTile.md +132 -0
- package/docs/widget/ListView.md +138 -0
- package/docs/widget/Markdown.md +175 -0
- package/docs/widget/MatrixRain.md +122 -0
- package/docs/widget/Modal.md +129 -0
- package/docs/widget/Pagination.md +135 -0
- package/docs/widget/ParallaxBox.md +128 -0
- package/docs/widget/ProgressBar.md +130 -0
- package/docs/widget/QRCode.md +117 -0
- package/docs/widget/README.md +188 -0
- package/docs/widget/Radio.md +123 -0
- package/docs/widget/Rating.md +131 -0
- package/docs/widget/Row.md +131 -0
- package/docs/widget/START_HERE.md +146 -0
- package/docs/widget/Scaffold.md +152 -0
- package/docs/widget/Skeleton.md +130 -0
- package/docs/widget/Slider.md +146 -0
- package/docs/widget/SnackBar.md +102 -0
- package/docs/widget/Stack.md +128 -0
- package/docs/widget/Stepper.md +145 -0
- package/docs/widget/Switch.md +118 -0
- package/docs/widget/Tabs.md +124 -0
- package/docs/widget/Text.md +133 -0
- package/docs/widget/Tooltip.md +145 -0
- package/docs/widget/TreeView.md +176 -0
- package/docs/widget/Video.md +127 -0
- package/docs/widget/_template.md +61 -0
- package/package.json +3 -2
- package/src/animations/animate.js +180 -70
- package/src/animations/index.js +1 -0
- package/src/create-index.sh +1 -1
- package/src/index.d.ts +45 -22
- package/src/index.js +1 -0
- package/src/navigations/AdaptiveScaffold.js +11 -2
- package/src/navigations/BottomNavigation.js +10 -3
- package/src/navigations/Tabs.js +6 -2
- package/src/tools/useState.js +54 -63
- package/src/widget-factory/assignProps.js +23 -1
- package/src/widget-factory/processProps.js +10 -3
- package/src/widget-factory/reactiveValue.js +43 -18
- package/src/widget-factory/translateProps.js +777 -901
- package/src/widgets/AlertDialog.js +7 -5
- package/src/widgets/Checkbox.js +9 -7
- package/src/widgets/CircularBar.js +12 -1
- package/src/widgets/FloatingActionButton.js +4 -7
- package/src/widgets/Image.js +5 -7
- package/src/widgets/Input.js +92 -0
- package/src/widgets/ListView.js +73 -29
- package/src/widgets/Pagination.js +6 -3
- package/src/widgets/ProgressBar.js +16 -13
- package/src/widgets/Radio.js +6 -7
- package/src/widgets/Slider.js +23 -4
- package/src/widgets/Stack.js +36 -3
- package/src/widgets/Stepper.js +4 -3
- package/src/widgets/Switch.js +3 -4
- package/src/widgets/TreeView.js +0 -1
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# Routing with FletBox
|
|
2
|
+
|
|
3
|
+
The FletBox router changes the visible screen without rebuilding a whole HTML page. It supports fixed paths, dynamic parameters, query values, browser history, and route subscriptions.
|
|
4
|
+
|
|
5
|
+
## Create routes
|
|
6
|
+
|
|
7
|
+
A route map connects URL paths with widgets or screen functions:
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
import { Container, Text, runApp } from "flet-box";
|
|
11
|
+
|
|
12
|
+
const HomeScreen = () => Container({
|
|
13
|
+
padding: 24,
|
|
14
|
+
child: Text({ text: "Home" }),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const AboutScreen = () => Container({
|
|
18
|
+
padding: 24,
|
|
19
|
+
child: Text({ text: "About" }),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const routes = {
|
|
23
|
+
"/": HomeScreen,
|
|
24
|
+
"/about": AboutScreen,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
runApp(HomeScreen, "root", true, routes);
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
When routes are passed to `runApp`, the router is initialized automatically.
|
|
31
|
+
|
|
32
|
+
## Navigate with `goTo`
|
|
33
|
+
|
|
34
|
+
```javascript
|
|
35
|
+
import { Button, goTo } from "flet-box";
|
|
36
|
+
|
|
37
|
+
Button({
|
|
38
|
+
text: "Open About",
|
|
39
|
+
onPress: () => goTo("/about"),
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The browser URL and the current route are updated together.
|
|
44
|
+
|
|
45
|
+
## Use a dynamic parameter
|
|
46
|
+
|
|
47
|
+
Define a parameter with `:name`:
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
const routes = {
|
|
51
|
+
"/users/:id": UserScreen,
|
|
52
|
+
};
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Read it inside the screen:
|
|
56
|
+
|
|
57
|
+
```javascript
|
|
58
|
+
import { Text, useParams } from "flet-box";
|
|
59
|
+
|
|
60
|
+
const UserScreen = () => {
|
|
61
|
+
const { id } = useParams();
|
|
62
|
+
return Text({ text: `User: ${id}` });
|
|
63
|
+
};
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Navigate to it:
|
|
67
|
+
|
|
68
|
+
```javascript
|
|
69
|
+
goTo("/users/42");
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The value is decoded and available as `id: "42"`.
|
|
73
|
+
|
|
74
|
+
## Query parameters
|
|
75
|
+
|
|
76
|
+
Navigate with a query string:
|
|
77
|
+
|
|
78
|
+
```javascript
|
|
79
|
+
goTo("/search?term=widgets", { page: 2 });
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Read query values:
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
import { Text, useQueryParams } from "flet-box";
|
|
86
|
+
|
|
87
|
+
const SearchScreen = () => {
|
|
88
|
+
const { term, page } = useQueryParams();
|
|
89
|
+
return Text({ text: `Search: ${term}, page ${page}` });
|
|
90
|
+
};
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
You can also build URLs safely:
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
import { buildUrl } from "flet-box";
|
|
97
|
+
|
|
98
|
+
const url = buildUrl(
|
|
99
|
+
"/users/:id",
|
|
100
|
+
{ id: 42 },
|
|
101
|
+
{ tab: "profile" },
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// /users/42?tab=profile
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Go back and forward
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
import { goBack, goForward } from "flet-box";
|
|
111
|
+
|
|
112
|
+
goBack();
|
|
113
|
+
goForward();
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`replace()` changes the current URL without adding another entry to the application history:
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
import { replace } from "flet-box";
|
|
120
|
+
|
|
121
|
+
replace("/login");
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Read the current route
|
|
125
|
+
|
|
126
|
+
```javascript
|
|
127
|
+
import {
|
|
128
|
+
getCurrentPath,
|
|
129
|
+
getCurrentRoute,
|
|
130
|
+
getCurrentRouteConfig,
|
|
131
|
+
isActive,
|
|
132
|
+
} from "flet-box";
|
|
133
|
+
|
|
134
|
+
const path = getCurrentPath();
|
|
135
|
+
const route = getCurrentRoute();
|
|
136
|
+
const config = getCurrentRouteConfig();
|
|
137
|
+
const isHome = isActive("/", true);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`getCurrentRouteConfig()` returns the route, params, query, and path together.
|
|
141
|
+
|
|
142
|
+
## Subscribe to navigation
|
|
143
|
+
|
|
144
|
+
```javascript
|
|
145
|
+
import { subscribe } from "flet-box";
|
|
146
|
+
|
|
147
|
+
const unsubscribe = subscribe((route, params, query) => {
|
|
148
|
+
console.log({ route, params, query });
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Call when the integration is destroyed.
|
|
152
|
+
unsubscribe();
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
The router also listens to browser back and forward events.
|
|
156
|
+
|
|
157
|
+
## Router with `Scaffold`
|
|
158
|
+
|
|
159
|
+
For larger applications, combine routes with `Scaffold`:
|
|
160
|
+
|
|
161
|
+
```javascript
|
|
162
|
+
import { Scaffold, Text, runApp } from "flet-box";
|
|
163
|
+
|
|
164
|
+
const routes = {
|
|
165
|
+
"/": () => Text({ text: "Home" }),
|
|
166
|
+
"/settings": () => Text({ text: "Settings" }),
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const App = () => Scaffold({
|
|
170
|
+
routes,
|
|
171
|
+
body: routes,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
runApp(App, "root", true, routes);
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Use `AppBar`, `DrawerItem`, `BottomNavigation`, or `Button` to call `goTo()`.
|
|
178
|
+
|
|
179
|
+
## Deploy under a subpath
|
|
180
|
+
|
|
181
|
+
A router URL is app-relative: `"/settings"`. Static hosts such as GitHub Pages
|
|
182
|
+
serve a project site under a prefix — `https://user.github.io/my-app/` — so the
|
|
183
|
+
browser sees `/my-app/settings` while the route map only knows `/settings`. The
|
|
184
|
+
router handles that prefix on its own. It reads the deployment base when it
|
|
185
|
+
initializes and adds it back to every URL it writes, so `goTo()` and the
|
|
186
|
+
back/forward buttons stay inside `/my-app/` instead of jumping to the host root.
|
|
187
|
+
|
|
188
|
+
The base is resolved in this order:
|
|
189
|
+
|
|
190
|
+
1. The explicit option, which wins over everything:
|
|
191
|
+
`initRouter(routes, path, { base: "/my-app" })`, or
|
|
192
|
+
`Scaffold({ routes, basePath: "/my-app" })`.
|
|
193
|
+
2. `window.__FLETBOX_BASE__`, when the live URL actually sits under it. Set it
|
|
194
|
+
in the page before the app loads: `<script>window.__FLETBOX_BASE__ = "/my-app/";</script>`.
|
|
195
|
+
The guard is what lets one build run both at `/my-app/` on Pages and at the
|
|
196
|
+
origin root inside a Capacitor WebView.
|
|
197
|
+
3. Auto-detection: the shortest leading segment of the URL whose remainder
|
|
198
|
+
matches a registered route becomes the base (`/my-app/settings` → base
|
|
199
|
+
`/my-app`, route `/settings`). A root-hosted app resolves to `""` and its
|
|
200
|
+
URLs are untouched.
|
|
201
|
+
|
|
202
|
+
Read the result with `getBasePath()` — it returns `"/my-app"` or `""`. Use it
|
|
203
|
+
for anything the router does not touch, such as fetching content files, and
|
|
204
|
+
compute it at call time rather than at import time, because the base only
|
|
205
|
+
exists once the router has initialized:
|
|
206
|
+
|
|
207
|
+
```javascript
|
|
208
|
+
import { getBasePath } from "flet-box";
|
|
209
|
+
|
|
210
|
+
const assetUrl = (path) =>
|
|
211
|
+
new URL(path, `${window.location.origin}${getBasePath()}/`).href;
|
|
212
|
+
// https://user.github.io/my-app/docs/guides/router.md · http://localhost:8000/docs/…
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Deep links on a static host
|
|
216
|
+
|
|
217
|
+
Being able to *write* `/my-app/settings` is only half of it: a static host has
|
|
218
|
+
no such file, so a visitor who opens or reloads that URL gets a 404. GitHub
|
|
219
|
+
Pages answers a missing path by serving the project's `404.html` **under the
|
|
220
|
+
requested URL**, which is exactly the hook an SPA needs — make `404.html` a
|
|
221
|
+
copy of `index.html` and the router will read the real path from the address
|
|
222
|
+
bar and render it.
|
|
223
|
+
|
|
224
|
+
Generate it at build time so it never drifts from the real entry point. Two
|
|
225
|
+
details make the copy work at any depth, and both are easy to get wrong:
|
|
226
|
+
|
|
227
|
+
- **Absolute references.** A page served for `/my-app/docs/guides/router.md`
|
|
228
|
+
resolves `"./loader.js"` against `/my-app/docs/guides/`, not the app root.
|
|
229
|
+
Rewrite every relative reference, including the import map and the
|
|
230
|
+
`serviceWorker.register()` path.
|
|
231
|
+
- **Declare the base.** A deep URL is not always recognizable as a route (an
|
|
232
|
+
unknown path matches nothing), so publish `window.__FLETBOX_BASE__` in the
|
|
233
|
+
fallback page.
|
|
234
|
+
|
|
235
|
+
A build script does the whole job in a few lines:
|
|
236
|
+
|
|
237
|
+
```javascript
|
|
238
|
+
// scripts/spa-fallback.mjs — run after the bundle is written.
|
|
239
|
+
const base = "/my-app/";
|
|
240
|
+
const html = await readFile("www/index.html", "utf8");
|
|
241
|
+
|
|
242
|
+
const fallback = html
|
|
243
|
+
.replace(/(["'])\.\//g, `$1${base}`)
|
|
244
|
+
.replace(
|
|
245
|
+
/register\((["'])service-worker\.js\1\)/g,
|
|
246
|
+
`register($1${base}service-worker.js$1)`,
|
|
247
|
+
)
|
|
248
|
+
.replace(
|
|
249
|
+
"</head>",
|
|
250
|
+
` <script>window.__FLETBOX_BASE__ = "${base}";</script>\n </head>`,
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
await writeFile("www/404.html", fallback, "utf8");
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Keep the service worker in the same scheme: derive its prefix from
|
|
257
|
+
`new URL("./", self.location).pathname` instead of hard-coding `/`, otherwise
|
|
258
|
+
the precache and the offline navigation fallback miss every file under a
|
|
259
|
+
subpath.
|
|
260
|
+
|
|
261
|
+
## Unknown routes
|
|
262
|
+
|
|
263
|
+
If the initial URL is not found, the router redirects to the first configured route. You can also define a `*` or `/404` route for navigation attempts that do not exist.
|
|
264
|
+
|
|
265
|
+
## Common mistakes
|
|
266
|
+
|
|
267
|
+
- Forgetting to pass routes to `runApp` or call `initRouter`.
|
|
268
|
+
- Using `/user/:id` but navigating to `/user` without an id.
|
|
269
|
+
- Reading query values from params, or params from query values.
|
|
270
|
+
- Calling `goTo()` before the router has been initialized.
|
|
271
|
+
- Forgetting to unsubscribe from route listeners in a long-lived integration.
|
|
272
|
+
- Expecting `goBack()` to work like a server redirect; it uses browser history.
|
|
273
|
+
- Assuming the app owns the host root: under a subpath, content fetches need
|
|
274
|
+
`getBasePath()` and the host needs a `404.html` fallback for deep links.
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Continue reading
|
|
279
|
+
|
|
280
|
+
- **Previous:** [State with `useState`](state.md)
|
|
281
|
+
- **Next:** [Frontend services guide](frontend-services.md) — Chapter 9 · State, Router & Services
|
|
282
|
+
- **Index:** [Guides index](README.md) · [The FletBox Book](../README.md)
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# State with `useState`
|
|
2
|
+
|
|
3
|
+
FletBox state lets widgets share values and update the interface when those values change.
|
|
4
|
+
|
|
5
|
+
## The simple idea
|
|
6
|
+
|
|
7
|
+
A state value is a named value stored by a key:
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
const [count, setCount] = useState("counter", 0);
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
- `"counter"` is the shared key.
|
|
14
|
+
- `0` is the initial value.
|
|
15
|
+
- `count` is the current reactive value.
|
|
16
|
+
- `setCount` changes the value.
|
|
17
|
+
|
|
18
|
+
## Small counter example
|
|
19
|
+
|
|
20
|
+
```javascript
|
|
21
|
+
import { Button, Column, Text, useState } from "flet-box";
|
|
22
|
+
|
|
23
|
+
const Counter = () => {
|
|
24
|
+
const [count, setCount] = useState("counter", 0);
|
|
25
|
+
|
|
26
|
+
return Column({
|
|
27
|
+
gap: 12,
|
|
28
|
+
children: [
|
|
29
|
+
Text({ text: `Count: ${count}` }), // derived text → app render (see below)
|
|
30
|
+
Button({
|
|
31
|
+
text: "Add one",
|
|
32
|
+
onPress: () => setCount((previous) => previous + 1),
|
|
33
|
+
}),
|
|
34
|
+
],
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
When `setCount` runs, FletBox writes the new value to the store and then decides how much
|
|
40
|
+
of the UI has to change. That decision is the subject of the next section, and it is worth
|
|
41
|
+
getting right: one style of using the value updates a single element, the other re-runs
|
|
42
|
+
your whole component.
|
|
43
|
+
|
|
44
|
+
## Surgical updates vs. app renders
|
|
45
|
+
|
|
46
|
+
A state value is a **live** value. If you hand it to a prop, the framework remembers that
|
|
47
|
+
link and, on change, patches only that widget:
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
const [count, setCount] = useState("counter", 0);
|
|
51
|
+
|
|
52
|
+
Text({ text: count }); // surgical: this element is patched
|
|
53
|
+
Container({ bgColor: theme }); // surgical
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
If you *compute with it inside the component*, the value is consumed before it ever
|
|
57
|
+
reaches a prop, so there is nothing left to patch — the only way to reflect the change is
|
|
58
|
+
to re-run the app:
|
|
59
|
+
|
|
60
|
+
```javascript
|
|
61
|
+
Text({ text: `Count: ${count}` }); // app render
|
|
62
|
+
Text({ text: count > 5 ? "MUCHO" : "poco" }); // app render
|
|
63
|
+
items.map((item) => Row({ child: Text({ text: item }) })); // app render
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Measured on the same screen, three changes to one key:
|
|
67
|
+
|
|
68
|
+
| How the key is used | App re-renders | Widgets patched |
|
|
69
|
+
| --- | --- | --- |
|
|
70
|
+
| `Text({ text: count })` only | **0** | the one `Text` |
|
|
71
|
+
| mixed with a template literal | 1 | patched, then replaced |
|
|
72
|
+
| template literal only | 1 | — |
|
|
73
|
+
|
|
74
|
+
Two details that make this easier to live with:
|
|
75
|
+
|
|
76
|
+
- **Reads outside the render are free.** Reading the value inside `onPress`, or inside a
|
|
77
|
+
`subscribe` callback, does **not** force an app render — only reads that happen while the
|
|
78
|
+
component is building its tree do.
|
|
79
|
+
- **Renders coalesce.** Several `setState` calls in the same tick produce **one** app
|
|
80
|
+
render, not one per call.
|
|
81
|
+
|
|
82
|
+
If you need derived text but want to stay surgical, derive it in the subscriber:
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
const App = () => {
|
|
86
|
+
const [count, setCount, onCount] = useState("counter", 0);
|
|
87
|
+
const label = Text({ text: "Count: 0" });
|
|
88
|
+
|
|
89
|
+
onCount((n) => label.update({ text: `Count: ${n}` }));
|
|
90
|
+
|
|
91
|
+
return Column({ children: [label] });
|
|
92
|
+
};
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The app render has no Virtual DOM diff behind it: it re-runs the component and **creates
|
|
96
|
+
new elements**, discarding the old ones. That is why passing the value straight into a prop
|
|
97
|
+
is worth the small change in style. See
|
|
98
|
+
[Reactivity in FletBox](../tools/reactivity.md) for the full decision rule.
|
|
99
|
+
|
|
100
|
+
## Set a direct value
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
const [theme, setTheme] = useState("theme", "light");
|
|
104
|
+
|
|
105
|
+
setTheme("dark");
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Use an updater function
|
|
109
|
+
|
|
110
|
+
Use an updater when the new value depends on the previous value:
|
|
111
|
+
|
|
112
|
+
```javascript
|
|
113
|
+
setCount((previous) => previous + 1);
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
This is useful for counters, arrays, and objects.
|
|
117
|
+
|
|
118
|
+
## Store an object
|
|
119
|
+
|
|
120
|
+
```javascript
|
|
121
|
+
const [user, setUser] = useState("user", {
|
|
122
|
+
name: "Ada",
|
|
123
|
+
online: false,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
setUser((previous) => ({
|
|
127
|
+
...previous,
|
|
128
|
+
online: true,
|
|
129
|
+
}));
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Create a new object or array when updating nested data. This makes the change clear and prevents accidental mutation.
|
|
133
|
+
|
|
134
|
+
## Store an array
|
|
135
|
+
|
|
136
|
+
```javascript
|
|
137
|
+
const [items, setItems] = useState("items", []);
|
|
138
|
+
|
|
139
|
+
setItems((previous) => [
|
|
140
|
+
...previous,
|
|
141
|
+
{ id: Date.now(), title: "New item" },
|
|
142
|
+
]);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Shared state between widgets
|
|
146
|
+
|
|
147
|
+
Two widgets using the same key can read and update the same stored value:
|
|
148
|
+
|
|
149
|
+
```javascript
|
|
150
|
+
const Status = () => {
|
|
151
|
+
const [enabled] = useState("feature-enabled", false);
|
|
152
|
+
return Text({ text: enabled ? "Enabled" : "Disabled" });
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const ToggleFeature = () => {
|
|
156
|
+
const [enabled, setEnabled] = useState("feature-enabled", false);
|
|
157
|
+
return Button({
|
|
158
|
+
text: enabled ? "Turn off" : "Turn on",
|
|
159
|
+
onPress: () => setEnabled((value) => !value),
|
|
160
|
+
});
|
|
161
|
+
};
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Use descriptive, unique keys. A key is global within the state store, so reusing a key accidentally can connect unrelated widgets.
|
|
165
|
+
|
|
166
|
+
## Watch a state key
|
|
167
|
+
|
|
168
|
+
`useWatchState` subscribes to changes without creating a state value:
|
|
169
|
+
|
|
170
|
+
```javascript
|
|
171
|
+
import { useWatchState } from "flet-box";
|
|
172
|
+
|
|
173
|
+
const stopWatching = useWatchState("counter", (newValue, oldValue) => {
|
|
174
|
+
console.log({ newValue, oldValue });
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Later:
|
|
178
|
+
stopWatching();
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Subscribe from `useState`
|
|
182
|
+
|
|
183
|
+
The current implementation returns a third value for subscriptions:
|
|
184
|
+
|
|
185
|
+
```javascript
|
|
186
|
+
const [value, setValue, subscribe] = useState("status", "idle");
|
|
187
|
+
|
|
188
|
+
const unsubscribe = subscribe((newValue, oldValue) => {
|
|
189
|
+
console.log(newValue, oldValue);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Later:
|
|
193
|
+
unsubscribe();
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## State and widgets
|
|
197
|
+
|
|
198
|
+
Normally you do not register anything: pass the state value into a prop and the widget
|
|
199
|
+
factory binds it to the key for you, releasing the binding when the widget is removed.
|
|
200
|
+
|
|
201
|
+
```javascript
|
|
202
|
+
const [theme, setTheme] = useState("theme", "light");
|
|
203
|
+
|
|
204
|
+
const panel = Container({ bgColor: theme }); // bound automatically
|
|
205
|
+
setTheme("dark"); // patches `panel` only
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
There is an explicit form for the case where the widget already exists and you want one of
|
|
209
|
+
its props driven by a key:
|
|
210
|
+
|
|
211
|
+
```javascript
|
|
212
|
+
const [label, setLabel] = useState(
|
|
213
|
+
"button-label",
|
|
214
|
+
"Save",
|
|
215
|
+
button,
|
|
216
|
+
"text",
|
|
217
|
+
);
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Both push the new value through `widget.update({ prop: value })`, so the update touches one
|
|
221
|
+
element instead of rebuilding the screen.
|
|
222
|
+
|
|
223
|
+
## Reading the value
|
|
224
|
+
|
|
225
|
+
The value returned by `useState` behaves like the current state in most expressions —
|
|
226
|
+
`${count}` and `count + 1` work — but under the hood it is a live function, and that shows
|
|
227
|
+
up in two places:
|
|
228
|
+
|
|
229
|
+
```javascript
|
|
230
|
+
typeof count; // "function", not "number"
|
|
231
|
+
count.valueOf(); // the current value, explicitly
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
For objects and arrays, **property access does not go through the state**:
|
|
235
|
+
|
|
236
|
+
```javascript
|
|
237
|
+
const [user, setUser] = useState("user", { name: "Ada", online: false });
|
|
238
|
+
|
|
239
|
+
user.name; // ✗ "live" — the function's own name, not the state's
|
|
240
|
+
user.online; // ✗ undefined
|
|
241
|
+
user().name; // ✓ "Ada" — call it, or pass it straight into a prop
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
So for object state, prefer letting a widget resolve it (pass the value itself as the prop,
|
|
245
|
+
not a field of it) or read it by calling the value inside a handler, where reads are free.
|
|
246
|
+
|
|
247
|
+
## Persistence during the session
|
|
248
|
+
|
|
249
|
+
FletBox stores state through its RAM store. The value is available by key while the application is running:
|
|
250
|
+
|
|
251
|
+
```javascript
|
|
252
|
+
import { saveRam, getRam } from "flet-box";
|
|
253
|
+
|
|
254
|
+
saveRam("language", "en");
|
|
255
|
+
const language = getRam("language");
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Use the storage services when you need browser persistence or a different lifetime.
|
|
259
|
+
|
|
260
|
+
## Important rules
|
|
261
|
+
|
|
262
|
+
- Call `useState` with a stable key.
|
|
263
|
+
- Do not create random keys on every render.
|
|
264
|
+
- Use updater functions for values based on previous state.
|
|
265
|
+
- Avoid mutating arrays and objects in place.
|
|
266
|
+
- Pass the state value straight into a prop when you can: that is what keeps the update
|
|
267
|
+
surgical instead of re-running the app.
|
|
268
|
+
- Do not read object state with `user.name`; call the value (`user()`) or let a widget
|
|
269
|
+
resolve it.
|
|
270
|
+
- Unsubscribe from manual watchers when the feature is destroyed.
|
|
271
|
+
- Use `runApp()` when you want global re-render behavior for a complete app.
|
|
272
|
+
|
|
273
|
+
## TypeScript note
|
|
274
|
+
|
|
275
|
+
The runtime currently returns `[value, setValue, subscribe]`. Keep the declaration file and this behavior aligned when changing the public API.
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## Continue reading
|
|
280
|
+
|
|
281
|
+
- **Previous:** [CollapsibleSideBar](../widget/CollapsibleSideBar.md) — end of the widget book.
|
|
282
|
+
- **Next:** [Routing with FletBox](router.md) — Chapter 9 · State, Router & Services
|
|
283
|
+
- **Index:** [Guides index](README.md) · [The FletBox Book](../README.md)
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# FletBox syntax philosophy
|
|
2
|
+
|
|
3
|
+
FletBox is designed around the developer, not the other way around.
|
|
4
|
+
|
|
5
|
+
You should be able to describe an interface in a way that feels natural to you. Developers coming from Flet, Flutter, React, or vanilla JavaScript may use slightly different names for the same idea. FletBox is intentionally friendly to those styles.
|
|
6
|
+
|
|
7
|
+
## One idea, several comfortable names
|
|
8
|
+
|
|
9
|
+
Some props have aliases because different ecosystems use different words.
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
Container({
|
|
13
|
+
bgColor: "#eff6ff",
|
|
14
|
+
padding: 16,
|
|
15
|
+
});
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
A developer coming from traditional web development may prefer:
|
|
19
|
+
|
|
20
|
+
```javascript
|
|
21
|
+
Container({
|
|
22
|
+
backgroundColor: "#eff6ff",
|
|
23
|
+
padding: "16px",
|
|
24
|
+
});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The goal is not to force one vocabulary. The goal is to make the intention obvious.
|
|
28
|
+
|
|
29
|
+
## Events that read naturally
|
|
30
|
+
|
|
31
|
+
Depending on your background, either event name may feel more familiar:
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
Button({
|
|
35
|
+
text: "Save",
|
|
36
|
+
onPress: save,
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```javascript
|
|
41
|
+
Button({
|
|
42
|
+
text: "Save",
|
|
43
|
+
onClick: save,
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Both express the same human idea: run this function when the user activates the button.
|
|
48
|
+
|
|
49
|
+
## Children and composition
|
|
50
|
+
|
|
51
|
+
Flet and Flutter developers may think in terms of a single `child`:
|
|
52
|
+
|
|
53
|
+
```javascript
|
|
54
|
+
Container({
|
|
55
|
+
child: Text({ text: "Hello" }),
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
React developers may prefer `children`:
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
Container({
|
|
63
|
+
children: [
|
|
64
|
+
Text({ text: "Hello" }),
|
|
65
|
+
Text({ text: "Welcome" }),
|
|
66
|
+
],
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Use `child` for one child and `children` for several children when the widget supports both.
|
|
71
|
+
|
|
72
|
+
## Direct values or helper functions
|
|
73
|
+
|
|
74
|
+
FletBox keeps simple values simple:
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
Container({
|
|
78
|
+
padding: 16,
|
|
79
|
+
margin: "8px 0",
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
You can also use helpers when you prefer an expressive style:
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
import { margin, padding } from "flet-box";
|
|
87
|
+
|
|
88
|
+
Container({
|
|
89
|
+
padding: padding({ horizontal: 20, vertical: 12 }),
|
|
90
|
+
margin: margin({ top: 8, bottom: 16 }),
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The first style is quick. The second style can make complex spacing more descriptive. Choose the one that makes your code easiest to read.
|
|
95
|
+
|
|
96
|
+
## Styling vocabulary
|
|
97
|
+
|
|
98
|
+
FletBox accepts familiar CSS-like values:
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
Container({
|
|
102
|
+
width: "100%",
|
|
103
|
+
minHeight: "100vh",
|
|
104
|
+
borderRadius: 12,
|
|
105
|
+
bgColor: "#ffffff",
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Numbers normally represent pixels. Strings can contain CSS units such as `%`, `vh`, `rem`, or `px`.
|
|
110
|
+
|
|
111
|
+
## Preferred and compatible syntax
|
|
112
|
+
|
|
113
|
+
For new projects, choose one style and use it consistently within a file. The examples in this documentation generally use the short, readable widget style:
|
|
114
|
+
|
|
115
|
+
```javascript
|
|
116
|
+
Text({ text: "Hello", size: 18, weight: "bold" });
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Aliases exist to make migration and adoption easier. They are not a requirement to mix every style in the same component.
|
|
120
|
+
|
|
121
|
+
Think of the API in three layers:
|
|
122
|
+
|
|
123
|
+
- **Preferred syntax**: the clearest style recommended by the current documentation.
|
|
124
|
+
- **Friendly aliases**: equivalent names that help developers coming from another ecosystem.
|
|
125
|
+
- **Compatibility syntax**: older names preserved so existing projects do not break.
|
|
126
|
+
|
|
127
|
+
## The rule for documentation
|
|
128
|
+
|
|
129
|
+
Every widget page should answer three questions:
|
|
130
|
+
|
|
131
|
+
1. What does this widget represent?
|
|
132
|
+
2. What is the smallest example that works?
|
|
133
|
+
3. Which alternative names or styles can a developer use?
|
|
134
|
+
|
|
135
|
+
That is why each widget page contains a basic example, an everyday example, a full example, common prop explanations, and beginner tips.
|
|
136
|
+
|
|
137
|
+
## The rule for framework design
|
|
138
|
+
|
|
139
|
+
FletBox should adapt to the developer wherever that flexibility remains clear, testable, and maintainable.
|
|
140
|
+
|
|
141
|
+
Friendly syntax must still have:
|
|
142
|
+
|
|
143
|
+
- Clear documentation.
|
|
144
|
+
- Consistent behavior.
|
|
145
|
+
- Type declarations.
|
|
146
|
+
- Working examples.
|
|
147
|
+
- Tests for important aliases.
|
|
148
|
+
- A migration path when an alias changes.
|
|
149
|
+
|
|
150
|
+
This keeps the experience soft for beginners without making the framework mysterious for experienced developers.
|