flet-box 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/README.md +16 -12
  2. package/bin/commands/checkUi.js +276 -45
  3. package/bin/commands/create.js +8 -1
  4. package/bin/commands/createBundle.js +18 -6
  5. package/bin/commands/createScreen.js +1 -1
  6. package/bin/commands/runBundle.js +1 -1
  7. package/bin/utils/bundleConfig.js +47 -3
  8. package/bin/utils/colors.js +0 -8
  9. package/bin/utils/helpers.js +0 -5
  10. package/bin/utils/images/jpeg.js +2 -3
  11. package/bin/utils/templates.js +101 -85
  12. package/docs/README.md +24 -1
  13. package/docs/cli/README.md +51 -10
  14. package/docs/guides/README.md +5 -2
  15. package/docs/guides/accessibility.md +282 -0
  16. package/docs/guides/clean-ui.md +53 -8
  17. package/docs/guides/first-app.md +6 -7
  18. package/docs/guides/minification-and-protection.md +1 -1
  19. package/docs/guides/mobile-and-platforms.md +51 -0
  20. package/docs/guides/online-updates.md +18 -6
  21. package/docs/guides/utilities.md +116 -4
  22. package/docs/guides/widget-structure.md +30 -0
  23. package/docs/tools/README.md +1 -1
  24. package/docs/tools/animation.md +134 -11
  25. package/docs/tools/device.md +75 -3
  26. package/docs/tools/text-and-time.md +116 -6
  27. package/docs/widget/Accordion.md +2 -0
  28. package/docs/widget/AnimatedBox.md +46 -20
  29. package/docs/widget/AnimatedText.md +13 -7
  30. package/docs/widget/Badge.md +1 -0
  31. package/docs/widget/COMMON_PROPS.md +26 -3
  32. package/docs/widget/Chart.md +1 -0
  33. package/docs/widget/Checkbox.md +7 -1
  34. package/docs/widget/CircularChart.md +2 -0
  35. package/docs/widget/Drawer.md +1 -0
  36. package/docs/widget/GridView.md +4 -1
  37. package/docs/widget/Input.md +2 -0
  38. package/docs/widget/InstallButton.md +1 -0
  39. package/docs/widget/Markdown.md +4 -0
  40. package/docs/widget/Rating.md +2 -0
  41. package/docs/widget/Slider.md +2 -0
  42. package/docs/widget/Stack.md +6 -5
  43. package/docs/widget/Stepper.md +2 -0
  44. package/docs/widget/Tooltip.md +3 -1
  45. package/package.json +3 -2
  46. package/skills/fletbox-design/SKILL.md +390 -14
  47. package/skills/fletbox-motion/SKILL.md +145 -47
  48. package/skills/fletbox-rules/SKILL.md +140 -1
  49. package/src/animations/AnimatedBox.js +321 -226
  50. package/src/animations/MatrixRain.js +7 -1
  51. package/src/animations/ParallaxBox.js +13 -10
  52. package/src/animations/animate.js +269 -106
  53. package/src/animations/color.js +91 -0
  54. package/src/animations/easings.js +275 -0
  55. package/src/animations/index.js +21 -0
  56. package/src/animations/motion.js +204 -0
  57. package/src/animations/transitions.js +327 -0
  58. package/src/core/runApp.js +0 -6
  59. package/src/helpers/boxEdges.js +69 -0
  60. package/src/helpers/html.js +39 -0
  61. package/src/index.d.ts +377 -25
  62. package/src/index.js +77 -0
  63. package/src/navigations/AdaptiveScaffold.js +6 -2
  64. package/src/navigations/AppBar.js +16 -7
  65. package/src/navigations/BottomNavigation.js +24 -1
  66. package/src/navigations/CollapsibleSideBar.js +0 -1
  67. package/src/navigations/Drawer.js +32 -1
  68. package/src/navigations/DrawerItem.js +1 -1
  69. package/src/navigations/Router.js +0 -1
  70. package/src/navigations/Scaffold.js +9 -3
  71. package/src/navigations/Tabs.js +104 -31
  72. package/src/services/RamStore.js +1 -2
  73. package/src/tools/breakpoints.js +113 -0
  74. package/src/tools/createList.js +0 -2
  75. package/src/tools/fonts.js +99 -0
  76. package/src/tools/index.js +74 -1
  77. package/src/tools/margin.js +5 -63
  78. package/src/tools/padding.js +5 -63
  79. package/src/tools/print.js +0 -8
  80. package/src/tools/scale.js +9 -2
  81. package/src/tools/time.js +56 -15
  82. package/src/tools/useState.js +1 -54
  83. package/src/utils/fieldFont.js +77 -0
  84. package/src/utils/focusRing.js +73 -0
  85. package/src/utils/focusScope.js +113 -0
  86. package/src/utils/markdownParser.js +7 -9
  87. package/src/utils/readableInk.js +7 -1
  88. package/src/utils/stackLayout.js +55 -0
  89. package/src/utils/styleInjector.js +25 -0
  90. package/src/utils/syntaxHighlight.js +1 -17
  91. package/src/utils/themes.js +15 -0
  92. package/src/utils/visualEffects.js +49 -47
  93. package/src/widget-factory/createWidget.js +16 -3
  94. package/src/widget-factory/effects.js +76 -3
  95. package/src/widget-factory/reactiveValue.js +0 -7
  96. package/src/widget-factory/tools.js +26 -143
  97. package/src/widget-factory/translateProps.js +5 -0
  98. package/src/widget-factory/widgetFactory.js +1 -1
  99. package/src/widgets/Accordion.js +70 -12
  100. package/src/widgets/AdMob.js +1 -1
  101. package/src/widgets/AdSense.js +1 -1
  102. package/src/widgets/AlertDialog.js +11 -9
  103. package/src/widgets/Avatar.js +10 -1
  104. package/src/widgets/Badge.js +7 -1
  105. package/src/widgets/BottomSheet.js +39 -5
  106. package/src/widgets/Chart.js +41 -9
  107. package/src/widgets/Checkbox.js +59 -9
  108. package/src/widgets/Chip.js +13 -1
  109. package/src/widgets/CircularBar.js +17 -5
  110. package/src/widgets/CircularChart.js +24 -2
  111. package/src/widgets/CodeViewer.js +5 -3
  112. package/src/widgets/Dropdown.js +223 -20
  113. package/src/widgets/GridView.js +17 -1
  114. package/src/widgets/Icon.js +21 -0
  115. package/src/widgets/Input.js +75 -44
  116. package/src/widgets/InstallButton.js +8 -2
  117. package/src/widgets/ListTile.js +6 -5
  118. package/src/widgets/ListView.js +21 -7
  119. package/src/widgets/Markdown.js +25 -44
  120. package/src/widgets/Modal.js +43 -6
  121. package/src/widgets/Pagination.js +35 -3
  122. package/src/widgets/ProgressBar.js +18 -0
  123. package/src/widgets/Radio.js +7 -0
  124. package/src/widgets/Rating.js +104 -16
  125. package/src/widgets/Skeleton.js +9 -5
  126. package/src/widgets/Slider.js +87 -7
  127. package/src/widgets/SnackBar.js +30 -1
  128. package/src/widgets/Stack.js +25 -13
  129. package/src/widgets/Stepper.js +112 -64
  130. package/src/widgets/Switch.js +10 -1
  131. package/src/widgets/Tooltip.js +93 -10
  132. package/src/widgets/TreeView.js +5 -5
  133. package/src/create-index.sh +0 -237
  134. package/src/widget-factory/pixTool.js +0 -40
  135. package/src/widget-factory/stringTool.js +0 -26
