flet-box 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/README.md +10 -0
  2. package/bin/cli.js +23 -7
  3. package/bin/commands/checkApi.js +761 -0
  4. package/bin/commands/create.js +77 -5
  5. package/bin/utils/dtsProps.js +191 -0
  6. package/bin/utils/moduleExports.js +165 -0
  7. package/bin/utils/neutralise.js +64 -0
  8. package/bin/utils/templates.js +366 -27
  9. package/docs/CONTRIBUTING.md +156 -0
  10. package/docs/README.md +68 -0
  11. package/docs/arquitectura.md +164 -0
  12. package/docs/cli/README.md +542 -0
  13. package/docs/guides/README.md +33 -0
  14. package/docs/guides/app-templates.md +197 -0
  15. package/docs/guides/compatibility.md +95 -0
  16. package/docs/guides/embedding.md +284 -0
  17. package/docs/guides/first-app.md +208 -0
  18. package/docs/guides/frontend-services.md +171 -0
  19. package/docs/guides/minification-and-protection.md +260 -0
  20. package/docs/guides/mobile-and-platforms.md +439 -0
  21. package/docs/guides/online-updates.md +196 -0
  22. package/docs/guides/router.md +282 -0
  23. package/docs/guides/state.md +283 -0
  24. package/docs/guides/syntax-philosophy.md +150 -0
  25. package/docs/guides/utilities.md +241 -0
  26. package/docs/guides/widget-structure.md +163 -0
  27. package/docs/server/README.md +68 -0
  28. package/docs/server/START_HERE.md +121 -0
  29. package/docs/server/api.md +225 -0
  30. package/docs/server/data-services.md +300 -0
  31. package/docs/server/deployment.md +113 -0
  32. package/docs/server/security.md +196 -0
  33. package/docs/services/README.md +79 -0
  34. package/docs/services/RamStore.md +84 -0
  35. package/docs/services/Session.md +84 -0
  36. package/docs/services/Storage.md +88 -0
  37. package/docs/services/http.md +86 -0
  38. package/docs/tools/README.md +138 -0
  39. package/docs/tools/animation.md +87 -0
  40. package/docs/tools/async-ids-log.md +80 -0
  41. package/docs/tools/device.md +132 -0
  42. package/docs/tools/introspection.md +67 -0
  43. package/docs/tools/lists.md +96 -0
  44. package/docs/tools/markdown.md +79 -0
  45. package/docs/tools/reactivity.md +288 -0
  46. package/docs/tools/state.md +103 -0
  47. package/docs/tools/styling.md +92 -0
  48. package/docs/tools/text-and-time.md +89 -0
  49. package/docs/tools/theme.md +102 -0
  50. package/docs/tools/validation.md +75 -0
  51. package/docs/tools/visual-effects.md +84 -0
  52. package/docs/widget/Accordion.md +144 -0
  53. package/docs/widget/AdMob.md +144 -0
  54. package/docs/widget/AdSense.md +133 -0
  55. package/docs/widget/AdaptiveScaffold.md +105 -0
  56. package/docs/widget/AlertDialog.md +103 -0
  57. package/docs/widget/AnimatedBox.md +130 -0
  58. package/docs/widget/AnimatedText.md +123 -0
  59. package/docs/widget/AppBar.md +118 -0
  60. package/docs/widget/Audio.md +135 -0
  61. package/docs/widget/Avatar.md +119 -0
  62. package/docs/widget/Badge.md +128 -0
  63. package/docs/widget/BottomNavigation.md +116 -0
  64. package/docs/widget/BottomSheet.md +125 -0
  65. package/docs/widget/Button.md +137 -0
  66. package/docs/widget/COMMON_PROPS.md +150 -0
  67. package/docs/widget/Card.md +125 -0
  68. package/docs/widget/Carousel.md +151 -0
  69. package/docs/widget/Chart.md +151 -0
  70. package/docs/widget/Checkbox.md +118 -0
  71. package/docs/widget/Chip.md +126 -0
  72. package/docs/widget/CircularBar.md +168 -0
  73. package/docs/widget/CircularChart.md +164 -0
  74. package/docs/widget/CodeViewer.md +130 -0
  75. package/docs/widget/CollapsibleSideBar.md +112 -0
  76. package/docs/widget/Column.md +129 -0
  77. package/docs/widget/Container.md +136 -0
  78. package/docs/widget/DataTable.md +150 -0
  79. package/docs/widget/Divider.md +109 -0
  80. package/docs/widget/DraggBox.md +129 -0
  81. package/docs/widget/Drawer.md +135 -0
  82. package/docs/widget/DrawerItem.md +98 -0
  83. package/docs/widget/Dropdown.md +139 -0
  84. package/docs/widget/DroppBox.md +152 -0
  85. package/docs/widget/FloatingActionButton.md +120 -0
  86. package/docs/widget/GridView.md +140 -0
  87. package/docs/widget/Icon.md +117 -0
  88. package/docs/widget/Image.md +125 -0
  89. package/docs/widget/Input.md +151 -0
  90. package/docs/widget/Inspector.md +92 -0
  91. package/docs/widget/InstallButton.md +128 -0
  92. package/docs/widget/ListTile.md +132 -0
  93. package/docs/widget/ListView.md +138 -0
  94. package/docs/widget/Markdown.md +175 -0
  95. package/docs/widget/MatrixRain.md +122 -0
  96. package/docs/widget/Modal.md +129 -0
  97. package/docs/widget/Pagination.md +135 -0
  98. package/docs/widget/ParallaxBox.md +128 -0
  99. package/docs/widget/ProgressBar.md +130 -0
  100. package/docs/widget/QRCode.md +117 -0
  101. package/docs/widget/README.md +188 -0
  102. package/docs/widget/Radio.md +123 -0
  103. package/docs/widget/Rating.md +131 -0
  104. package/docs/widget/Row.md +131 -0
  105. package/docs/widget/START_HERE.md +146 -0
  106. package/docs/widget/Scaffold.md +152 -0
  107. package/docs/widget/Skeleton.md +130 -0
  108. package/docs/widget/Slider.md +146 -0
  109. package/docs/widget/SnackBar.md +102 -0
  110. package/docs/widget/Stack.md +128 -0
  111. package/docs/widget/Stepper.md +145 -0
  112. package/docs/widget/Switch.md +118 -0
  113. package/docs/widget/Tabs.md +124 -0
  114. package/docs/widget/Text.md +133 -0
  115. package/docs/widget/Tooltip.md +145 -0
  116. package/docs/widget/TreeView.md +176 -0
  117. package/docs/widget/Video.md +127 -0
  118. package/docs/widget/_template.md +61 -0
  119. package/package.json +3 -2
  120. package/src/animations/animate.js +180 -70
  121. package/src/animations/index.js +1 -0
  122. package/src/create-index.sh +1 -1
  123. package/src/index.d.ts +45 -22
  124. package/src/index.js +1 -0
  125. package/src/navigations/AdaptiveScaffold.js +11 -2
  126. package/src/navigations/BottomNavigation.js +10 -3
  127. package/src/navigations/Tabs.js +6 -2
  128. package/src/tools/useState.js +54 -63
  129. package/src/widget-factory/assignProps.js +23 -1
  130. package/src/widget-factory/processProps.js +10 -3
  131. package/src/widget-factory/reactiveValue.js +43 -18
  132. package/src/widget-factory/translateProps.js +777 -901
  133. package/src/widgets/AlertDialog.js +7 -5
  134. package/src/widgets/Checkbox.js +9 -7
  135. package/src/widgets/CircularBar.js +12 -1
  136. package/src/widgets/FloatingActionButton.js +4 -7
  137. package/src/widgets/Image.js +5 -7
  138. package/src/widgets/Input.js +92 -0
  139. package/src/widgets/ListView.js +73 -29
  140. package/src/widgets/Pagination.js +6 -3
  141. package/src/widgets/ProgressBar.js +16 -13
  142. package/src/widgets/Radio.js +6 -7
  143. package/src/widgets/Slider.js +23 -4
  144. package/src/widgets/Stack.js +36 -3
  145. package/src/widgets/Stepper.js +4 -3
  146. package/src/widgets/Switch.js +3 -4
  147. package/src/widgets/TreeView.js +0 -1
