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.
- package/README.md +16 -12
- package/bin/commands/checkUi.js +276 -45
- package/bin/commands/create.js +8 -1
- package/bin/commands/createBundle.js +18 -6
- package/bin/commands/createScreen.js +1 -1
- package/bin/commands/runBundle.js +1 -1
- package/bin/utils/bundleConfig.js +47 -3
- package/bin/utils/colors.js +0 -8
- package/bin/utils/helpers.js +0 -5
- package/bin/utils/images/jpeg.js +2 -3
- package/bin/utils/templates.js +101 -85
- package/docs/README.md +24 -1
- package/docs/cli/README.md +51 -10
- package/docs/guides/README.md +5 -2
- package/docs/guides/accessibility.md +282 -0
- package/docs/guides/clean-ui.md +53 -8
- package/docs/guides/first-app.md +6 -7
- package/docs/guides/minification-and-protection.md +1 -1
- package/docs/guides/mobile-and-platforms.md +51 -0
- package/docs/guides/online-updates.md +18 -6
- package/docs/guides/utilities.md +116 -4
- package/docs/guides/widget-structure.md +30 -0
- package/docs/tools/README.md +1 -1
- package/docs/tools/animation.md +134 -11
- package/docs/tools/device.md +75 -3
- package/docs/tools/text-and-time.md +116 -6
- package/docs/widget/Accordion.md +2 -0
- package/docs/widget/AnimatedBox.md +46 -20
- package/docs/widget/AnimatedText.md +13 -7
- package/docs/widget/Badge.md +1 -0
- package/docs/widget/COMMON_PROPS.md +26 -3
- package/docs/widget/Chart.md +1 -0
- package/docs/widget/Checkbox.md +7 -1
- package/docs/widget/CircularChart.md +2 -0
- package/docs/widget/Drawer.md +1 -0
- package/docs/widget/GridView.md +4 -1
- package/docs/widget/Input.md +2 -0
- package/docs/widget/InstallButton.md +1 -0
- package/docs/widget/Markdown.md +4 -0
- package/docs/widget/Rating.md +2 -0
- package/docs/widget/Slider.md +2 -0
- package/docs/widget/Stack.md +6 -5
- package/docs/widget/Stepper.md +2 -0
- package/docs/widget/Tooltip.md +3 -1
- package/package.json +3 -2
- package/skills/fletbox-design/SKILL.md +390 -14
- package/skills/fletbox-motion/SKILL.md +145 -47
- package/skills/fletbox-rules/SKILL.md +140 -1
- package/src/animations/AnimatedBox.js +321 -226
- package/src/animations/MatrixRain.js +7 -1
- package/src/animations/ParallaxBox.js +13 -10
- package/src/animations/animate.js +269 -106
- package/src/animations/color.js +91 -0
- package/src/animations/easings.js +275 -0
- package/src/animations/index.js +21 -0
- package/src/animations/motion.js +204 -0
- package/src/animations/transitions.js +327 -0
- package/src/core/runApp.js +0 -6
- package/src/helpers/boxEdges.js +69 -0
- package/src/helpers/html.js +39 -0
- package/src/index.d.ts +377 -25
- package/src/index.js +77 -0
- package/src/navigations/AdaptiveScaffold.js +6 -2
- package/src/navigations/AppBar.js +16 -7
- package/src/navigations/BottomNavigation.js +24 -1
- package/src/navigations/CollapsibleSideBar.js +0 -1
- package/src/navigations/Drawer.js +32 -1
- package/src/navigations/DrawerItem.js +1 -1
- package/src/navigations/Router.js +0 -1
- package/src/navigations/Scaffold.js +9 -3
- package/src/navigations/Tabs.js +104 -31
- package/src/services/RamStore.js +1 -2
- package/src/tools/breakpoints.js +113 -0
- package/src/tools/createList.js +0 -2
- package/src/tools/fonts.js +99 -0
- package/src/tools/index.js +74 -1
- package/src/tools/margin.js +5 -63
- package/src/tools/padding.js +5 -63
- package/src/tools/print.js +0 -8
- package/src/tools/scale.js +9 -2
- package/src/tools/time.js +56 -15
- package/src/tools/useState.js +1 -54
- package/src/utils/fieldFont.js +77 -0
- package/src/utils/focusRing.js +73 -0
- package/src/utils/focusScope.js +113 -0
- package/src/utils/markdownParser.js +7 -9
- package/src/utils/readableInk.js +7 -1
- package/src/utils/stackLayout.js +55 -0
- package/src/utils/styleInjector.js +25 -0
- package/src/utils/syntaxHighlight.js +1 -17
- package/src/utils/themes.js +15 -0
- package/src/utils/visualEffects.js +49 -47
- package/src/widget-factory/createWidget.js +16 -3
- package/src/widget-factory/effects.js +76 -3
- package/src/widget-factory/reactiveValue.js +0 -7
- package/src/widget-factory/tools.js +26 -143
- package/src/widget-factory/translateProps.js +5 -0
- package/src/widget-factory/widgetFactory.js +1 -1
- package/src/widgets/Accordion.js +70 -12
- package/src/widgets/AdMob.js +1 -1
- package/src/widgets/AdSense.js +1 -1
- package/src/widgets/AlertDialog.js +11 -9
- package/src/widgets/Avatar.js +10 -1
- package/src/widgets/Badge.js +7 -1
- package/src/widgets/BottomSheet.js +39 -5
- package/src/widgets/Chart.js +41 -9
- package/src/widgets/Checkbox.js +59 -9
- package/src/widgets/Chip.js +13 -1
- package/src/widgets/CircularBar.js +17 -5
- package/src/widgets/CircularChart.js +24 -2
- package/src/widgets/CodeViewer.js +5 -3
- package/src/widgets/Dropdown.js +223 -20
- package/src/widgets/GridView.js +17 -1
- package/src/widgets/Icon.js +21 -0
- package/src/widgets/Input.js +75 -44
- package/src/widgets/InstallButton.js +8 -2
- package/src/widgets/ListTile.js +6 -5
- package/src/widgets/ListView.js +21 -7
- package/src/widgets/Markdown.js +25 -44
- package/src/widgets/Modal.js +43 -6
- package/src/widgets/Pagination.js +35 -3
- package/src/widgets/ProgressBar.js +18 -0
- package/src/widgets/Radio.js +7 -0
- package/src/widgets/Rating.js +104 -16
- package/src/widgets/Skeleton.js +9 -5
- package/src/widgets/Slider.js +87 -7
- package/src/widgets/SnackBar.js +30 -1
- package/src/widgets/Stack.js +25 -13
- package/src/widgets/Stepper.js +112 -64
- package/src/widgets/Switch.js +10 -1
- package/src/widgets/Tooltip.js +93 -10
- package/src/widgets/TreeView.js +5 -5
- package/src/create-index.sh +0 -237
- package/src/widget-factory/pixTool.js +0 -40
- 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
|
|
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
|
|
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 (
|
|
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
|
*
|
package/bin/utils/colors.js
CHANGED
|
@@ -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
|
};
|
package/bin/utils/helpers.js
CHANGED
package/bin/utils/images/jpeg.js
CHANGED
|
@@ -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
|
|
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
|
|
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 = [];
|
package/bin/utils/templates.js
CHANGED
|
@@ -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
|
|
1326
|
-
//
|
|
1327
|
-
//
|
|
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.
|
|
1331
|
-
#
|
|
1332
|
-
#
|
|
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
|
|
1800
|
-
|
|
1801
|
-
|
|
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
|
-
-
|
|
1892
|
-
|
|
1893
|
-
\`
|
|
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,
|
|
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
|
|
2268
|
-
// re-renders; the
|
|
2269
|
-
|
|
2270
|
-
|
|
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 =
|
|
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,
|
|
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
|
|
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 [
|
|
2311
|
-
|
|
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 =
|
|
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,
|
|
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
|
|
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 [
|
|
2387
|
-
|
|
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
|
|
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 =
|
|
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
|
|
2551
|
-
'seeded from `
|
|
2552
|
-
'module scope —
|
|
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
|
-
'-
|
|
2570
|
-
'
|
|
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.
|
package/docs/cli/README.md
CHANGED
|
@@ -21,14 +21,17 @@ npm --version
|
|
|
21
21
|
|
|
22
22
|
## Install FletBox
|
|
23
23
|
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
88
|
-
placeholder stays in
|
|
89
|
-
|
|
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
|
-
|
|
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
|
|
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
|
```
|
package/docs/guides/README.md
CHANGED
|
@@ -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
|
|
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)
|