@@ -9,7 +9,8 @@
9
9
  * comparison that drives the OTA is the `hash` — the version is a human label.
10
10
  *
11
11
  * Update source URL resolution order:
12
- * 1. `update.url` in `fletbox.config.json` (explicit wins)
12
+ * 1. `update.url` in `fletbox.config.json`, when it is set and is no longer
13
+ * the scaffold placeholder (explicit wins over the derivation below)
13
14
  * 2. derived from the `repository` field of the project's package.json,
14
15
  * mapped to its GitHub Pages URL: https://<user>.github.io/<repo>/
15
16
  * 3. derived from the CURRENT PROJECT's git remote `origin` (when it is a
@@ -69,6 +70,23 @@ export const parseGithubRepo = (repository) => {
69
70
  return null;
70
71
  };
71
72
 
73
+ /**
74
+ * True when a declared `update.url` is still the scaffold placeholder — the two
75
+ * tokens `flet-box create` writes when it was not told where the app will live
76
+ * (`OTA_PLACEHOLDER_URL` in `templates.js`, and the same tokens in the generated
77
+ * workflow header).
78
+ *
79
+ * A placeholder is not a decision, so it must not outrank derivation: a bundle
80
+ * that carries it points an installed app at a host that does not exist, and the
81
+ * OTA check fails on every launch in silence — OTA is built never to break the
82
+ * app, so the only symptom is that an update is never offered.
83
+ *
84
+ * @param {*} url
85
+ * @returns {boolean}
86
+ */
87
+ export const isPlaceholderUpdateUrl = (url) =>
88
+ typeof url === "string" && /TU-USUARIO|TU-REPO/.test(url);
89
+
72
90
  /**
73
91
  * Resolves the OTA update URL for a project.
74
92
  *
@@ -82,9 +100,15 @@ export const resolveUpdateUrl = (projectRoot) => {
82
100
  {},
83
101
  );
84
102
 
85
- // 1. Explicit configuration always wins.
103
+ // 1. Explicit configuration wins — over the derivations below, not over the
104
+ // truth. An unfilled placeholder falls through, so a project whose origin
105
+ // remote is a GitHub repo gets its real Pages address without editing a file.
86
106
  const explicit = fletboxConfig?.update?.url;
87
- if (typeof explicit === "string" && explicit.trim()) {
107
+ if (
108
+ typeof explicit === "string" &&
109
+ explicit.trim() &&
110
+ !isPlaceholderUpdateUrl(explicit)
111
+ ) {
88
112
  return explicit.endsWith("/") ? explicit : `${explicit}/`;
89
113
  }
90
114
 
@@ -107,6 +131,26 @@ export const resolveUpdateUrl = (projectRoot) => {
107
131
  return null;
108
132
  };
109
133
 
134
+ /**
135
+ * Reads only what the project itself declares as its update source, without
136
+ * resolving or deriving anything.
137
+ *
138
+ * The build prints this when the URL that landed in the manifest came from
139
+ * derivation instead of from the file: a bundle pointing at a real site while
140
+ * `fletbox.config.json` still holds the placeholder is a half-filled config, and
141
+ * only the person editing that file can tell the two apart.
142
+ *
143
+ * @param {string} projectRoot - Absolute path to the project root.
144
+ * @returns {*} The declared `update.url`, or null when the file does not say.
145
+ */
146
+ export const declaredUpdateUrl = (projectRoot) => {
147
+ const declared = readJsonSafe(
148
+ path.join(projectRoot, "fletbox.config.json"),
149
+ {},
150
+ )?.update?.url;
151
+ return typeof declared === "string" && declared.trim() ? declared : null;
152
+ };
153
+
110
154
  /**
111
155
  * The `<meta>` tags a social scraper resolves against its own machine.
112
156
  *
@@ -173,9 +173,6 @@ export const panel = (title, subtitle = "", { minW = 62 } = {}) => {
173
173
  export const banner = (title, subtitle = "", opts = {}) =>
174
174
  panel(title, subtitle, opts);
175
175
 
176
- export const badge = (icon, text, color = "brightCyan") =>
177
- ` ${c(color, icon + " " + text)}`;
178
-
179
176
  // Status dot (green by default, red/amber for failures/warnings)
180
177
  export const dot = (color = "#34d399") => hex("●", color);
181
178
 
@@ -207,9 +204,4 @@ export const setupCtrlC = () => {
207
204
  console.log(`\n\n${rainbow("👋 Bye!")}\n`);
208
205
  process.exit(0);
209
206
  });
210
- };
211
-
212
- // Show help message for Ctrl+C
213
- export const showExitHint = () => {
214
- console.log(`${c("gray", "Press Ctrl+C to exit")}`);
215
207
  };
@@ -73,8 +73,3 @@ export const execCmd = (cmd) => {
73
73
  return false;
74
74
  }
75
75
  };
76
-
77
- // Fixed: Now returns actual package path
78
- export const getPackagePath = () => {
79
- return path.join(process.cwd(), "node_modules", "flet-box");
80
- };
@@ -338,7 +338,7 @@ export const decodeJpeg = (buf) => {
338
338
  const decodeScan = (scan, ctx) => {
339
339
  const { buf, components, width, height, maxH, maxV, block, scratch } = ctx;
340
340
  const { restartInterval, tables: { quant, dc, ac } } = scan;
341
- const specs = readScanHeader(scan.payload, components.length);
341
+ const specs = readScanHeader(scan.payload);
342
342
  if (specs.length !== components.length && specs.length !== 1) {
343
343
  throw new Error(`JPEG scan covers ${specs.length} of ${components.length} components, which is neither interleaved nor single-component`);
344
344
  }
@@ -558,11 +558,10 @@ const readDht = (payload, dc, ac) => {
558
558
  * scan.
559
559
  *
560
560
  * @param {Buffer} payload
561
- * @param {number} componentCount
562
561
  * @returns {{ id: number, tables: number }[]} Component specifiers, in scan order.
563
562
  * @throws {UnsupportedImage} For a progressive or refined scan.
564
563
  */
