flet-box 1.0.5 → 1.0.7

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 (94) hide show
  1. package/README.md +28 -1
  2. package/bin/cli.js +85 -18
  3. package/bin/commands/auditFix.js +682 -0
  4. package/bin/commands/checkApi.js +7 -5
  5. package/bin/commands/create.js +40 -10
  6. package/bin/commands/createBundle.js +54 -1
  7. package/bin/commands/images.js +349 -0
  8. package/bin/commands/pruneAssets.js +170 -0
  9. package/bin/commands/release.js +34 -1
  10. package/bin/commands/runCloudflare.js +47 -9
  11. package/bin/commands/runServer.js +81 -24
  12. package/bin/commands/toPng.js +346 -0
  13. package/bin/commands/unpack.js +1173 -0
  14. package/bin/ota/loader.js +90 -0
  15. package/bin/utils/appSources.js +101 -0
  16. package/bin/utils/bundleConfig.js +166 -0
  17. package/bin/utils/dtsProps.js +147 -0
  18. package/bin/utils/imageTools.js +271 -0
  19. package/bin/utils/images/bmp.js +99 -0
  20. package/bin/utils/images/gif.js +244 -0
  21. package/bin/utils/images/index.js +163 -0
  22. package/bin/utils/images/jpeg.js +643 -0
  23. package/bin/utils/images/png.js +486 -0
  24. package/bin/utils/images/unsupported.js +29 -0
  25. package/bin/utils/neutralise.js +109 -6
  26. package/bin/utils/templates.js +307 -64
  27. package/docs/cli/README.md +82 -0
  28. package/docs/guides/online-updates.md +57 -9
  29. package/docs/widget/Accordion.md +1 -0
  30. package/docs/widget/AdMob.md +1 -0
  31. package/docs/widget/AdSense.md +1 -0
  32. package/docs/widget/AnimatedBox.md +1 -0
  33. package/docs/widget/AnimatedText.md +1 -0
  34. package/docs/widget/Audio.md +2 -1
  35. package/docs/widget/Avatar.md +12 -3
  36. package/docs/widget/Badge.md +1 -0
  37. package/docs/widget/Button.md +1 -0
  38. package/docs/widget/Card.md +1 -0
  39. package/docs/widget/Carousel.md +1 -0
  40. package/docs/widget/Chart.md +1 -0
  41. package/docs/widget/Checkbox.md +1 -0
  42. package/docs/widget/Chip.md +1 -0
  43. package/docs/widget/CircularBar.md +1 -0
  44. package/docs/widget/CircularChart.md +1 -0
  45. package/docs/widget/CodeViewer.md +1 -0
  46. package/docs/widget/Column.md +1 -0
  47. package/docs/widget/Container.md +1 -0
  48. package/docs/widget/DataTable.md +1 -0
  49. package/docs/widget/Divider.md +1 -0
  50. package/docs/widget/DraggBox.md +3 -2
  51. package/docs/widget/Dropdown.md +3 -0
  52. package/docs/widget/DroppBox.md +2 -1
  53. package/docs/widget/FloatingActionButton.md +1 -0
  54. package/docs/widget/GridView.md +1 -0
  55. package/docs/widget/Icon.md +1 -0
  56. package/docs/widget/Image.md +11 -3
  57. package/docs/widget/Input.md +1 -0
  58. package/docs/widget/InstallButton.md +1 -0
  59. package/docs/widget/ListTile.md +1 -0
  60. package/docs/widget/ListView.md +1 -0
  61. package/docs/widget/Maps.md +188 -0
  62. package/docs/widget/Markdown.md +1 -0
  63. package/docs/widget/MatrixRain.md +1 -0
  64. package/docs/widget/Pagination.md +1 -0
  65. package/docs/widget/ParallaxBox.md +1 -0
  66. package/docs/widget/ProgressBar.md +1 -0
  67. package/docs/widget/QRCode.md +1 -0
  68. package/docs/widget/README.md +12 -10
  69. package/docs/widget/Radio.md +1 -0
  70. package/docs/widget/Rating.md +1 -0
  71. package/docs/widget/Row.md +1 -0
  72. package/docs/widget/Skeleton.md +1 -0
  73. package/docs/widget/Slider.md +1 -0
  74. package/docs/widget/Stack.md +1 -0
  75. package/docs/widget/Stepper.md +1 -0
  76. package/docs/widget/Switch.md +1 -0
  77. package/docs/widget/Text.md +1 -0
  78. package/docs/widget/Tooltip.md +11 -13
  79. package/docs/widget/TreeView.md +1 -0
  80. package/docs/widget/Video.md +3 -2
  81. package/package.json +2 -2
  82. package/src/core/hmr-client.js +129 -34
  83. package/src/index.d.ts +132 -0
  84. package/src/index.js +1 -0
  85. package/src/navigations/AppBar.js +15 -4
  86. package/src/navigations/Drawer.js +9 -1
  87. package/src/widget-factory/translateProps.js +32 -1
  88. package/src/widgets/Avatar.js +228 -131
  89. package/src/widgets/Container.js +13 -0
  90. package/src/widgets/Dropdown.js +33 -1
  91. package/src/widgets/Image.js +102 -3
  92. package/src/widgets/Maps.js +1495 -0
  93. package/src/widgets/Tooltip.js +12 -2
  94. package/src/widgets/index.js +1 -0