@@ -0,0 +1,288 @@
1
+ # Reactivity in FletBox
2
+
3
+ ## Overview
4
+
5
+ Reactivity in FletBox means **a widget changes when something else changes** — a state
6
+ value, the theme, the route, or a prop you assign at runtime. There is no Virtual DOM
7
+ layer: each mechanism below writes to a real element in place, and the most efficient one
8
+ updates a single widget without re-running any component code.
9
+
10
+ This page is the **catalogue** of every reactivity path the framework ships. There is no single magic system; there are complementary layers, from the in-memory store up to per-widget DOM accessors.
11
+
12
+ ## The catalogue at a glance
13
+
14
+ | # | Mechanism | Produced by | Scope of the update |
15
+ | --- | --- | --- | --- |
16
+ | 1 | Key-based shared state | `useState(key, value)` | Bound widgets patched; app render only when needed |
17
+ | 2 | Low-level store | `subscribeRam` / `saveRam` | Any code subscribed to RamStore keys |
18
+ | 3 | Registered-widget updates | `useState(key, value, widget, propName)` | A single widget prop |
19
+ | 4 | Reactive property setters | `widget.prop = value` | A single widget, no global render |
20
+ | 5 | Imperative updates | `widget.update(props)` | One widget's merged props |
21
+ | 6 | Live handles | `ref()` | The bound widget (methods + props) |
22
+ | 7 | Side-effect watchers | `useWatchState(key, cb)` | A callback, no widget |
23
+ | 8 | Theme reactivity | `subscribeTheme(cb)` / `watchSystemTheme()` | Theme subscribers |
24
+ | 9 | Router reactivity | `subscribe(cb)` (Router) | Route subscribers (Scaffold, nav bars) |
25
+ | 10 | Reactive prop values | `_isReactive` objects/functions | Resolved at apply time **and bound to the key** |
26
+
27
+ ## 1. Key-based shared state — `useState`
28
+
29
+ The public state hook. Values live in the in-memory store keyed by a string and survive re-renders.
30
+
31
+ ```javascript
32
+ const [count, setCount, subscribe] = useState("counter", 0);
33
+ setCount((previous) => previous + 1);
34
+ ```
35
+
36
+ `setState` writes to RamStore. One shared dispatcher then, in order: patches every
37
+ widget bound to that key, runs the `subscribe` / `useWatchState` callbacks for the key,
38
+ and **only if necessary** schedules an app render (debounced, so several changes made in
39
+ the same tick cost one render). See **The surgical decision**, further down this page.
40
+
41
+ Read the full rules in [State, memo & refs](state.md) and the [State guide](../guides/state.md).
42
+
43
+ ## 2. The low-level store — `RamStore`
44
+
45
+ The raw reactive map behind `useState`. You can talk to it directly when you want one store shared by several subsystems.
46
+
47
+ ```javascript
48
+ import { saveRam, getRam, subscribeRam } from "flet-box";
49
+
50
+ subscribeRam((key, newValue, oldValue) => {
51
+ if (key === "user") renderProfile(newValue);
52
+ });
53
+ saveRam("user", { name: "Ada" });
54
+ ```
55
+
56
+ `saveRam`, `updateRam`, and `deleteRam` all notify subscribers with `(key, newValue, oldValue)`; `clearAllRam` notifies with `null`s.
57
+
58
+ ## 3. Registered-widget updates — `useState(key, value, widget, propName)`
59
+
60
+ The optional `widget` and `propName` arguments register a specific widget so a state change pushes the new value straight into it via `widget.update({ [propName]: newValue })` — the machinery that powers targeted updates.
61
+
62
+ ```javascript
63
+ const label = Text({ text: "waiting…" });
64
+ const [status, setStatus] = useState("status", "idle", label, "text");
65
+
66
+ setStatus("done"); // calls label.update({ text: "done" })
67
+ ```
68
+
69
+ Because the widget is now bound to the key, this form is surgical on its own: `setStatus`
70
+ patches `label` and skips the app render, unless the component also *computed* with that
71
+ key during its last render (see **The surgical decision**). Prefer passing the state value
72
+ straight into a prop — mechanism 10 binds it for you — and use this signature when the
73
+ widget already exists.
74
+
75
+ ## 4. Reactive property setters — `widget.prop = value`
76
+
77
+ Every style, spacing, colour, typography, and text prop installed by the widget factory becomes a live accessor: reading `widget.prop` returns the current value, assigning calls `widget.update({ prop })` and patches **that element only**. No app render, no subscribers involved.
78
+
79
+ ```javascript
80
+ header.text = "New title"; // element.textContent updates in place
81
+ panel.update({ bgColor: "#e11d48" });
82
+ ```
83
+
84
+ Two protections, by design:
85
+
86
+ - `value` is never shadowed on form elements so the browser keeps live input state (use `setValue` or `widget.value` on inputs).
87
+ - `width` / `height` are not shadowed on replaced elements (`img`, `canvas`, `video`, `iframe`, `embed`, `object`, `source`) to preserve their native intrinsic-size behaviour.
88
+
89
+ ## 5. Imperative updates — `widget.update(props)`
90
+
91
+ The factory-level update method on every widget. It merges the new props into the stored props and re-applies styles, attributes, events, children, and effects through the same pipeline used at construction. A recursion guard (`MAX_UPDATE_DEPTH`) prevents runaway loops, and `child` / `children` are normalised so only one wins.
92
+
93
+ ```javascript
94
+ card.update({ padding: 24, borderRadius: 16 });
95
+ ```
96
+
97
+ ## 6. Live handles — `ref()`
98
+
99
+ A `ref` is a function bound to a widget's `ref` prop. The handle mirrors the widget's methods and property accessors, so you can read and write live:
100
+
101
+ ```javascript
102
+ const label = ref();
103
+ Text({ text: "Hi", ref: label });
104
+
105
+ label.update({ text: "Saved!" }); // same as widget.update
106
+ label.text = "Also works"; // forwarded to the widget
107
+ ```
108
+
109
+ ## 7. Side-effect watchers — `useWatchState`
110
+
111
+ Like `useState`'s `subscribe`, but for side effects that should not touch widgets. Returns an unsubscribe function.
112
+
113
+ ```javascript
114
+ const unsubscribe = useWatchState("counter", (newValue) => {
115
+ console.log("Counter hit", newValue);
116
+ });
117
+ ```
118
+
119
+ ## 8. Theme reactivity — `subscribeTheme`
120
+
121
+ Theme changes re-render subscribers. `runApp` subscribes itself so the whole app recolors on `setTheme` / `toggleTheme`, and some widgets (`ListTile`, `Rating`, `Badge`) subscribe individually to stay themed. `watchSystemTheme()` follows OS light/dark preference.
122
+
123
+ ```javascript
124
+ const unsubscribeTheme = subscribeTheme((palette) => {
125
+ root.style.backgroundColor = palette.background;
126
+ });
127
+ ```
128
+
129
+ ## 9. Router reactivity — `subscribe`
130
+
131
+ The router notifies subscribers on navigation. Layout shells and navigation widgets (`Scaffold`, `BottomNavigation`, `DrawerItem`) subscribe so highlighting and route content stay in sync; they unsubscribe automatically when removed from the DOM.
132
+
133
+ ```javascript
134
+ subscribe((route, params, query) => setBreadcrumb(route.path));
135
+ ```
136
+
137
+ ## 10. Reactive prop values — resolved **and bound when passed directly**
138
+
139
+ A prop value can be a function or object flagged as reactive (`_isReactive`). The
140
+ factory resolves it to its current value when the prop is applied — and when the
141
+ value carries a state key (what `useState` returns), the factory **binds the widget
142
+ to that key automatically**.
143
+
144
+ ```javascript
145
+ const [color, setColor] = useState("color", "red");
146
+
147
+ const box = Container({ bgColor: color }); // ← the binding is created here
148
+ setColor("blue"); // ← only `box` is patched
149
+ ```
150
+
151
+ A directly-passed reactive prop gives you a **surgical** update: the change patches
152
+ that one widget. The app does **not** re-render — not even once — and the DOM node
153
+ is reused, so `widget.prop = value` writes made earlier are not thrown away.
154
+
155
+ The binding is released automatically when the widget is removed (`onUnmount`), so
156
+ detached widgets stop receiving updates.
157
+
158
+ ### The surgical decision
159
+
160
+ The value returned by `useState` is a **live function**. When it reaches a prop, the
161
+ factory resolves it *and* records the `(key → widget → prop)` link, so a later change
162
+ patches that widget instead of re-running anything. When it is consumed inside the
163
+ component, the string or boolean that reaches the prop carries no key left to bind, so the
164
+ only way to reflect a change is to re-run the app.
165
+
166
+ The dispatcher decides **per key, per change**, from exactly two facts:
167
+
168
+ ```javascript
169
+ if (handled === 0 || isStructuralRead(changedKey)) triggerRender();
170
+ ```
171
+
172
+ - `handled` — how many widgets were patched for this key (mechanisms 3 and 10).
173
+ - `isStructuralRead(key)` — whether the component read that value *while rendering*,
174
+ during the last app render pass.
175
+
176
+ So the app re-renders when the key has no bound widget, or when the component computed
177
+ with it. A key can be both: then the widget **is** patched *and* the app re-renders, and
178
+ the patch is wasted work on a node the rebuild replaces.
179
+
180
+ What counts as a structural read:
181
+
182
+ | Code inside the component | Kind of read | Update |
183
+ | --- | --- | --- |
184
+ | `Text({ text: count })` | value passed as a prop | surgical |
185
+ | `Container({ bgColor: color })` | value passed as a prop | surgical |
186
+ | `Text({ text: "Count: " + count })` | concatenation | app render |
187
+ | `count > 5 ? A : B` | comparison in the component | app render |
188
+ | `getState("items").map(…)` | list shape | app render |
189
+ | `onPress: () => setCount(count + 1)` | inside an event handler | surgical |
190
+ | `onCount((n) => …)` | inside a subscriber | surgical |
191
+
192
+ Two properties of that set are worth knowing:
193
+
194
+ - **It is rebuilt on every render pass.** A structural read that only happens under a
195
+ condition stops costing app renders as soon as the component stops doing it.
196
+ - **Changes coalesce.** The render is scheduled with `setTimeout(…, 0)`, so three
197
+ `setCount` calls in one tick cost **one** app render.
198
+
199
+ Measured on a `Column` whose `Text` children depend on one key:
200
+
201
+ | component | changes | app renders | `Text` built | node reused |
202
+ | --- | --- | --- | --- | --- |
203
+ | only `Text({ text: count })` | 3 in one tick | **0** | 1 | yes |
204
+ | Concatenation + `text: count` + ternary | 3 in one tick | 1 | 2 | no |
205
+ | Only the concatenation | 2 in separate ticks | 2 | 3 | no |
206
+
207
+ The first row is the surgical path: no component code runs, `count`'s single bound widget
208
+ gets `update({ text: … })`. The last row shows the coalescing limit — a render per tick, not
209
+ per change.
210
+
211
+ ### Keeping derived text surgical
212
+
213
+ A derived string does not need an app render if you derive it in the subscriber instead of
214
+ in the template:
215
+
216
+ ```javascript
217
+ const App = () => {
218
+ const [count, setCount, onCount] = useState("counter", 0);
219
+ const label = Text({ text: "Count: 0" });
220
+ const badge = Text({ text: "poco" });
221
+
222
+ onCount((n) => {
223
+ label.update({ text: `Count: ${n}` });
224
+ badge.update({ text: n > 5 ? "MUCHO" : "poco" });
225
+ });
226
+
227
+ return Column({ children: [label, badge] });
228
+ };
229
+ ```
230
+
231
+ `onCount` runs outside a render pass, so its reads are not structural and the key stays
232
+ surgical — measured at **0 app renders** for three changes. This is the same reason reading
233
+ state inside `onPress` is free.
234
+
235
+ ### What the app render does *not* do
236
+
237
+ There is no Virtual DOM and no reconciliation: an app render **re-runs the component and
238
+ creates new elements**, and the previous ones are dropped. Earlier `widget.prop = value`
239
+ writes and any internal state of the replaced subtree are lost (form fields keep what the
240
+ user typed, because `value` is never shadowed). A key read structurally is therefore the
241
+ one case where the framework does more work than it needs to — which is why the
242
+ recommendation is always the same: pass state values straight into props.
243
+
244
+ ## Widget-owned reactivity
245
+
246
+ Beyond the generic layer, many widgets expose their own imperative APIs and some are reactive setters themselves (`Slider.value`, `Accordion.expanded`, `Dropdown.options`). Each widget page lists them under **Instance methods**. These follow the same rule: they call `update(...)` internally on that widget.
247
+
248
+ ## Lifecycle containment
249
+
250
+ Every subscriber API returns an unsubscribe function. State bindings are released through
251
+ the widget's `onUnmount` hook — fired on both teardown paths — so a detached widget stops
252
+ receiving patches and its key entry is dropped once no widget is left bound to it.
253
+ `runApp`'s teardown sweeps the whole tree so re-renders do not leak listeners, timers, or
254
+ observers.
255
+
256
+ Note that `_cleanup` is **not** a lifecycle hook the framework calls; it is legacy. Resource
257
+ release belongs in `onUnmount`.
258
+
259
+ ## Which one should you use?
260
+
261
+ | You want to… | Use | Cost |
262
+ | --- | --- | --- |
263
+ | Update ONE widget from a state value, without touching the app | `useState` + pass the value straight into a prop (`Container({ bgColor: color })`) | surgical — 0 app renders |
264
+ | Update ONE widget that already exists | `useState(key, value, widget, propName)` | surgical — 0 app renders |
265
+ | Show derived text (`Count: 3`) without an app render | derive inside `subscribe` / `onCount` and call `widget.update(…)` | surgical — 0 app renders |
266
+ | Change the structure of the tree from state | `useState` + read it in the component (ternary, `.map()`) | one app render per tick |
267
+ | Update one widget without state at all | `widget.prop = value`, `widget.update(props)`, `ref().update` | surgical — 0 app renders |
268
+ | React to any RamStore change | `subscribeRam` | surgical |
269
+ | Run a side effect on a key | `useWatchState` / `subscribe` | none |
270
+ | Follow the theme or the route | `subscribeTheme` / router `subscribe` | theme re-renders the app |
271
+ | Update a widget's whole prop set | `widget.update(props)` | surgical |
272
+
273
+ ## Related pages
274
+
275
+ - [State, memo & refs](state.md) — the `useState` / `ref` / `memo` reference (previous chapter).
276
+ - [State guide](../guides/state.md) — full `useState` semantics for app state.
277
+ - [Async, ids & logging](async-ids-log.md) — next chapter.
278
+ - [Themes](theme.md) — theme tokens and theme control.
279
+
280
+ ---
281
+
282
+ ## Continue reading
283
+
284
+ - **Previous:** [State, memo & refs](state.md)
285
+ - **Next:** [Async, ids & logging](async-ids-log.md)
286
+ - **Index:** [Tools index](README.md) · [The FletBox Book](../README.md)
287
+
288
+ You are reading **Chapter 10 · Tools & Utilities** (4.5 of 12).
@@ -0,0 +1,103 @@
1
+ # State, memo & refs
2
+
3
+ ## Overview
4
+ Three power-ups for state and performance: `useState`/`useWatchState` for reactive values, `memo`/`memoWithKey`/`clearMemo` for caching expensive results, and `ref` for keeping a live handle to a widget.
5
+
6
+ ## When to use
7
+ - Share values between widgets and re-render on change (`useState`).
8
+ - Cache an expensive computation (parsing, formatting, mapping) with `memo`.
9
+ - Grab a widget instance to update it later (`ref`).
10
+
11
+ ## Import
12
+
13
+ ```javascript
14
+ import { useState, useWatchState, memo, ref } from "flet-box";
15
+ ```
16
+
17
+ ## Functions
18
+
19
+ | Function | Signature | Returns | Description |
20
+ | --- | --- | --- | --- |
21
+ | `useState` | `useState(key, initialState, widget?, propName?)` | `[value, setValue, subscribe]` | Reactive state stored under `key`. `value` is live: pass it straight into a prop and the factory binds that widget to the key, so `setValue` patches only it. Optional `widget` + `propName` binds an existing widget explicitly. |
22
+ | `useWatchState` | `useWatchState(key, callback)` | `() => void` | Watch a state key; returns an unsubscribe function. |
23
+ | `memo` | `memo(fn)` | `T & { cache? }` | Cache a function by its arguments. |
24
+ | `memoWithKey` | `memoWithKey(fn, keyFn)` | `T` | Cache using a custom result key. |
25
+ | `clearMemo` | `clearMemo(memoizedFn)` | `void` | Drop a memoized function's cache. |
26
+ | `ref` | `ref()` | `(widget) => widget & { update }` | Assign to a widget's `ref` prop to get a live handle. |
27
+
28
+ ## Examples
29
+
30
+ ### Everyday example
31
+
32
+ ```javascript
33
+ import { useState, Button, Text, Column } from "flet-box";
34
+
35
+ const [count, setCount] = useState("counter", 0);
36
+
37
+ Column({
38
+ children: [
39
+ Text({ text: `Count: ${count}` }), // derived here → app render
40
+ Text({ text: count }), // passed as a prop → surgical
41
+ Button({
42
+ text: "Add one",
43
+ onPress: () => setCount((previous) => previous + 1),
44
+ }),
45
+ ],
46
+ });
47
+ ```
48
+
49
+ ### Full example — live widget updates via `ref`
50
+
51
+ ```javascript
52
+ import { Text, Button, ref } from "flet-box";
53
+
54
+ const label = ref();
55
+
56
+ Button({
57
+ text: "Rename",
58
+ onPress: () => label.update({ text: "Saved!" }),
59
+ ref: label,
60
+ });
61
+ ```
62
+
63
+ ### Caching with `memo`
64
+
65
+ ```javascript
66
+ import { memo } from "flet-box";
67
+
68
+ const parseData = (raw) => JSON.parse(raw);
69
+ const parseMemo = memo(parseData);
70
+
71
+ const first = parseMemo(jsonText); // runs
72
+ const second = parseMemo(jsonText); // cached, fast
73
+ ```
74
+
75
+ ## Notes
76
+
77
+ - Read the full [State guide](../guides/state.md) for the complete `useState` rules: stable keys, updater functions, immutability, and widget binding.
78
+ - `useState` returns `[value, setValue, subscribe]` — `subscribe(cb)` returns an unsubscribe function.
79
+ - The returned value is **live**: passing it as a prop (`Text({ text: count })`) binds that
80
+ widget to the key and `setValue` patches it alone, with no app render. Computing with it
81
+ inside a component (`` `Count: ${count}` ``, a ternary, a `.map()`) is a structural read,
82
+ and that does re-render the app — see
83
+ [Reactivity in FletBox](reactivity.md).
84
+ - Reads inside an event handler or a `subscribe` callback are never structural, so they
85
+ never cost an app render.
86
+ - `memo` caches by serialized arguments; use `memoWithKey` when you want to control the cache key yourself, and `clearMemo` to free memory when the cache grows too large.
87
+ - `ref()` returns a function for a widget's `ref` prop; the handle gives you `update(props)` to push new props into the widget.
88
+
89
+ ## Related pages
90
+ - [Reactivity in FletBox](reactivity.md) — the full catalogue of every update mechanism.
91
+ - [State guide](../guides/state.md) — full `useState` semantics.
92
+ - [Text & time](text-and-time.md) — previous chapter.
93
+ - [Async, ids & logging](async-ids-log.md) — next chapter.
94
+
95
+ ---
96
+
97
+ ## Continue reading
98
+
99
+ - **Previous:** [Text & time](text-and-time.md)
100
+ - **Next:** [Reactivity in FletBox](reactivity.md)
101
+ - **Index:** [Tools index](README.md) · [The FletBox Book](../README.md)
102
+
103
+ You are reading **Chapter 10 · Tools & Utilities** (4 of 12).
@@ -0,0 +1,92 @@
1
+ # Styling helpers (CSS in JS)
2
+
3
+ ## Overview
4
+ Each styling helper returns a **CSS string** you can pass to a widget prop (`style`, `shadow`, `gradient`, etc.) — so you style widgets without hand-writing raw CSS. Sizes are in px unless the widget converts units for you.
5
+
6
+ ## When to use
7
+ - Build `border`, `margin`, `padding`, `shadow`, `transform`, or `transition` values on the fly.
8
+ - Create gradients and color variations with one line.
9
+
10
+ ## Import
11
+
12
+ ```javascript
13
+ import { border, margin, padding, gradient, shadow, color } from "flet-box";
14
+ ```
15
+
16
+ ## Functions
17
+
18
+ | Function | Signature | Returns | Description |
19
+ | --- | --- | --- | --- |
20
+ | `animation` | `animation(name, duration?, timing?, iteration?)` | `string` | CSS `animation` shorthand. `name` can also be an options object (`{ name, duration, timing, delay, iteration, direction, fillMode }`). |
21
+ | `border` | `border(width = 1, style = "solid", color?)` | `string` | CSS `border` shorthand, e.g. `1px solid #e2e8f0`. |
22
+ | `gradient` | `gradient(type, colors, angle = 135)` | `string` | A CSS gradient background. `type` is `"linear"`, `"circle"` (radial), or `"conic"`; `colors` is an array of color strings. |
23
+ | `margin` / `padding` | `margin(value)` / `padding(value)` | `string` | Spacing CSS. `value` can be a number, a string, or `{ all, horizontal, vertical, top, right, bottom, left }`. |
24
+ | `rgba` | `rgba(r, g, b, a = 1)` | `string` | `rgba(r, g, b, a)` with an alpha default of `1`. |
25
+ | `shadow` | `shadow(x, y, blur, spread, color)` | `string` | A `box-shadow` value. |
26
+ | `transform` | `transform(options)` | `string` | CSS `transform` built from `{ translate, translateX, rotate, scale, skew, matrix, matrix3d, perspective, … }`. |
27
+ | `transition` | `transition(options)` | `string` | CSS `transition` built from `{ property, duration, timing, delay }` (or an array of them). |
28
+ | `filter` | `filter(options)` | `string` | CSS `filter` from `{ blur, brightness, contrast, dropShadow, grayscale, hueRotate, invert, opacity, saturate, sepia }`. |
29
+ | `flex` | `flex(grow, shrink = 1, basis = "0%")` | `string` | CSS `flex` shorthand. `grow` can also be `{ grow, shrink, basis }`. |
30
+ | `grid` | `grid(options)` | `string` | CSS grid template from `{ columns, rows, gap }`; `gap` can be a number or `{ row, column }`. |
31
+ | `color` | `color(value)` | Color object | A mutable, chainable color. See the Color object below. |
32
+ | `stackPosition` | `stackPosition(widget, props)` | `Widget` | Applies absolute positioning/centering to a widget and returns it. |
33
+ | `toREM` / `toPX` | `toREM(value)` / `toPX(value)` | `string \| undefined` | Convert a value between `rem` and `px`. |
34
+ | `setBaseFontSize` | `setBaseFontSize(px)` | `void` | Change the `rem` base (default `16`). |
35
+ | `getBaseFontSize` | `getBaseFontSize()` | `number` | The current `rem` base. |
36
+
37
+ ### The Color object
38
+
39
+ `color(value)` returns a chainable object:
40
+
41
+ `hex()`, `rgb()`, `rgba()` — string forms · `lighten(percent)`, `darken(percent)`, `alpha(value)`, `complement()` — mutate and return the object · `isDark()`, `isLight()` — booleans · `contrast()` — a readable contrast color.
42
+
43
+ ## Examples
44
+
45
+ ### Everyday example
46
+
47
+ ```javascript
48
+ import { Text, border } from "flet-box";
49
+
50
+ Text({
51
+ text: "Card",
52
+ padding: 12,
53
+ border: border(1, "solid", "#cbd5e1"),
54
+ });
55
+ ```
56
+
57
+ ### Full example
58
+
59
+ ```javascript
60
+ import { Container, Text, gradient, shadow, color } from "flet-box";
61
+
62
+ const blue = color("#2563eb").lighten(10);
63
+
64
+ Container({
65
+ padding: 24,
66
+ gradient: gradient("linear", ["#2563eb", "#7c3aed"], 120),
67
+ shadow: shadow(0, 8, 24, 0, "rgba(0,0,0,0.2)"),
68
+ child: Text({ text: blue.hex(), color: "white" }),
69
+ });
70
+ ```
71
+
72
+ ## Notes
73
+
74
+ - These helpers only build **strings** — they do not render anything by themselves. Hand the result to a widget prop or `style`.
75
+ - A number passed to `margin`/`padding` becomes `Npx`; a string is used as-is (so you can write `"1rem"` or `"10px 20px"`).
76
+ - `animation(...)` differs from the [animate function](animation.md): this builds the CSS string, `animate()` runs an animation on an element.
77
+ - The Color object is stateful — create a copy (`color(value)`) when you need two different variations.
78
+
79
+ ## Related pages
80
+ - [Lists, arrays & data](lists.md) — the next chapter.
81
+ - [Visual effects](visual-effects.md) — effects that apply to elements directly.
82
+ - [Animation helpers](animation.md) — run animations on elements.
83
+
84
+ ---
85
+
86
+ ## Continue reading
87
+
88
+ - **Previous:** [Tools index](README.md)
89
+ - **Next:** [Lists, arrays & data](lists.md)
90
+ - **Index:** [Tools index](README.md) · [The FletBox Book](../README.md)
91
+
92
+ You are reading **Chapter 10 · Tools & Utilities** (1 of 12).
@@ -0,0 +1,89 @@
1
+ # Text & time
2
+
3
+ ## Overview
4
+ Small pure helpers for strings, dates, and media time. They take a value and return a formatted one — no side effects.
5
+
6
+ ## When to use
7
+ - Normalize user names or titles before display (`capitalize`, `truncate`).
8
+ - Show friendly dates and "2 minutes ago" labels.
9
+ - Format audio/video positions in player UIs.
10
+
11
+ ## Import
12
+
13
+ ```javascript
14
+ import { capitalize, truncate, formatDate, relativeTime } from "flet-box";
15
+ ```
16
+
17
+ ## Functions
18
+
19
+ ### Text
20
+
21
+ | Function | Signature | Returns | Description |
22
+ | --- | --- | --- | --- |
23
+ | `capitalize` | `capitalize(str)` | `string` | First letter uppercase. |
24
+ | `capitalizeWords` | `capitalizeWords(str)` | `string` | First letter of every word uppercase. |
25
+ | `lowerCase` | `lowerCase(str)` | `string` | All lowercase. |
26
+ | `upperCase` | `upperCase(str)` | `string` | All uppercase. |
27
+ | `reverseString` | `reverseString(str)` | `string` | Reversed text. |
28
+ | `truncate` | `truncate(str, length?, suffix?)` | `string` | Cut to `length` characters and append `suffix` (default `"..."`). |
29
+
30
+ ### Dates & time
31
+
32
+ | Function | Signature | Returns | Description |
33
+ | --- | --- | --- | --- |
34
+ | `formatDate` | `formatDate(date, format?)` | `string` | Format a date/string/number as text. |
35
+ | `relativeTime` | `relativeTime(date)` | `string` | A human label, e.g. `"2 minutes ago"`. |
36
+ | `now` | `now()` | `number` | Timestamp in milliseconds. |
37
+
38
+ ### Media time
39
+
40
+ | Function | Signature | Returns | Description |
41
+ | --- | --- | --- | --- |
42
+ | `formatMediaTime` | `formatMediaTime(seconds)` | `string` | Compact `m:ss` (or `h:mm:ss`). |
43
+ | `formatMediaTimeLong` | `formatMediaTimeLong(seconds)` | `string` | Long form with units. |
44
+ | `getProgressPercent` | `getProgressPercent(current, total)` | `number` | `current/total` as a `0–100` percent. |
45
+ | `percentToSeconds` | `percentToSeconds(percent, total)` | `number` | Inverse of `getProgressPercent`. |
46
+ | `formatMediaProgress` | `formatMediaProgress(current, total)` | `string` | `"current / total"` formatted for a player. |
47
+
48
+ ## Examples
49
+
50
+ ### Everyday example
51
+
52
+ ```javascript
53
+ import { Text, capitalizeWords, relativeTime } from "flet-box";
54
+
55
+ Text({
56
+ text: `${capitalizeWords("hello fletbox")} — ${relativeTime(Date.now() - 120000)}`,
57
+ });
58
+ // "Hello Fletbox — 2 minutes ago"
59
+ ```
60
+
61
+ ### Full example
62
+
63
+ ```javascript
64
+ import { formatMediaProgress, getProgressPercent } from "flet-box";
65
+
66
+ const pct = getProgressPercent(90, 300); // 30
67
+ const label = formatMediaProgress(90, 300); // "1:30 / 5:00"
68
+ ```
69
+
70
+ ## Notes
71
+
72
+ - `formatDate(date, format?)` accepts a `Date`, a timestamp, or a date string; `format` is optional and platform-specific tokens apply.
73
+ - The media-time helpers are used by the [Audio](../widget/Audio.md) and [Video](../widget/Video.md) widgets.
74
+ - All functions return new values — inputs are never modified.
75
+
76
+ ## Related pages
77
+ - [Lists, arrays & data](lists.md) — previous chapter.
78
+ - [State, memo & refs](state.md) — next chapter.
79
+ - [Input validation](validation.md) — more string rules.
80
+
81
+ ---
82
+
83
+ ## Continue reading
84
+
85
+ - **Previous:** [Lists, arrays & data](lists.md)
86
+ - **Next:** [State, memo & refs](state.md)
87
+ - **Index:** [Tools index](README.md) · [The FletBox Book](../README.md)
88
+
89
+ You are reading **Chapter 10 · Tools & Utilities** (3 of 12).
@@ -0,0 +1,102 @@
1
+ # Themes
2
+
3
+ ## Overview
4
+ FletBox ships a built-in color system: `colors` is the palette of the current theme, `palettes` holds all named palettes, and the theme functions switch between light and dark, subscribe to changes, and follow the system preference. A `ThemeProvider` and the `useTheme()` hook wrap them for component-based apps.
5
+
6
+ ## When to use
7
+ - Style widgets with semantic tokens (`colors.surface`, `colors.primary`, `colors.error`) instead of hard-coded hex values.
8
+ - Add a light/dark toggle that re-colors the whole app instantly.
9
+ - Follow the OS dark/light setting automatically.
10
+
11
+ ## Import
12
+
13
+ ```javascript
14
+ import { colors, setTheme, toggleTheme, subscribeTheme, ThemeProvider } from "flet-box";
15
+ ```
16
+
17
+ ## Functions
18
+
19
+ | Function | Signature | Returns | Description |
20
+ | --- | --- | --- | --- |
21
+ | `setTheme` | `setTheme('light' \| 'dark')` | `Record<string, string>` | Switch the active palette. |
22
+ | `getTheme` | `getTheme()` | `string` | The active theme name. |
23
+ | `toggleTheme` | `toggleTheme()` | `string` | Flip light/dark and return the new name. |
24
+ | `getColor` | `getColor(name, opacity?)` | `string` | Resolve a token (optionally with alpha). |
25
+ | `subscribeTheme` | `subscribeTheme(callback)` | `() => void` | Subscribe to theme changes; returns an unsubscribe function. |
26
+ | `applySystemTheme` | `applySystemTheme()` | `string` | Apply light/dark from the OS preference. |
27
+ | `watchSystemTheme` | `watchSystemTheme()` | `() => void` | Follow the OS preference live; returns an unsubscribe function. |
28
+ | `ThemeProvider` | `ThemeProvider({ children, theme? })` | `Widget` | Provide a theme (defaults to system) for a subtree. |
29
+ | `useTheme` | `useTheme()` | object | `{ colors, setTheme, getTheme, toggleTheme, getColor, applySystemTheme }`. |
30
+
31
+ Plus the constants: `colors` (current palette) and `palettes` (all named palettes, keyed by name).
32
+
33
+ ## Color tokens
34
+
35
+ Every token is available on `colors`:
36
+
37
+ - **Brand:** `primary`, `secondary`, `info`, `success`, `warning`, `danger`, `error`.
38
+ - **Surfaces:** `background`, `surface`, `card`, `overlay`.
39
+ - **Text:** `text`, `textSecondary`, `textDisabled`, `divider`, `border`, `shadow`.
40
+ - **Tints:** `gray50` … `gray900`, plus the soft backgrounds `errorBg`, `successBg`, `warningBg`, `infoBg`.
41
+
42
+ The `dark` palette overrides the surface/text tokens; `gray*` and brand colors also shift to keep contrast. Read [the palette walkthrough](../guides/utilities.md) for usage patterns.
43
+
44
+ ## Examples
45
+
46
+ ### Everyday example
47
+
48
+ ```javascript
49
+ import { Button, colors, toggleTheme } from "flet-box";
50
+
51
+ Button({
52
+ text: "Switch theme",
53
+ onPress: () => toggleTheme(),
54
+ });
55
+
56
+ const card = { backgroundColor: colors.surface, color: colors.text };
57
+ ```
58
+
59
+ ### Full example
60
+
61
+ ```javascript
62
+ import { runApp, Container, Column, Button, Text, useTheme } from "flet-box";
63
+
64
+ const ThemedApp = () => {
65
+ const theme = useTheme();
66
+
67
+ return Container({
68
+ backgroundColor: theme.colors.background,
69
+ padding: 24,
70
+ child: Column({
71
+ gap: 12,
72
+ children: [
73
+ Text({ text: `Theme: ${theme.getTheme()}`, color: theme.colors.text }),
74
+ Button({ text: "Toggle", onPress: () => theme.toggleTheme() }),
75
+ ],
76
+ }),
77
+ });
78
+ };
79
+
80
+ runApp(ThemeProvider({ children: ThemedApp() }), "root");
81
+ ```
82
+
83
+ ## Notes
84
+
85
+ - The shared palette tokens are updated across all widgets when you switch themes; widgets referencing `colors.*` at build time pick up the active palette.
86
+ - `watchSystemTheme()` and `applySystemTheme()` read `prefers-color-scheme`; both return/need cleanup to release listeners.
87
+ - `subscribeTheme(callback)` fires with the new palette object — remember to unsubscribe when a feature is destroyed.
88
+
89
+ ## Related pages
90
+ - [Device & environment](device.md) — previous chapter.
91
+ - [Markdown, code & HTML](markdown.md) — next chapter.
92
+ - [Colors and utilities guide](../guides/utilities.md) — palette walkthrough.
93
+
94
+ ---
95
+
96
+ ## Continue reading
97
+
98
+ - **Previous:** [Device & environment](device.md)
99
+ - **Next:** [Markdown, code & HTML](markdown.md)
100
+ - **Index:** [Tools index](README.md) · [The FletBox Book](../README.md)
101
+
102
+ You are reading **Chapter 10 · Tools & Utilities** (7 of 12).