565
- const readScanHeader = (payload, componentCount) => {
564
+ const readScanHeader = (payload) => {
566
565
  const count = payload[0];
567
566
  if (payload.length < 4 + count * 2) throw new Error("JPEG scan header is truncated");
568
567
  const specs = [];
@@ -709,54 +709,6 @@ echo "Press Ctrl+C to stop"
709
709
  python3 -m http.server $PORT --bind $BIND
710
710
  `;
711
711
 
712
- export const createBundleSh = (target = "www") => `#!/bin/bash
713
- # createBundle.sh — Bundle UNIFICADO a \${TARGET}/ (un solo app.js + PWA)
714
- # Uso: bash createBundle.sh [target]
715
- set -euo pipefail
716
- cd "\$(dirname "\$0")"
717
- TARGET="\${1:-${target}}"
718
-
719
- if ! npx --no-install esbuild --version >/dev/null 2>&1; then
720
- echo "📦 Instalando esbuild (devDependency)…"
721
- npm install --save-dev esbuild >/dev/null
722
- fi
723
-
724
- rm -rf "\$TARGET"
725
- mkdir -p "\$TARGET/src" "\$TARGET/assets"
726
-
727
- # Root static files (PWA + preview)
728
- for f in index.html manifest.json run.sh service-worker.js; do
729
- [ -f "\$f" ] && cp "\$f" "\$TARGET/"
730
- done
731
- [ -f "\$TARGET/run.sh" ] && chmod +x "\$TARGET/run.sh"
732
-
733
- # Assets: iconos PWA + fuentes de iconos
734
- if [ -d "src/assets" ]; then
735
- cp -R src/assets/. "\$TARGET/src/assets/"
736
- cp -R src/assets/. "\$TARGET/assets/"
737
- fi
738
-
739
- # CSS global (lo carga index.html)
740
- if [ -f "src/styles/global.css" ]; then
741
- mkdir -p "\$TARGET/src/styles"
742
- cp src/styles/global.css "\$TARGET/src/styles/"
743
- fi
744
-
745
- # BUNDLE UNIFICADO: flet-box + widgets en UN solo app.js
746
- echo "📦 Bundling (esbuild + tree-shaking)…"
747
- npx esbuild src/app.js \\
748
- --bundle --outfile="\$TARGET/src/app.js" --format=esm --minify \\
749
- --tree-shaking=true --target=es2020 --define:FLETBOX_DEV=false \\
750
- --external:*.css --external:*.woff2 --resolve-extensions=.js,.json
751
-
752
- # App source code (required by the "View code" button)
753
- for d in modules screens database components; do
754
- [ -d "src/\$d" ] && cp -R "src/\$d" "\$TARGET/src/"
755
- done
756
-
757
- echo "✅ Bundle listo: \$TARGET/ Prueba: cd \$TARGET && bash run.sh"
758
- `;
759
-
760
712
  export const serviceWorkerJs = () => `// service-worker.js - PWA offline support
761
713
  const CACHE_NAME = 'fletbox-v2';
762
714
  // Deployment prefix: a worker's own URL always sits at the app root, so this
@@ -879,6 +831,28 @@ self.addEventListener('fetch', event => {
879
831
  });
880
832
  `;
881
833
 
834
+ // Not TypeScript: an editor reads this file and runs its bundled JS language
835
+ // service over `flet-box`'s own .d.ts, so a prop autocompletes and a prop that
836
+ // does not exist is a squiggle. `bundler` is the resolution an unbundled-ESM
837
+ // app needs, and TypeScript 7 removed `node10` outright — an older editor that
838
+ // still only knows `node` ignores the whole jsconfig, which is no worse than
839
+ // not having one. Nothing here is compiled or installed.
840
+ export const jsconfig = () => `{
841
+ "compilerOptions": {
842
+ "target": "ES2020",
843
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
844
+ "module": "ESNext",
845
+ "moduleResolution": "bundler",
846
+ "allowJs": true,
847
+ "checkJs": true,
848
+ "strict": false,
849
+ "noEmit": true,
850
+ "skipLibCheck": true
851
+ },
852
+ "include": ["src/**/*.js"],
853
+ "exclude": ["node_modules", "www", "dist"]
854
+ }`;
855
+
882
856
  export const packageJson = (name) => `{
883
857
  "name": ${JSON.stringify(name)},
884
858
  "version": "1.0.0",
@@ -1322,14 +1296,21 @@ export const githubWorkflow = (appName = "FletBox App", siteUrl = null) => {
1322
1296
  const site = /^https?:\/\//i.test(String(siteUrl || ""))
1323
1297
  ? String(siteUrl).replace(/\/?$/, "/")
1324
1298
  : `https://TU-USUARIO.github.io/${slug}/`;
1325
- // Only when the owner is still a guess: the address lives in two files, and a
1326
- // developer who edits one and not the other ships an app that polls a site
1327
- // that is not where the bundle is.
1299
+ // Only when the owner is still a guess. The build no longer trusts the
1300
+ // placeholder in fletbox.config.json (createBundle derives the real Pages
1301
+ // address from this repository), so what is left to correct here is the
1302
+ // address a human reads before reading a single step.
1328
1303
  const todo = site.includes("TU-USUARIO")
1329
1304
  ? `# The owner above is a placeholder: this project was created without \`--repo\`,
1330
- # so the folder name is only a guess at the repo name. Write the real address in
1331
- # both files that state it — this header, and fletbox.config.json (update.url),
1332
- # which is the URL an installed app polls for an update.
1305
+ # so the folder name is only a guess at the repo name. Put the real owner here
1306
+ # once the repository exists.
1307
+ #
1308
+ # \`fletbox.config.json\` may keep its own placeholder: the build recognises it as
1309
+ # "not configured" and derives the address from this repo's repository field or
1310
+ # git remote, so an installed app polls the site this workflow deploys. The URL
1311
+ # that landed in the manifest is printed by the build and by the
1312
+ # \`Where this lives\` step below. Set \`update.url\` by hand only when this app
1313
+ # will live on another host.
1333
1314
  `
1334
1315
  : "";
1335
1316
  return `name: Deploy ${appName}
@@ -1663,6 +1644,15 @@ npm install
1663
1644
  npm run dev
1664
1645
  \`\`\`
1665
1646
 
1647
+ ## Autocomplete without TypeScript
1648
+
1649
+ \`jsconfig.json\` points the editor's own JavaScript language service at
1650
+ \`flet-box\`'s bundled \`.d.ts\` — nothing to install, nothing to compile. Every
1651
+ widget, prop, colour token and helper autocompletes in VS Code (or any editor
1652
+ that reads jsconfig), and a prop that does not exist is underlined as an error
1653
+ instead of failing silently at runtime. \`src/\` is the only folder checked;
1654
+ \`npm run check\` remains the gate that runs in CI.
1655
+
1666
1656
  ## Working with an AI assistant
1667
1657
 
1668
1658
  This project ships an in-project agent contract, so an AI editing the app uses the
@@ -1796,9 +1786,12 @@ Two workflow files ship with the project, and they answer to different triggers:
1796
1786
  The address this project publishes to is written at the top of
1797
1787
  \`.github/workflows/build.yml\`. \`flet-box create <name> --repo <user>/<repo>\`
1798
1788
  fills it in from the repo; without \`--repo\` it stays
1799
- \`https://TU-USUARIO.github.io/<name>/\`, and the header says which two files to
1800
- edit. That is a sub-path, so \`index.html\` uses relative asset paths and works
1801
- without changes.
1789
+ \`https://TU-USUARIO.github.io/<name>/\`, and the header explains what is still a
1790
+ guess. The build ignores that placeholder in \`fletbox.config.json\` and derives
1791
+ the real address from this repository, so the OTA feed is correct either way.
1792
+
1793
+ The Pages address is a sub-path, so \`index.html\` uses relative asset paths and
1794
+ works without changes.
1802
1795
 
1803
1796
  **Why a push does not update anyone's app:** this same address is the OTA feed
1804
1797
  installed apps poll, and the update check compares bundle **hashes**, not version
@@ -1888,9 +1881,10 @@ updates:
1888
1881
  \`\`\`
1889
1882
 
1890
1883
  - The GitHub repository **must be public**.
1891
- - Replace \`TU-USUARIO\` with your GitHub username and \`TU-REPO\` with the
1892
- repository name, or set \`update.url\` to any HTTPS host that serves your
1893
- \`config.json\` + bundle.
1884
+ - \`TU-USUARIO\`/\`TU-REPO\` are a placeholder, not a setting: the build sees them as
1885
+ "not configured" and derives the Pages address from this project's \`repository\`
1886
+ field or git remote, so pushing the folder is enough. Edit \`update.url\` only
1887
+ when the app lives on another HTTPS host that serves your \`config.json\` + bundle.
1894
1888
  - Enable GitHub Pages: **Settings → Pages → Source: GitHub Actions**. Without
1895
1889
  that the deploy step is green and no site exists.
1896
1890
  - Cut a release with \`flet-box release --push\` (bumps the version, rebuilds
@@ -2066,7 +2060,7 @@ import { AppBar, Icon, Tooltip, colors, openDrawer, toggleTheme, getTheme } from
2066
2060
  // It reads the theme instead of subscribing to it: setTheme() re-renders the app,
2067
2061
  // so a component that subscribed would add a listener on every render and never
2068
2062
  // remove it.
2069
- export const AppBarComponent = ({ title, isMobile = false, showBack = false, onBack }) => {
2063
+ export const AppBarComponent = ({ title, isMobile = false, showBack = false, onBack = () => {} }) => {
2070
2064
  const isDark = getTheme() === 'dark';
2071
2065
 
2072
2066
  return AppBar({
@@ -2244,7 +2238,7 @@ runApp(App, 'root');
2244
2238
  `;
2245
2239
 
2246
2240
  export const basicAppJs = () => `// app.js - Basic template (AppBar + Drawer)
2247
- import { runApp, Scaffold, colors, useState, dimensions } from 'flet-box';
2241
+ import { runApp, Scaffold, colors, useState, atLeast, onBreakpointChange } from 'flet-box';
2248
2242
  // Registers this app's design tokens. Without it every screen paints
2249
2243
  // with the framework's own palette, and the file that looks like the design
2250
2244
  // system is decoration.
@@ -2264,13 +2258,14 @@ const routes = {
2264
2258
  };
2265
2259
 
2266
2260
  // The drawer is only reachable through the AppBar's menu button, so the shell has
2267
- // to know whether it is on a phone. Key-based state keeps the width across
2268
- // re-renders; the resize listener is registered once, at module scope.
2269
- const [viewportWidth, setViewportWidth] = useState('viewportWidth', dimensions.getWidth());
2270
- dimensions.addListener(({ width }) => setViewportWidth(width));
2261
+ // to know whether it is on a phone. Key-based state keeps the answer across
2262
+ // re-renders; the subscription is registered once, at module scope, and only
2263
+ // fires when the viewport crosses a breakpoint, not on every pixel of a resize.
2264
+ const [isWide, setIsWide] = useState('isWide', atLeast('md'));
2265
+ onBreakpointChange(() => setIsWide(atLeast('md')));
2271
2266
 
2272
2267
  const MyApp = () => {
2273
- const isMobile = viewportWidth.valueOf() < 768;
2268
+ const isMobile = !isWide.valueOf();
2274
2269
  return Scaffold({
2275
2270
  routes,
2276
2271
  appBar: AppBarComponent({ title: 'FletBox', isMobile }),
@@ -2284,7 +2279,7 @@ runApp(MyApp, 'root');
2284
2279
 
2285
2280
  export const fullAppJs =
2286
2281
  () => `// app.js - Full template (AppBar + Drawer + BottomNav)
2287
- import { runApp, Scaffold, colors, useState, dimensions } from 'flet-box';
2282
+ import { runApp, Scaffold, colors, useState, atLeast, onBreakpointChange } from 'flet-box';
2288
2283
  // Registers this app's design tokens. Without it every screen paints
2289
2284
  // with the framework's own palette, and the file that looks like the design
2290
2285
  // system is decoration.
@@ -2304,15 +2299,15 @@ const routes = {
2304
2299
  '/about': AboutScreen
2305
2300
  };
2306
2301
 
2307
- // State is key-based, so the viewport width survives re-renders. One resize
2302
+ // State is key-based, so the layout answer survives re-renders. One breakpoint
2308
2303
  // subscription feeds it from module scope: registering it inside the component
2309
- // would add a listener on every render.
2310
- const [viewportWidth, setViewportWidth] = useState('viewportWidth', dimensions.getWidth());
2311
- dimensions.addListener(({ width }) => setViewportWidth(width));
2304
+ // would add a listener on every render, and it only fires when a step is crossed.
2305
+ const [isWide, setIsWide] = useState('isWide', atLeast('md'));
2306
+ onBreakpointChange(() => setIsWide(atLeast('md')));
2312
2307
 
2313
2308
  const MyApp = () => {
2314
2309
  // Structural read of the key, so crossing the breakpoint re-renders the shell.
2315
- const isMobile = viewportWidth.valueOf() < 768;
2310
+ const isMobile = !isWide.valueOf();
2316
2311
  return Scaffold({
2317
2312
  routes,
2318
2313
  appBar: AppBarComponent({ title: 'FletBox', isMobile }),
@@ -2357,7 +2352,7 @@ runApp(MyApp, 'root');
2357
2352
 
2358
2353
  export const adaptiveAppJs =
2359
2354
  () => `// app.js - Adaptive template (mobile + desktop)
2360
- import { runApp, Scaffold, colors, useState, dimensions } from 'flet-box';
2355
+ import { runApp, Scaffold, colors, useState, atLeast, onBreakpointChange } from 'flet-box';
2361
2356
  // Registers this app's design tokens. Without it every screen paints
2362
2357
  // with the framework's own palette, and the file that looks like the design
2363
2358
  // system is decoration.
@@ -2380,11 +2375,11 @@ const routes = {
2380
2375
  '/profile': ProfileScreen
2381
2376
  };
2382
2377
 
2383
- // State is key-based, so the viewport width survives re-renders. The resize
2378
+ // State is key-based, so the layout answer survives re-renders. The breakpoint
2384
2379
  // subscription is registered once at module scope; doing it inside the component
2385
2380
  // would add another listener on every render.
2386
- const [viewportWidth, setViewportWidth] = useState('viewportWidth', dimensions.getWidth());
2387
- dimensions.addListener(({ width }) => setViewportWidth(width));
2381
+ const [isWide, setIsWide] = useState('isWide', atLeast('md'));
2382
+ onBreakpointChange(() => setIsWide(atLeast('md')));
2388
2383
 
2389
2384
  // One menu, three surfaces: the drawer and the bottom bar on a phone, the
2390
2385
  // sidebar on a desktop. The labels stay short because the bottom bar has to fit
@@ -2397,9 +2392,9 @@ const menu = [
2397
2392
  ];
2398
2393
 
2399
2394
  const MyApp = () => {
2400
- // Structural read of the keyed viewport state, so crossing the breakpoint
2395
+ // Structural read of the keyed layout state, so crossing the breakpoint
2401
2396
  // re-renders the shell (drawer + bottom bar <-> sidebar).
2402
- const isMobile = viewportWidth.valueOf() < 768;
2397
+ const isMobile = !isWide.valueOf();
2403
2398
  return Scaffold({
2404
2399
  routes,
2405
2400
  appBar: AppBarComponent({ title: 'FletBox', isMobile }),
@@ -2547,9 +2542,13 @@ export const agentsMd = (appName, template, version) =>
2547
2542
  'Real `Scaffold` props: `appBar body bottomBar fab drawer leftNavBar',
2548
2543
  'leftNavBarWidth rightNavBar rightNavBarWidth routes basePath backgroundColor',
2549
2544
  'closeDrawerOnNavigate`. (`--props Scaffold` is authoritative.)',
2550
- 'For a responsive app, branch in JS on the keyed viewport state — `useState`',
2551
- 'seeded from `dimensions.getWidth()` plus one `dimensions.addListener()` at',
2552
- 'module scope — not on CSS alone. See `src/app.js` in this project.',
2545
+ 'For a responsive app, branch in JS on the breakpoint table, not on CSS alone:',
2546
+ '`useState` seeded from `atLeast("md")` plus one `onBreakpointChange()` at',
2547
+ 'module scope — it fires when the viewport crosses a step, not on every pixel',
2548
+ 'of a resize. `md` is 768, the same line `AdaptiveScaffold` crosses, so a',
2549
+ 'screen and its shell cannot disagree. A number that really wants to be',
2550
+ 'different at 400px and 1400px comes from `pick({ xs: 16, md: 24 })`.',
2551
+ 'See `src/app.js` in this project.',
2553
2552
  '',
2554
2553
  'Custom widgets follow the same shape every framework widget uses',
2555
2554
  '(`QRCode.js` is the reference):',
@@ -2566,8 +2565,25 @@ export const agentsMd = (appName, template, version) =>
2566
2565
  ' idempotent `onUnmount` disposer. Never monkey-patch `_cleanup`.',
2567
2566
  '- State is key-based: `useState("key", 0)` plus subscribers. There is no',
2568
2567
  ' component re-render tree to reason about.',
2569
- '- Reuse the framework utilities (`colors`, `margin`, `padding`, `gradient',
2570
- ' shadow`, `rgba`, `stackPosition`) instead of hand-writing CSS.',
2568
+ '- The package exports 123 helpers next to the widgets and they are the short',
2569
+ ' form of the code you would otherwise write: `mapList` (an array, a number or',
2570
+ " an object), `createList`, `range`, `repeat`, `sort`, `unique`, `chunk`,",
2571
+ ' `shadow`, `gradient`, `rgba`, `transform`, `transition`, `filter` (a CSS',
2572
+ ' filter, not a list filter), `color(hex).darken(12).hex()`, `random.*`,',
2573
+ ' `shortId`, `uuid`, `formatDate`, `formatTimeAmPm`, `relativeTime`, `timeAgo`,',
2574
+ ' `addDays`, `subtractDays`, `dateDiff`, `dayOfWeek`, `isWeekend`, `today`,',
2575
+ ' `seconds`, `days`, `parseDate`, `timer`, `every`, `everySecond`, `waitUntil`,',
2576
+ ' `split`, `join`, `trim`, `startsWith`, `endsWith`, `includes`,',
2577
+ ' `delay`, `withMinDelay`,',
2578
+ ' `retry`, `memo`, `dict`, `print`, `ref`, `atLeast`, `pick`,',
2579
+ ' `onBreakpointChange`, `loadFont`, `clipboard`. Hand-write the long version',
2580
+ ' only when it is genuinely shorter or clearer. `npm run check -- --list`',
2581
+ ' prints the set for the version actually installed.',
2582
+ ' The trap: a routed prop already IS the short form. `padding: 16` reaches the',
2583
+ ' rem contract; `padding(16)` returns a pixel string. Use the helpers for a',
2584
+ ' CSS value a prop cannot express, never instead of a number.',
2585
+ ' Second trap: `months(1)` is 30 days and `years(1)` is 365, and `addDays`',
2586
+ ' counts calendar days while `addHours` counts real elapsed ones.',
2571
2587
  '',
2572
2588
  'One rendering engine exists: `src/widget-factory/`. Never add a second one, and',
2573
2589
  'never style by reaching around the factory. **If you believe the engine is',
@@ -2625,7 +2641,7 @@ export const agentsMd = (appName, template, version) =>
2625
2641
  ' 40 · the smallest tap target, a roomy control',
2626
2642
  ' 48 · hero spacing',
2627
2643
  ' 64 / 96 · a full-bleed section, a landing hero',
2628
- 'radius 4 / 8 / 12 / 16 / 999 (pill)',
2644
+ 'radius 4 / 8 / 12 / 16 / 20 / 24 / 999 (pill)',
2629
2645
  'type 12 caption · 14 body-small · 16 body · 18 subtitle · 24 title',
2630
2646
  ' 32 headline · 40 / 48 display (a hero, a big number)',
2631
2647
  'weight 400 body · 500 label · 600 title · 700 headline',
package/docs/README.md CHANGED
@@ -17,6 +17,7 @@ This documentation is one continuous book. Start at **Chapter 0** and follow the
17
17
  | 10 | [Tools & Utilities](tools/README.md) | The helper functions that ship with the framework | Intermediate |
18
18
  | 11 | [The CLI](cli/README.md) | Create, run, and build projects with `flet-box` | Intermediate |
19
19
  | 12 | [Clean UI](guides/clean-ui.md) | The four skills every app ships with, the design system they hold, and the gate that enforces it | Intermediate |
20
+ | 13 | [Accessibility](guides/accessibility.md) | The focus ring, the keyboard pass over every press, the roles the widgets declare, and the overlays that hold the keyboard | Intermediate |
20
21
  | A | [Appendices](guides/minification-and-protection.md) | Minification, mobile, compatibility, embedding, online updates (OTA) | Advanced |
21
22
 
22
23
  > **Levels.** *Beginner:* start here and read in order. *Intermediate:* continue in order — you are comfortable with widgets. *Advanced:* for when you ship, skip on a first read.
@@ -54,6 +55,7 @@ Front cover (this page)
54
55
  → Utilities guide · guides/utilities.md
55
56
  → The CLI · cli/README.md
56
57
  → Clean UI · guides/clean-ui.md
58
+ → Accessibility · guides/accessibility.md
57
59
  → Appendices (minification → mobile → compatibility → embedding → online updates)
58
60
  → The end of the book
59
61
  ```
@@ -69,4 +71,25 @@ Front cover (this page)
69
71
 
70
72
  - [Server toolkit](server/README.md) — Express/SQLite/Redis backend helpers (separate toolkit).
71
73
  - [Architecture](arquitectura.md) — how the framework is built inside.
72
- - [Contributing](CONTRIBUTING.md) — for people who add code or docs to FletBox.
74
+ - [Contributing](CONTRIBUTING.md) — for people who add code or docs to FletBox.
75
+
76
+ ## Read this book as an app
77
+
78
+ The same pages are also a FletBox application: **[flet-box-docs](https://github.com/kuko53348/flet-box-docs)**, published to npm as its own package.
79
+
80
+ ```bash
81
+ npm install flet-box-docs # then: cd node_modules/flet-box-docs && npm install && npm run dev
82
+ ```
83
+
84
+ It is a normal FletBox project — the router, a categorized index, cross-document
85
+ Previous/Next, a light/dark toggle, an offline cache, and Capacitor wrappers for
86
+ Android and iOS — and every page is rendered by the framework's own `Markdown`
87
+ widget, so the book is also a live demo of the widget that writes it.
88
+
89
+ Its `docs/` folder is a **snapshot** of this folder, refreshed before each
90
+ release; when the app is online it reads the published source instead, so a page
91
+ added here arrives on an installed device without a new build. That is why
92
+ `flet-box-docs` carries a version of its own: it is a shipping app, and its
93
+ `npm run build` refuses to bundle until `src/constants.js` states both versions
94
+ out loud — the app's own, which must match its `package.json`, and the framework
95
+ release its pages describe.
@@ -21,14 +21,17 @@ npm --version
21
21
 
22
22
  ## Install FletBox
23
23
 
24
- Inside the FletBox package directory:
24
+ FletBox is published to npm, so the CLI is one install away:
25
25
 
26
26
  ```bash
27
- npm install
28
- npm link
27
+ npm install -g flet-box
29
28
  ```
30
29
 
31
- After linking, the `flet-box` command is available globally on your computer.
30
+ Or run it without installing anything globally — `npx` fetches the same package:
31
+
32
+ ```bash
33
+ npx flet-box create my-app
34
+ ```
32
35
 
33
36
  Check that it works:
34
37
 
@@ -37,12 +40,22 @@ flet-box --version
37
40
  flet-box --help
38
41
  ```
39
42
 
40
- When using the published package, install it in a project with:
43
+ Inside a project the framework itself is an ordinary dependency:
41
44
 
42
45
  ```bash
43
46
  npm install flet-box
44
47
  ```
45
48
 
49
+ Cloning the repository and linking it is the exception, and it is for people
50
+ editing the framework:
51
+
52
+ ```bash
53
+ git clone https://github.com/kuko53348/flet-box.git
54
+ cd flet-box
55
+ npm install
56
+ npm link # the `flet-box` command now points at this checkout
57
+ ```
58
+
46
59
  ## Your first project
47
60
 
48
61
  Create a project with the default template:
@@ -84,9 +97,12 @@ CI: .github/workflows/build.yml publishes the web to GitHub Pages on every
84
97
  ```
85
98
 
86
99
  The first positional argument is always the project name; a flag that arrives
87
- before it is read as the name. Without `--repo` nothing is invented — the
88
- placeholder stays in both files and the workflow says which two to edit. The
89
- reason the workflow log, not the header, is the authority on the live address is
100
+ before it is read as the name. Without `--repo` nothing is invented: the
101
+ placeholder stays in `fletbox.config.json` and in the workflow header, and the
102
+ header says which part of it is still a guess. `flet-box createBundle` reads that
103
+ placeholder as "not configured" and derives the real Pages address from the
104
+ project, so the manifest is never aimed at a host that does not exist. The reason
105
+ the workflow log, not the header, is the authority on the live address is
90
106
  explained in [The Pages address of a generated app](../guides/online-updates.md).
91
107
 
92
108
  ## Project templates
@@ -275,6 +291,7 @@ A typical generated project looks like this:
275
291
  ```text
276
292
  my-app/
277
293
  ├── index.html
294
+ ├── jsconfig.json
278
295
  ├── package.json
279
296
  ├── capacitor.config.json
280
297
  ├── manifest.json
@@ -301,7 +318,23 @@ my-app/
301
318
 
302
319
  The exact folders depend on the selected template.
303
320
 
304
- ## A complete beginner workflow
321
+ ### Autocomplete without TypeScript
322
+
323
+ `jsconfig.json` is not a build step: it tells the editor's own JavaScript
324
+ language service to read `flet-box`'s bundled `src/index.d.ts`. With `checkJs`
325
+ on, VS Code, Cursor and any editor that understands `jsconfig.json` autocomplete
326
+ every widget, prop, colour token and helper — and underline a prop that does not
327
+ exist, which is the same error `npm run check` reports from the command line.
328
+
329
+ Nothing is installed and nothing is compiled. The framework keeps the promise
330
+ honest with `scripts/check-app-types.mjs`, which creates all five templates and
331
+ requires zero diagnostics over their `src/` — a starter that produces red
332
+ squiggles fails `npm test`.
333
+
334
+ ```bash
335
+ npm run check # the gate: real API, real props, real imports
336
+ ```
337
+
305
338
 
306
339
  ```bash
307
340
  # 1. Create the project
@@ -619,9 +652,17 @@ For normal application development, `npm install flet-box` is usually enough.
619
652
 
620
653
  ### `flet-box: command not found`
621
654
 
622
- The CLI is not linked or installed globally. From the FletBox package directory, run:
655
+ The CLI is not installed globally. Install the published package:
656
+
657
+ ```bash
658
+ npm install -g flet-box
659
+ ```
660
+
661
+ If you are working from a clone of the framework instead, link that checkout so the
662
+ command points at the code you are editing:
623
663
 
624
664
  ```bash
665
+ cd flet-box
625
666
  npm install
626
667
  npm link
627
668
  ```
@@ -11,8 +11,11 @@ This directory is the **conceptual index** of FletBox: how to build apps, set up
11
11
  - **[FletBox utilities](utilities.md)** — Chapter 10 · layout/theme/format helpers and validator tools.
12
12
  - **[The CLI](../cli/README.md)** — Chapter 11 · create, run, and build projects with `flet-box`.
13
13
  - **[Clean UI](clean-ui.md)** — Chapter 12 · the four skills `flet-box create`
14
- installs, the token scale, the 25 rules, and the `flet-box check-ui` gate that
15
- enforces them.
14
+ installs, the token scale, the numbered rules (00–24 enforced, 25–37 the
15
+ visual atlas), and the `flet-box check-ui` gate that enforces them.
16
+ - **[Accessibility](accessibility.md)** — Chapter 13 · the focus ring, the
17
+ keyboard pass over every press, the roles and states the widgets declare, and
18
+ the overlays that hold the keyboard.
16
19
  - **[Widget structure](widget-structure.md)** — the canonical, modular shape every widget follows (and how new ones are added).
17
20
 
18
21
  ## Appendices (Advanced — for shipping)