@@ -15,7 +15,11 @@ you edit source code
15
15
  v
16
16
  git tag v1.0.1 pushed to GitHub
17
17
  |
18
- | GitHub Actions (on: tags v*)
18
+ | GitHub Actions — the web job deploys www/ on every push to main,
19
+ | and keeps www/config.json only when that commit carries a v* tag.
20
+ | A run started BY THE TAG cannot deploy (the github-pages environment
21
+ | accepts the default branch only), so publish the feed with one
22
+ | manual run on main: Actions → Deploy → Run workflow.
19
23
  v
20
24
  www/ deployed to GitHub Pages <- the ONLY place an update is published
21
25
  |
@@ -33,7 +37,7 @@ loader.js fetches <update.url>/config.json
33
37
 
34
38
  Three invariants make this safe:
35
39
 
36
- 1. **Updates are published only by release tags.** Ordinary `git push` runs CI (so breakage is caught) but never publishes, so a half-finished test build can never reach your users.
40
+ 1. **Only a release tag can be offered as an update.** The web workflow publishes your site on every push to main, so the preview is never stale — but it deletes `www/config.json` from any build whose commit carries no `v*` tag. With no manifest to read, an installed app logs `no remote config (offline or not deployed yet)` and keeps running the release it already has. So the site is live for reading while the update feed stays on tags, and a half-finished test build can never reach your users.
37
41
  2. **The app verifies integrity.** A downloaded bundle is executed only if its sha256 matches the hash in `config.json`.
38
42
  3. **The app always launches.** Offline, a wrong URL, a hash mismatch, or a declined dialog all fall back to the bundle already on the device. OTA can never brick the app.
39
43
 
@@ -54,7 +58,7 @@ flet-box release --push # also push the commit and the tag to origin
54
58
  2. Bumps the version in `package.json` using the odometer scheme below.
55
59
  3. Rebuilds the production bundle (`www/`), which regenerates `www/config.json` with the new version and the sha256 of the new bundle.
56
60
  4. Commits the version bump and creates the annotated tag `vX.Y.Z`.
57
- 5. Optionally pushes (`--push`), which triggers the CI deploy.
61
+ 5. Optionally pushes (`--push`), which deploys the site. The tag is what turns the update feed on, and a tag run cannot deploy by itself — after pushing, start the workflow once more from Actions (Run workflow, on main) and that build keeps `www/config.json`.
58
62
 
59
63
  If the build fails, the version bump is rolled back so a failed release leaves no trace.
60
64
 
@@ -79,6 +83,10 @@ Every bundle ships a small manifest at `www/config.json`. This is what a running
79
83
  "builtAt": "2026-10-11T19:53:24.524Z",
80
84
  "bundle": "src/app.js",
81
85
  "size": 36132,
86
+ "assets": [
87
+ { "path": "docs/widget/Maps.md", "hash": "b0f2…", "size": 4213 },
88
+ { "path": "data/catalog.json", "hash": "7ac9…", "size": 980 }
89
+ ],
82
90
  "minNativeVersion": "0.0.0",
83
91
  "update": {
84
92
  "url": "https://kuko53348.github.io/my-app/",
@@ -96,6 +104,7 @@ Every bundle ships a small manifest at `www/config.json`. This is what a running
96
104
  | `hash` | sha256 of the bundle. **This is what drives the update decision**, not the version. |
97
105
  | `bundle` | Path of the bundle relative to the update URL. |
98
106
  | `size` | Bundle size in bytes (shown in the dialog as KB). |
107
+ | `assets` | Extra files this release publishes (see [Shipping extra files](#shipping-extra-files-release-assets)). Each entry is verified by its own `hash`; the bundle `hash` never covers them. |
99
108
  | `minNativeVersion` | Oldest native shell this bundle supports. Older shells are told to update from the store instead. |
100
109
  | `update.url` | Where `config.json` and the bundle are published (your GitHub Pages site). |
101
110
  | `update.mode` | `prompt` (ask the user, default) or `auto` (download silently). |
@@ -127,6 +136,44 @@ To configure it by hand, create `fletbox.config.json`:
127
136
  }
128
137
  ```
129
138
 
139
+ ## Shipping extra files (release assets)
140
+
141
+ OTA swaps **one file**: `src/app.js`. Anything your app reads from disk at runtime — markdown pages, JSON data, per-tenant config — keeps the copy baked into the build the device installed. So an app installed three releases ago can run a bundle that was updated over the air and still be missing files the current catalog refers to. The symptom is not a crash, it is a "this page does not exist" for content that clearly exists online.
142
+
143
+ Declare those files in `fletbox.config.json` and they become part of every release:
144
+
145
+ ```json
146
+ {
147
+ "update": {
148
+ "assets": ["docs/**/*.md", "data/catalog.json"]
149
+ }
150
+ }
151
+ ```
152
+
153
+ Globs are relative to the project root and support `**`, `*` and `?`. At build time `flet-box createBundle` copies each match into `www/` at the same path, hashes it, and lists it in the manifest's `assets` array — which is also what your CI publishes to GitHub Pages.
154
+
155
+ The app then reads them through the loader instead of `fetch`:
156
+
157
+ ```js
158
+ const asset = await globalThis.__FLETBOX_OTA_ASSET__?.("docs/widget/Maps.md");
159
+ const markdown = asset && asset.ok ? await asset.text() : await (await fetch("./docs/widget/Maps.md")).text();
160
+ ```
161
+
162
+ It returns a `Response`, or `null` when the file exists in neither place. For each path it tries, in order:
163
+
164
+ 1. **The OTA cache** — the published copy already downloaded on a previous launch, so repeat reads and offline reads are free.
165
+ 2. **The published release** at `update.url`. The bytes are verified against the manifest hash; a mismatch is logged and ignored rather than trusted.
166
+ 3. **The copy inside this build**, exactly as `fetch` would return it.
167
+
168
+ Because step 3 is always available, the helper can never leave you with less than you had before: an unpublished file, an offline device, or a tampered download all serve the shipped copy.
169
+
170
+ Two rules keep this honest:
171
+
172
+ - **A file only updates OTA if it is listed.** An empty or missing `assets` list means the manifest publishes nothing but the bundle.
173
+ - **Bundle and assets are verified separately.** `hash` is the bundle; each asset entry carries its own hash, so one corrupted file cannot serve unverified content — and a bad asset never blocks a good bundle update.
174
+
175
+ **Old shells have no helper.** `loader.js` is baked into the native build and OTA replaces the bundle, never the loader — so a store build made before this feature cannot fetch published assets, however many updates it accepts. That is what the `fetch` fallback in the snippet is for, and it closes only when you ship a new store build.
176
+
130
177
  ## What the app does on launch
131
178
 
132
179
  `www/loader.js` (wired in automatically as the module entry of every production bundle) runs **before** your app:
@@ -141,17 +188,17 @@ The dialog is plain HTML/CSS rendered by the loader itself, so it appears before
141
188
 
142
189
  ## What OTA can and cannot change
143
190
 
144
- OTA replaces the **web layer only** — the files inside `www/`.
191
+ OTA replaces the **web layer only**: the `src/app.js` bundle plus whatever you list in `update.assets`. Everything else on the device is what the store build shipped.
145
192
 
146
193
  | Updates over the air | Needs a new store submission |
147
194
  | --- | --- |
148
195
  | UI, screens, widgets, styles | New native plugins / Capacitor version |
149
196
  | App logic and routing | New Android/iOS permissions |
150
- | Text, images, fonts, assets in `www/` | Changes to `AndroidManifest.xml` / `Info.plist` |
197
+ | Release assets declared in `update.assets` (markdown, JSON, images) | Changes to `AndroidManifest.xml` / `Info.plist` |
151
198
  | Bug fixes and new JS features | Native code or signing changes |
152
199
  | Anything inside the bundle | Anything that changes the app's identity or purpose |
153
200
 
154
- Rule of thumb: if the change lives in `src/` (your FletBox code) it ships OTA; if it lives in the native shell it goes through the store. The `minNativeVersion` field exists for the boundary case where a new web bundle needs a newer shell: older shells skip the OTA and you direct users to the store update.
201
+ Rule of thumb: if the change lives in `src/` (your FletBox code) it ships OTA; if it lives in the native shell it goes through the store. A data or content file ships OTA only when it is listed in `update.assets` — otherwise the device keeps the copy its store build baked in. The `minNativeVersion` field exists for the boundary case where a new web bundle needs a newer shell: older shells skip the OTA and you direct users to the store update.
155
202
 
156
203
  ## Store policy (read this before relying on OTA)
157
204
 
@@ -170,19 +217,20 @@ The generated service worker uses a hybrid strategy so OTA and offline coexist:
170
217
  - **Static assets** (fonts, icons, images, css): cache-first, since they are immutable per release.
171
218
  - **Cross-origin requests** (your remote `config.json` and bundle) are left to the network and are never cached by the worker.
172
219
 
173
- Downloaded OTA bundles live in a **separate** cache (`fletbox-ota`) owned by the loader, so a service-worker update never wipes an installed OTA bundle.
220
+ Downloaded OTA bundles **and** release assets live in a **separate** cache (`fletbox-ota`) owned by the loader, so a service-worker update never wipes an installed OTA bundle. The loader fetches published assets with `cache: no-store`, so a stale service-worker copy of a file in `www/` cannot shadow the published one.
174
221
 
175
222
  ## Requirements and troubleshooting
176
223
 
177
- Requirements: a **public** GitHub repository, GitHub Pages enabled (Settings → Pages → Source = "GitHub Actions"), and the workflow from `flet-box create` (it already deploys `www/` on `v*` tags). GitHub Pages and Actions are free for public repos.
224
+ Requirements: a **public** GitHub repository, GitHub Pages enabled (Settings → Pages → Source = "GitHub Actions"), and the workflow from `flet-box create` (`.github/workflows/build.yml` publishes `www/` on every push, and publishes the OTA manifest only for a `v*` tag; Android and iOS live in `build-native.yml`, which starts by hand). GitHub Pages and Actions are free for public repos.
178
225
 
179
226
  | Symptom | Likely cause |
180
227
  | --- | --- |
181
228
  | Build prints "THIS APP DOES NOT SUPPORT OTA UPDATES" | No public GitHub repo is reachable from the project (no `origin` remote, a non-GitHub remote, or nothing configured). The app still works — it runs the bundle it shipped with. Create the repo, push it, and rebuild. |
182
- | No dialog, app runs old code | `update.url` missing/incorrect, or the tag was never pushed (CI never published). |
229
+ | No dialog, app runs old code | `update.url` missing/incorrect, or no `v*` tag was published — a normal push updates the site but deliberately leaves the manifest out, so there is nothing to compare against. |
183
230
  | `hash mismatch` in console | The published `config.json` and bundle are from different builds. Re-run `flet-box release` so both are published together. |
184
231
  | Dialog appears but download fails | The Pages site is not reachable/CORS-blocked from the app origin. Pages serves with permissive headers; check you are not behind a proxy that strips them. |
185
232
  | Update applies but reverts on next launch | Cache Storage unavailable (private browsing). The bundle runs once but cannot persist. |
233
+ | A data/content file is missing on the device but published online | It is not listed in `update.assets`, so OTA only ever replaced the bundle — or the installed shell predates release assets, in which case the app must fall back to its own copy. |
186
234
  | Want to force a clean state | Clear site data for the app origin (this removes the OTA cache and localStorage entry). |
187
235
 
188
236
  Because the decision is hash-based, two builds of identical source produce the same hash and no spurious update is offered; only a real content change triggers the dialog.
@@ -58,6 +58,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
58
58
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
59
59
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
60
60
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
61
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
61
62
  | `color` | `Color` | Foreground color, usually the text or icon color. |
62
63
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
63
64
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -57,6 +57,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
57
57
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
58
58
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
59
59
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
60
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
60
61
  | `color` | `Color` | Foreground color, usually the text or icon color. |
61
62
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
62
63
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -51,6 +51,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
51
51
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
52
52
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
53
53
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
54
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
54
55
  | `color` | `Color` | Foreground color, usually the text or icon color. |
55
56
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
56
57
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -45,6 +45,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
45
45
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
46
46
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
47
47
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
48
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
48
49
  | `color` | `Color` | Foreground color, usually the text or icon color. |
49
50
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
50
51
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -41,6 +41,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
41
41
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
42
42
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
43
43
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
44
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
44
45
  | `color` | `Color` | Foreground color, usually the text or icon color. |
45
46
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
46
47
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -48,6 +48,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
48
48
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
49
49
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
50
50
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
51
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
51
52
  | `color` | `Color` | Foreground color, usually the text or icon color. |
52
53
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
53
54
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -132,4 +133,4 @@ const track = Audio({
132
133
  - **Next:** [Video](Video.md)
133
134
  - **Index:** [Widget index](README.md) · [Start here](START_HERE.md)
134
135
 
135
- You are reading **Chapter 7 · Media and drag & drop** (1 of 4).
136
+ You are reading **Chapter 7 · Media and drag & drop** (1 of 5).
@@ -1,6 +1,6 @@
1
1
  # Avatar
2
2
 
3
- A compact identity chip that shows a profile image, the person's initials, or a fallback icon. It falls back gracefully when no image is available.
3
+ A compact identity chip that shows a profile image, the person's initials, or a fallback icon. It falls back gracefully when no image is available and can carry a presence dot.
4
4
 
5
5
  ## When to use it
6
6
 
@@ -29,6 +29,8 @@ const user = Avatar({
29
29
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
30
30
  | `textColor` | `Color` | Color of the button label. |
31
31
  | `shape` | `'circle' \| 'rounded' \| 'square'` | The `shape` value for the widget. |
32
+ | `online` | `boolean` | Shows a presence dot in the corner of the avatar. |
33
+ | `badgeColor` | `Color` | Color of the presence dot. |
32
34
  | `onPress` | `(widget: Widget) => void` | Callback fired when the widget is pressed. Alias: `onClick`. |
33
35
 
34
36
  ### Common props
@@ -43,6 +45,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
43
45
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
44
46
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
45
47
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
48
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
46
49
  | `color` | `Color` | Foreground color, usually the text or icon color. |
47
50
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
48
51
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -69,6 +72,7 @@ const user = Avatar({
69
72
  name: "Ada Lovelace",
70
73
  size: 48,
71
74
  shape: "circle",
75
+ online: true,
72
76
  });
73
77
  ```
74
78
 
@@ -84,14 +88,17 @@ const user = Avatar({
84
88
  shape: "rounded",
85
89
  bgColor: "#2563eb",
86
90
  textColor: "#ffffff",
91
+ online: true,
92
+ badgeColor: "#f59e0b",
87
93
  onPress: () => console.log("open profile"),
88
94
  });
89
95
  ```
90
96
 
91
97
  ## Tips
92
98
 
93
- - Provide both src and name so initials render if the image fails.
99
+ - Provide both src and name so the initials are ready if you swap the image out.
94
100
  - Use shape: "circle" for people and shape: "rounded" for organizations or bots.
101
+ - Leave badgeColor at its default for an available dot; pass another color to signal away or busy.
95
102
 
96
103
  ## Accessibility
97
104
 
@@ -99,8 +106,10 @@ const user = Avatar({
99
106
 
100
107
  ## Behavior
101
108
 
102
- - When src is missing or errors, the widget automatically shows initials from name.
109
+ - Content is picked once, in order: src, then icon, then initials from name, then a generic person icon. A failing src does not swap the image for initials by itself.
103
110
  - onPress makes the whole avatar interactive, which is useful for opening profiles.
111
+ - online draws a dot in the bottom-right corner. It sits inside the edge and is sized from the avatar, so a circular avatar clips it no more than a square one.
112
+ - Every own prop applies again through update() or updateContent(), so an avatar bound to a state key changes without rebuilding the element.
104
113
 
105
114
  ## Related widgets
106
115
 
@@ -47,6 +47,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
47
47
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
48
48
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
49
49
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
50
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
50
51
  | `color` | `Color` | Foreground color, usually the text or icon color. |
51
52
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
52
53
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -54,6 +54,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
54
54
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
55
55
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
56
56
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
57
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
57
58
  | `color` | `Color` | Foreground color, usually the text or icon color. |
58
59
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
59
60
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -45,6 +45,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
45
45
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
46
46
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
47
47
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
48
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
48
49
  | `color` | `Color` | Foreground color, usually the text or icon color. |
49
50
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
50
51
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -54,6 +54,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
54
54
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
55
55
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
56
56
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
57
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
57
58
  | `color` | `Color` | Foreground color, usually the text or icon color. |
58
59
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
59
60
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -60,6 +60,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
60
60
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
61
61
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
62
62
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
63
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
63
64
  | `color` | `Color` | Foreground color, usually the text or icon color. |
64
65
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
65
66
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -41,6 +41,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
41
41
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
42
42
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
43
43
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
44
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
44
45
  | `color` | `Color` | Foreground color, usually the text or icon color. |
45
46
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
46
47
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -47,6 +47,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
47
47
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
48
48
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
49
49
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
50
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
50
51
  | `color` | `Color` | Foreground color, usually the text or icon color. |
51
52
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
52
53
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -72,6 +72,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
72
72
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
73
73
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
74
74
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
75
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
75
76
  | `color` | `Color` | Foreground color, usually the text or icon color. |
76
77
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
77
78
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -67,6 +67,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
67
67
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
68
68
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
69
69
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
70
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
70
71
  | `color` | `Color` | Foreground color, usually the text or icon color. |
71
72
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
72
73
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -47,6 +47,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
47
47
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
48
48
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
49
49
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
50
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
50
51
  | `color` | `Color` | Foreground color, usually the text or icon color. |
51
52
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
52
53
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -44,6 +44,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
44
44
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
45
45
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
46
46
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
47
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
47
48
  | `color` | `Color` | Foreground color, usually the text or icon color. |
48
49
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
49
50
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -51,6 +51,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
51
51
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
52
52
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
53
53
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
54
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
54
55
  | `color` | `Color` | Foreground color, usually the text or icon color. |
55
56
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
56
57
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -55,6 +55,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
55
55
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
56
56
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
57
57
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
58
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
58
59
  | `color` | `Color` | Foreground color, usually the text or icon color. |
59
60
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
60
61
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -37,6 +37,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
37
37
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
38
38
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
39
39
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
40
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
40
41
  | `color` | `Color` | Foreground color, usually the text or icon color. |
41
42
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
42
43
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -46,6 +46,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
46
46
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
47
47
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
48
48
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
49
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
49
50
  | `color` | `Color` | Foreground color, usually the text or icon color. |
50
51
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
51
52
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -122,8 +123,8 @@ const task = DraggBox({
122
123
 
123
124
  ## Continue reading
124
125
 
125
- - **Previous:** [Video](Video.md)
126
+ - **Previous:** [Maps](Maps.md)
126
127
  - **Next:** [DroppBox](DroppBox.md)
127
128
  - **Index:** [Widget index](README.md) · [Start here](START_HERE.md)
128
129
 
129
- You are reading **Chapter 7 · Media and drag & drop** (3 of 4).
130
+ You are reading **Chapter 7 · Media and drag & drop** (4 of 5).
@@ -53,6 +53,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
53
53
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
54
54
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
55
55
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
56
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
56
57
  | `color` | `Color` | Foreground color, usually the text or icon color. |
57
58
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
58
59
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -112,6 +113,7 @@ const country = Dropdown({
112
113
 
113
114
  - Pass objects with value/label when the value sent to onChange must differ from the text shown, such as an id versus a name.
114
115
  - Set clearable: true to give users an explicit way back to no selection.
116
+ - Bind a useState value to `value` to move the selection from code, such as auto-picking a default after a lookup; pass `''` to clear it back to the placeholder.
115
117
 
116
118
  ## Accessibility
117
119
 
@@ -121,6 +123,7 @@ const country = Dropdown({
121
123
 
122
124
  - The option menu is portaled to document.body by default and repositions on scroll/resize; set portal: false to keep it inline.
123
125
  - The returned element exposes value, open(), close() and update(), and removes its listeners on unmount.
126
+ - A value applied from outside — a bound state change or `dropdown.value = x` — re-fires onChange when it differs from the current selection, and is ignored when it is the same, so a state → value → onChange → state loop settles after one pass.
124
127
 
125
128
  ## Related widgets
126
129
 
@@ -61,6 +61,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
61
61
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
62
62
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
63
63
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
64
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
64
65
  | `color` | `Color` | Foreground color, usually the text or icon color. |
65
66
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
66
67
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -149,4 +150,4 @@ const zone = DroppBox({
149
150
  - **Next:** [Scaffold](Scaffold.md) — Chapter 8 · App navigation
150
151
  - **Index:** [Widget index](README.md) · [Start here](START_HERE.md)
151
152
 
152
- You are reading **Chapter 7 · Media and drag & drop** (4 of 4).
153
+ You are reading **Chapter 7 · Media and drag & drop** (5 of 5).
@@ -45,6 +45,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
45
45
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
46
46
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
47
47
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
48
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
48
49
  | `color` | `Color` | Foreground color, usually the text or icon color. |
49
50
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
50
51
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -54,6 +54,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
54
54
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
55
55
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
56
56
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
57
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
57
58
  | `color` | `Color` | Foreground color, usually the text or icon color. |
58
59
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
59
60
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -39,6 +39,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
39
39
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
40
40
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
41
41
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
42
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
42
43
  | `color` | `Color` | Foreground color, usually the text or icon color. |
43
44
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
44
45
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -1,6 +1,6 @@
1
1
  # Image
2
2
 
3
- Renders an image element from a URL or path. Supports fixed sizing, object-fit modes, and a circular crop for avatars.
3
+ Renders an image element from a URL or path. Supports fixed sizing, object-fit modes, a circular crop for avatars, and saving on click.
4
4
 
5
5
  ## When to use it
6
6
 
@@ -29,6 +29,8 @@ const photo = Image({
29
29
  | `height` | `Size` | Height of the widget. A number is pixels; a string uses any CSS unit. |
30
30
  | `fit` | `'cover' \| 'contain' \| 'fill' \| 'none' \| 'scale-down'` | How the image fits its box (CSS `object-fit`). |
31
31
  | `circular` | `boolean` | Clips the image into a circle. |
32
+ | `downloadable` | `boolean` | Makes a click on the image save it. |
33
+ | `downloadFileName` | `string` | File name suggested when saving; defaults to the name in the URL. |
32
34
  | `onLoad` | `(widget: Widget) => void` | Fired when the media finishes loading. |
33
35
  | `onError` | `(widget: Widget) => void` | Fired when the media fails to load. |
34
36
  | `onPress` | `(widget: Widget) => void` | Callback fired when the widget is pressed. Alias: `onClick`. |
@@ -45,6 +47,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
45
47
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
46
48
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
47
49
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
50
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
48
51
  | `color` | `Color` | Foreground color, usually the text or icon color. |
49
52
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
50
53
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -82,12 +85,14 @@ const cover = Image({
82
85
  import { Image } from "flet-box";
83
86
 
84
87
  const teamPhoto = Image({
85
- src: "/team/anna.jpg",
88
+ src: "/team/anna.jpg?v=3",
86
89
  alt: "Anna at the keynote",
87
90
  width: 96,
88
91
  height: 96,
89
92
  fit: "cover",
90
93
  circular: true,
94
+ downloadable: true,
95
+ downloadFileName: "anna-keynote.jpg",
91
96
  onLoad: () => console.log("loaded"),
92
97
  onError: () => console.error("failed"),
93
98
  onPress: () => console.log("open profile"),
@@ -98,6 +103,7 @@ const teamPhoto = Image({
98
103
 
99
104
  - Set both width and height with fit: "cover" to avoid stretched or squashed art.
100
105
  - Use circular: true instead of a full borderRadius when you want a perfect avatar crop.
106
+ - downloadable only saves the file when the image is same-origin or CORS-served; anything else opens in a new tab instead of replacing your app.
101
107
 
102
108
  ## Accessibility
103
109
 
@@ -106,7 +112,9 @@ const teamPhoto = Image({
106
112
  ## Behavior
107
113
 
108
114
  - onLoad fires once the image decodes; onError fires when the src fails to load.
109
- - onPress is attached to the wrapper, so the image behaves like a tappable tile.
115
+ - onPress is attached to the image itself, so the image behaves like a tappable tile.
116
+ - With downloadable: true a click saves the image under downloadFileName, or under the file name in the URL when that prop is absent.
117
+ - Both download props stay live: update() can turn the offer on or off and rename it, and a downloadable bound to a useState key follows the store.
110
118
 
111
119
  ## Related widgets
112
120
 
@@ -65,6 +65,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
65
65
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
66
66
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
67
67
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
68
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
68
69
  | `color` | `Color` | Foreground color, usually the text or icon color. |
69
70
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
70
71
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -43,6 +43,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
43
43
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
44
44
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
45
45
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
46
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
46
47
  | `color` | `Color` | Foreground color, usually the text or icon color. |
47
48
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
48
49
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -54,6 +54,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
54
54
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
55
55
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
56
56
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
57
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
57
58
  | `color` | `Color` | Foreground color, usually the text or icon color. |
58
59
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
59
60
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |
@@ -53,6 +53,7 @@ Every widget also accepts these shared props — see [common props](COMMON_PROPS
53
53
  | `padding` | `Padding` | Space inside the widget, between its content and its border. |
54
54
  | `margin` | `Margin` | Space outside the widget, between it and its neighbors. |
55
55
  | `bgColor` | `Color` | Background color. Alias: `backgroundColor`. |
56
+ | `backgroundColor` | `Color` | Background color. Alias of `bgColor`. |
56
57
  | `color` | `Color` | Foreground color, usually the text or icon color. |
57
58
  | `borderRadius` | `number \| string` | Rounds the corners of the widget. |
58
59
  | `elevation` | `number` | Shadow depth. Higher values lift the widget off the page. |