flet-box 1.0.9 → 1.1.1

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 (132) hide show
  1. package/bin/commands/checkUi.js +327 -46
  2. package/bin/commands/create.js +48 -19
  3. package/bin/commands/createScreen.js +1 -1
  4. package/bin/commands/runBundle.js +1 -1
  5. package/bin/utils/colors.js +0 -8
  6. package/bin/utils/helpers.js +0 -5
  7. package/bin/utils/images/jpeg.js +2 -3
  8. package/bin/utils/skills.js +59 -0
  9. package/bin/utils/templates.js +196 -575
  10. package/docs/README.md +3 -1
  11. package/docs/cli/README.md +18 -1
  12. package/docs/guides/README.md +6 -1
  13. package/docs/guides/accessibility.md +282 -0
  14. package/docs/guides/clean-ui.md +84 -21
  15. package/docs/guides/minification-and-protection.md +1 -1
  16. package/docs/guides/mobile-and-platforms.md +51 -0
  17. package/docs/guides/utilities.md +116 -4
  18. package/docs/guides/widget-structure.md +30 -0
  19. package/docs/tools/README.md +1 -1
  20. package/docs/tools/animation.md +134 -11
  21. package/docs/tools/device.md +75 -3
  22. package/docs/tools/text-and-time.md +116 -6
  23. package/docs/widget/Accordion.md +2 -0
  24. package/docs/widget/AnimatedBox.md +46 -20
  25. package/docs/widget/AnimatedText.md +13 -7
  26. package/docs/widget/Badge.md +1 -0
  27. package/docs/widget/COMMON_PROPS.md +26 -3
  28. package/docs/widget/Chart.md +1 -0
  29. package/docs/widget/Checkbox.md +7 -1
  30. package/docs/widget/CircularChart.md +2 -0
  31. package/docs/widget/Drawer.md +1 -0
  32. package/docs/widget/GridView.md +4 -1
  33. package/docs/widget/Input.md +2 -0
  34. package/docs/widget/InstallButton.md +1 -0
  35. package/docs/widget/Markdown.md +4 -0
  36. package/docs/widget/Rating.md +2 -0
  37. package/docs/widget/Slider.md +2 -0
  38. package/docs/widget/Stack.md +6 -5
  39. package/docs/widget/Stepper.md +2 -0
  40. package/docs/widget/Tooltip.md +3 -1
  41. package/package.json +4 -2
  42. package/skills/fletbox-design/SKILL.md +949 -0
  43. package/skills/fletbox-memory/SKILL.md +134 -0
  44. package/skills/fletbox-motion/SKILL.md +266 -0
  45. package/skills/fletbox-rules/SKILL.md +293 -0
  46. package/src/animations/AnimatedBox.js +321 -226
  47. package/src/animations/MatrixRain.js +7 -1
  48. package/src/animations/ParallaxBox.js +13 -10
  49. package/src/animations/animate.js +269 -106
  50. package/src/animations/color.js +91 -0
  51. package/src/animations/easings.js +275 -0
  52. package/src/animations/index.js +21 -0
  53. package/src/animations/motion.js +204 -0
  54. package/src/animations/transitions.js +327 -0
  55. package/src/core/runApp.js +0 -6
  56. package/src/helpers/boxEdges.js +69 -0
  57. package/src/helpers/html.js +39 -0
  58. package/src/index.d.ts +377 -25
  59. package/src/index.js +77 -0
  60. package/src/navigations/AdaptiveScaffold.js +6 -2
  61. package/src/navigations/AppBar.js +16 -7
  62. package/src/navigations/BottomNavigation.js +24 -1
  63. package/src/navigations/CollapsibleSideBar.js +0 -1
  64. package/src/navigations/Drawer.js +32 -1
  65. package/src/navigations/DrawerItem.js +1 -1
  66. package/src/navigations/Router.js +0 -1
  67. package/src/navigations/Scaffold.js +9 -3
  68. package/src/navigations/Tabs.js +104 -31
  69. package/src/services/RamStore.js +1 -2
  70. package/src/tools/breakpoints.js +113 -0
  71. package/src/tools/createList.js +0 -2
  72. package/src/tools/fonts.js +99 -0
  73. package/src/tools/index.js +74 -1
  74. package/src/tools/margin.js +5 -63
  75. package/src/tools/padding.js +5 -63
  76. package/src/tools/print.js +0 -8
  77. package/src/tools/scale.js +9 -2
  78. package/src/tools/time.js +56 -15
  79. package/src/tools/useState.js +1 -54
  80. package/src/utils/fieldFont.js +77 -0
  81. package/src/utils/focusRing.js +73 -0
  82. package/src/utils/focusScope.js +113 -0
  83. package/src/utils/markdownParser.js +7 -9
  84. package/src/utils/readableInk.js +7 -1
  85. package/src/utils/stackLayout.js +55 -0
  86. package/src/utils/styleInjector.js +25 -0
  87. package/src/utils/syntaxHighlight.js +1 -17
  88. package/src/utils/themes.js +31 -0
  89. package/src/utils/visualEffects.js +49 -47
  90. package/src/widget-factory/createWidget.js +16 -3
  91. package/src/widget-factory/effects.js +76 -3
  92. package/src/widget-factory/reactiveValue.js +0 -7
  93. package/src/widget-factory/tools.js +26 -143
  94. package/src/widget-factory/translateProps.js +5 -0
  95. package/src/widget-factory/widgetFactory.js +1 -1
  96. package/src/widgets/Accordion.js +70 -12
  97. package/src/widgets/AdMob.js +1 -1
  98. package/src/widgets/AdSense.js +1 -1
  99. package/src/widgets/AlertDialog.js +11 -9
  100. package/src/widgets/Avatar.js +10 -1
  101. package/src/widgets/Badge.js +7 -1
  102. package/src/widgets/BottomSheet.js +39 -5
  103. package/src/widgets/Chart.js +41 -9
  104. package/src/widgets/Checkbox.js +59 -9
  105. package/src/widgets/Chip.js +13 -1
  106. package/src/widgets/CircularBar.js +17 -5
  107. package/src/widgets/CircularChart.js +24 -2
  108. package/src/widgets/CodeViewer.js +5 -3
  109. package/src/widgets/Dropdown.js +223 -20
  110. package/src/widgets/GridView.js +17 -1
  111. package/src/widgets/Icon.js +21 -0
  112. package/src/widgets/Input.js +75 -44
  113. package/src/widgets/InstallButton.js +8 -2
  114. package/src/widgets/ListTile.js +6 -5
  115. package/src/widgets/ListView.js +21 -7
  116. package/src/widgets/Markdown.js +25 -44
  117. package/src/widgets/Modal.js +43 -6
  118. package/src/widgets/Pagination.js +35 -3
  119. package/src/widgets/ProgressBar.js +18 -0
  120. package/src/widgets/Radio.js +7 -0
  121. package/src/widgets/Rating.js +104 -16
  122. package/src/widgets/Skeleton.js +9 -5
  123. package/src/widgets/Slider.js +87 -7
  124. package/src/widgets/SnackBar.js +30 -1
  125. package/src/widgets/Stack.js +25 -13
  126. package/src/widgets/Stepper.js +112 -64
  127. package/src/widgets/Switch.js +10 -1
  128. package/src/widgets/Tooltip.js +93 -10
  129. package/src/widgets/TreeView.js +5 -5
  130. package/src/create-index.sh +0 -237
  131. package/src/widget-factory/pixTool.js +0 -40
  132. package/src/widget-factory/stringTool.js +0 -26
@@ -1,17 +1,32 @@
1
1
  # Text & time
2
2
 
3
3
  ## Overview
4
- Small pure helpers for strings, dates, and media time. They take a value and return a formatted one — no side effects.
4
+ Small pure helpers for strings, dates, waiting and repeating. They take a value
5
+ and return a formatted one — no side effects, except the four that hand back a
6
+ timer id or a promise.
5
7
 
6
8
  ## When to use
7
9
  - Normalize user names or titles before display (`capitalize`, `truncate`).
8
- - Show friendly dates and "2 minutes ago" labels.
10
+ - Show friendly dates, weekday names and "2 minutes ago" labels.
11
+ - Step a date by a calendar day, or measure the gap between two.
12
+ - Wait for a condition or an element without writing the poll loop twice.
9
13
  - Format audio/video positions in player UIs.
10
14
 
11
15
  ## Import
12
16
 
13
17
  ```javascript
14
- import { capitalize, truncate, formatDate, relativeTime } from "flet-box";
18
+ import {
19
+ capitalize,
20
+ truncate,
21
+ formatDate,
22
+ formatDateLong,
23
+ addDays,
24
+ dateDiff,
25
+ relativeTime,
26
+ seconds,
27
+ sleep,
28
+ everySecond,
29
+ } from "flet-box";
15
30
  ```
16
31
 
17
32
  ## Functions
@@ -20,6 +35,12 @@ import { capitalize, truncate, formatDate, relativeTime } from "flet-box";
20
35
 
21
36
  | Function | Signature | Returns | Description |
22
37
  | --- | --- | --- | --- |
38
+ | `startsWith` | `startsWith(str, prefix)` | `boolean` | Whether `str` begins with `prefix`. |
39
+ | `endsWith` | `endsWith(str, suffix)` | `boolean` | Whether `str` ends with `suffix`. |
40
+ | `includes` | `includes(str, substr)` | `boolean` | Whether `str` contains `substr`. |
41
+ | `split` | `split(str, separator)` | `string[]` | Text into parts. |
42
+ | `join` | `join(arr, separator?)` | `string` | Parts back into text. |
43
+ | `trim` | `trim(str)` | `string` | Leading and trailing whitespace removed. |
23
44
  | `capitalize` | `capitalize(str)` | `string` | First letter uppercase. |
24
45
  | `capitalizeWords` | `capitalizeWords(str)` | `string` | First letter of every word uppercase. |
25
46
  | `lowerCase` | `lowerCase(str)` | `string` | All lowercase. |
@@ -27,13 +48,66 @@ import { capitalize, truncate, formatDate, relativeTime } from "flet-box";
27
48
  | `reverseString` | `reverseString(str)` | `string` | Reversed text. |
28
49
  | `truncate` | `truncate(str, length?, suffix?)` | `string` | Cut to `length` characters and append `suffix` (default `"..."`). |
29
50
 
51
+ ### Units
52
+
53
+ Every one of these takes a count and returns **milliseconds**, so it reads as a
54
+ sentence at the call site: `waitFor(seconds(30))`, `now() - days(2)`.
55
+
56
+ | Function | Signature | Returns |
57
+ | --- | --- | --- |
58
+ | `seconds` | `seconds(n)` | `n * 1000` |
59
+ | `minutes` | `minutes(n)` | `n * 60 * 1000` |
60
+ | `hours` | `hours(n)` | `n * 60 * 60 * 1000` |
61
+ | `days` | `days(n)` | `n * 24 * 60 * 60 * 1000` |
62
+ | `weeks` | `weeks(n)` | `7 * days(n)` |
63
+ | `months` | `months(n)` | `30 * days(n)` — an approximation, on purpose |
64
+ | `years` | `years(n)` | `365 * days(n)` — an approximation, on purpose |
65
+
66
+ `months()` and `years()` are calendar lies told for arithmetic: use them for a
67
+ cache window or a retry backoff, never for an age or a birthday.
68
+
30
69
  ### Dates & time
31
70
 
32
71
  | Function | Signature | Returns | Description |
33
72
  | --- | --- | --- | --- |
34
- | `formatDate` | `formatDate(date, format?)` | `string` | Format a date/string/number as text. |
35
- | `relativeTime` | `relativeTime(date)` | `string` | A human label, e.g. `"2 minutes ago"`. |
36
73
  | `now` | `now()` | `number` | Timestamp in milliseconds. |
74
+ | `today` | `today()` | `string` | This day as `"YYYY-MM-DD"`. |
75
+ | `yesterday` | `yesterday()` | `string` | Yesterday as `"YYYY-MM-DD"`. |
76
+ | `tomorrow` | `tomorrow()` | `string` | Tomorrow as `"YYYY-MM-DD"`. |
77
+ | `dayOfWeek` | `dayOfWeek(date)` | `string` | Full weekday name, e.g. `"Monday"`. |
78
+ | `monthName` | `monthName(date)` | `string` | Full month name, e.g. `"January"`. |
79
+ | `isToday` | `isToday(date)` | `boolean` | Same calendar day as now. |
80
+ | `isYesterday` | `isYesterday(date)` | `boolean` | The day before now. |
81
+ | `isTomorrow` | `isTomorrow(date)` | `boolean` | The day after now. |
82
+ | `isWeekend` | `isWeekend(date)` | `boolean` | Saturday or Sunday. |
83
+ | `isLeapYear` | `isLeapYear(year)` | `boolean` | Whether a four-digit year has a February 29. |
84
+ | `formatDate` | `formatDate(date, format?)` | `string` | Tokens `YYYY MM DD HH mm ss`; anything else passes through. |
85
+ | `formatDateLong` | `formatDateLong(date, locale?)` | `string` | `"Monday, January 15, 2024"`. |
86
+ | `formatDateShort` | `formatDateShort(date, locale?)` | `string` | `"Jan 15, 2024"`. |
87
+ | `formatTime` | `formatTime(date)` | `string` | 24-hour `"14:05:30"`. |
88
+ | `formatTimeAmPm` | `formatTimeAmPm(date)` | `string` | 12-hour `"02:05:30 PM"`. |
89
+ | `parseDate` | `parseDate(str, format?)` | `Date` | Reads `"15/01/2024"` with `"DD/MM/YYYY"`. |
90
+ | `dateDiff` | `dateDiff(a, b, unit?)` | `number` | Absolute difference; `unit` is one of the names above. |
91
+ | `addDays` | `addDays(date, n)` | `Date` | `n` calendar days along (negative goes back). |
92
+ | `addHours` / `addMinutes` / `addSeconds` | `addHours(date, n)` | `Date` | Elapsed time, not calendar time. |
93
+ | `subtractDays` / `subtractHours` / `subtractMinutes` / `subtractSeconds` | `subtractDays(date, n)` | `Date` | The same steps backwards. |
94
+ | `timeAgo` | `timeAgo(date)` | `string` | `"5 minutes ago"`, `"just now"` under a second. |
95
+ | `relativeTime` | `relativeTime(date)` | `string` | Alias of `timeAgo`. |
96
+ | `timeAgoLocale` | `timeAgoLocale(date, locale?)` | `string` | The same label in `"en"` or `"es"` — `"hace 1 hora"`. |
97
+ | `waitFor` | `waitFor(ms)` | `Promise<void>` | Resolves after `ms`. |
98
+ | `wait` / `sleep` | `sleep(ms)` | `Promise<void>` | Aliases of `waitFor`. |
99
+ | `waitUntil` | `waitUntil(condition, timeout?, interval?)` | `Promise<true>` | Polls `condition`; **throws** at the timeout (default 30s, 1s apart). |
100
+ | `waitForElement` | `waitForElement(selector, timeout?, interval?)` | `Promise<Element>` | Polls the DOM; needs a document, so call it from a screen (default 10s, 0.5s apart). |
101
+ | `timer` | `timer(label)` | `{ stop, elapsed }` | `stop()` logs and returns the milliseconds; `elapsed()` reads without logging. |
102
+ | `every` | `every(ms, callback)` | `number` | `setInterval` with the duration first. Release the id it returns. |
103
+ | `everySecond` / `everyMinute` / `everyHour` / `everyDay` | `everySecond(callback)` | `number` | The same, at a named period. |
104
+
105
+ `date` is a `Date`, a timestamp, or a string. A bare `"YYYY-MM-DD"` is read as a
106
+ **calendar day in the reader's own zone**, so `isToday(today())` is true on every
107
+ machine; a string with an offset or a `Z` is read as an instant, and a formatter
108
+ then prints it in local time. `addDays` counts days on the calendar, which is why
109
+ it survives a DST night, while `addHours` counts real elapsed time — the right
110
+ choice for a countdown and the wrong one for "this time next week".
37
111
 
38
112
  ### Media time
39
113
 
@@ -58,6 +132,40 @@ Text({
58
132
  // "Hello Fletbox — 2 minutes ago"
59
133
  ```
60
134
 
135
+ ### A due date, read as a day
136
+
137
+ ```javascript
138
+ import { addDays, dateDiff, dayOfWeek, formatDate, isWeekend, timeAgoLocale } from "flet-box";
139
+
140
+ const due = addDays(new Date(), 7);
141
+ const label = `${dayOfWeek(due)}, ${formatDate(due, "DD MMM")} · in ${dateDiff(new Date(), due, "days")} days`;
142
+ const note = isWeekend(due) ? " (it lands on a weekend)" : "";
143
+
144
+ Text({ text: label + note });
145
+ Text({ text: timeAgoLocale(Date.now() - seconds(90), "es") }); // "hace 1 minuto"
146
+ ```
147
+
148
+ ### Waiting, and releasing what repeats
149
+
150
+ ```javascript
151
+ import { Column, Text, everySecond, formatTime, now, seconds, useState, waitUntil } from "flet-box";
152
+
153
+ export const ClockScreen = () => {
154
+ const [clock, setClock] = useState("clock", formatTime(now()));
155
+ const root = Column({ children: [Text({ text: clock })] });
156
+
157
+ const tick = everySecond(() => setClock(formatTime(now())));
158
+ // R6: a repeating helper hands back an id, and the screen releases it.
159
+ root.onUnmount(() => clearInterval(tick));
160
+
161
+ waitUntil(() => navigator.onLine, seconds(30), seconds(1))
162
+ .then(() => setClock("back online"))
163
+ .catch(() => setClock("still offline")); // waitUntil throws at its timeout
164
+
165
+ return root;
166
+ };
167
+ ```
168
+
61
169
  ### Full example
62
170
 
63
171
  ```javascript
@@ -69,7 +177,9 @@ const label = formatMediaProgress(90, 300); // "1:30 / 5:00"
69
177
 
70
178
  ## Notes
71
179
 
72
- - `formatDate(date, format?)` accepts a `Date`, a timestamp, or a date string; `format` is optional and platform-specific tokens apply.
180
+ - `formatDate(date, format?)` accepts a `Date`, a timestamp, or a date string. The tokens it reads are `YYYY MM DD HH mm ss`; it has no `ddd`, so build a weekday name with `dayOfWeek()`.
181
+ - `waitUntil()` and `waitForElement()` **throw** on their timeout — await them inside a `try`, or attach a `.catch`.
182
+ - `every()`, `everySecond()` and friends return a `setInterval` id. Per the working contract, whatever a screen registers it also releases in `onUnmount`.
73
183
  - The media-time helpers are used by the [Audio](../widget/Audio.md) and [Video](../widget/Video.md) widgets.
74
184
  - All functions return new values — inputs are never modified.
75
185
 
@@ -130,6 +130,8 @@ const faq = Accordion({
130
130
 
131
131
  - onToggle receives the new expanded boolean whenever the header is activated.
132
132
  - disabled blocks toggling, and animate: false skips the open/close transition.
133
+ - A shut panel still shows its edge: the header keeps the divider and the
134
+ - 'contained' variant paints the raised plate, so it never reads as plain text.
133
135
 
134
136
  ## Related widgets
135
137
 
@@ -9,9 +9,9 @@ Use it to animate a widget's position, transform, color, or size without hand-wr
9
9
  ## Quick start
10
10
 
11
11
  ```javascript
12
- import { AnimatedBox, Text, pulse } from "flet-box";
12
+ import { AnimatedBox, Text } from "flet-box";
13
13
 
14
- const pulse = AnimatedBox({
14
+ const badge = AnimatedBox({
15
15
  child: Text({ text: "Hello" }),
16
16
  animations: [{ effect: "scale", from: 1, to: 1.2 }],
17
17
  });
@@ -23,10 +23,10 @@ const pulse = AnimatedBox({
23
23
 
24
24
  | Prop | Type | Description |
25
25
  | --- | --- | --- |
26
- | `animations` | `AnimationStep[]` | Animations to apply, as a name or an array of names. |
26
+ | `animations` | `AnimationStep[]` | The steps to play, each `{ effect, from, to }` plus its own `duration`, `delay`, `loop` and `reverse`. |
27
27
  | `child` | `Widget` | A single child widget. Alias: `children` for a list. |
28
- | `timing` | `string` | Timing function for the animation. |
29
- | `delay` | `string` | Delay before the animation starts, in milliseconds. |
28
+ | `timing` | `string` | Timing function for the animation, passed straight to CSS. |
29
+ | `delay` | `number \| string` | Default delay for the steps that do not name one: milliseconds as a number, or a CSS time as a string (`"0.2s"`). |
30
30
  | `fillMode` | `string` | How styles persist before and after the animation (CSS `fill-mode`). |
31
31
  | `top` | `number \| string` | The `top` value for the widget. |
32
32
  | `right` | `number \| string` | The `right` value for the widget. |
@@ -87,9 +87,14 @@ import { AnimatedBox, Text } from "flet-box";
87
87
 
88
88
  const toast = AnimatedBox({
89
89
  child: Text({ text: "Saved" }),
90
- animations: [{ effect: "opacity", from: 0, to: 1 }],
91
- timing: "ease-in-out",
92
- delay: "200ms",
90
+ // Each step keeps its own clock: the fade is over while the lift is still moving.
91
+ animations: [
92
+ { effect: "opacity", from: 0, to: 1, duration: 200 },
93
+ { effect: "translateY", from: 16, to: 0, duration: 400, delay: 120 },
94
+ { effect: "backgroundColor", from: "#fef3c7", to: "#ffffff", duration: 600 },
95
+ ],
96
+ timing: "ease-out",
97
+ delay: 200,
93
98
  fillMode: "forwards",
94
99
  top: 16,
95
100
  right: 16,
@@ -98,24 +103,45 @@ const toast = AnimatedBox({
98
103
 
99
104
  ## Tips
100
105
 
101
- - Each animation is { effect, from, to } where effect is a CSS property like opacity,
102
- - scale, translateY, or backgroundColor. It runs once and holds `to` at the end.
103
- - Set timing (ease, ease-out, linear) and delay at the widget level; add reverse: true
104
- - to cycle back to `from`, together with loop: true for a breathing pulse.
105
- - top, right, bottom, and left are forwarded to the child as positioning hints for
106
- - layout systems.
106
+ - A step is `{ effect, from, to }`, where the effect is one of `scale`, `scaleX`,
107
+ `scaleY`, `rotate`, `rotateX`, `rotateY`, `rotateZ`, `skewX`, `skewY`, `translateX`,
108
+ `translateY`, `translateZ`, `opacity`, `color`, `backgroundColor`, `borderColor`,
109
+ `borderWidth`, `borderRadius`, `width`, `height`, `boxShadow`, `background` or
110
+ `textGradient` (the margin and padding steps work as well). A name outside that list
111
+ does nothing, silently.
112
+ - Each step's `duration`, `delay` and `loop` are read off **that step**:
113
+ a 600 ms `scale` and a 400 ms `opacity` in one entrance run together for 600 ms,
114
+ with the fade holding from 400 ms on. Times are milliseconds as a number
115
+ (`duration: 400`) or a CSS time as a string (`delay: "0.4s"`); a bare number as a
116
+ `delay` used to make the whole `animation` shorthand invalid, which read as the
117
+ widget not animating at all.
118
+ - `reverse: true` makes a step swing back to `from` instead of holding `to`; with
119
+ `loop: true` it breathes.
120
+ - A looping step and a one-shot step cannot share one `@keyframes` rule — repeating
121
+ the rule repeats the entrance — so they are generated as two rules. Two rules
122
+ cannot compose one `transform`, so keep every transform step in one group: mixing
123
+ a looping `rotate` with a one-shot `translateY` moves only one of them, and the
124
+ widget warns about it.
125
+ - A length is a framework unit, not a pixel: `translateY: 20` is `20 / base-font-size`
126
+ rem, so the movement grows with the reader's font like the rest of the UI.
127
+ - `top`, `right`, `bottom` and `left` are forwarded to the child's dataset as
128
+ positioning hints for layout systems.
107
129
 
108
130
  ## Accessibility
109
131
 
110
- - Honor prefers-reduced-motion by keeping animations subtle or giving users a way to
111
- - disable them.
132
+ - Nothing to do here: the engine reads `prefers-reduced-motion` and the
133
+ [motion mode](../tools/animation.md#reduced-motion), and a looping or entering
134
+ step collapses to one frame that lands on its `to` values, so content is never
135
+ held invisible behind an animation the reader refused.
112
136
 
113
137
  ## Behavior
114
138
 
115
- - The first animation step is applied as an inline style immediately, so there is no
116
- - flash of the final state before the keyframes run.
117
- - Generated @keyframes are injected once per sequence and reused, so repeated
118
- - animations stay cheap.
139
+ - Every step's `from` is written on the element before its rule starts, including
140
+ the ones that begin later — so there is no flash of the final state before the
141
+ keyframes run, and no flash of the *first* step's end while a delayed step waits.
142
+ - Generated `@keyframes` are named by their content and injected once, so a sequence
143
+ repeated — or one word staggered across `AnimatedText` letters — reuses one rule
144
+ instead of adding a `<style>` per call.
119
145
 
120
146
  ## Related widgets
121
147
 
@@ -24,9 +24,9 @@ const title = AnimatedText({
24
24
  | Prop | Type | Description |
25
25
  | --- | --- | --- |
26
26
  | `child` | `Widget` | A single child widget. Alias: `children` for a list. |
27
- | `animations` | `AnimationStep[]` | Animations to apply, as a name or an array of names. |
27
+ | `animations` | `AnimationStep[]` | The steps to play, each `{ effect, from, to }` plus its own `duration`, `delay`, `loop` and `reverse`. |
28
28
  | `sameTime` | `boolean` | Runs child animations at the same time. |
29
- | `delayBetween` | `number` | Stagger delay between each child animation. |
29
+ | `delayBetween` | `number` | Stagger between each child animation, in seconds (`0.05` is 50 ms). |
30
30
  | `orientation` | `'row' \| 'column'` | Orientation of the widget or control. |
31
31
 
32
32
  ### Common props
@@ -92,11 +92,17 @@ const title = AnimatedText({
92
92
 
93
93
  ## Tips
94
94
 
95
- - child must be a Text widget, because its text is split into single-character
96
- - widgets that inherit the original props.
97
- - Set sameTime: false and control delayBetween to create a wave; use sameTime: true
98
- - for a synchronized pop.
99
- - orientation: "column" stacks the letters vertically for vertical-word effects.
95
+ - `child` must be a Text widget, because its text is split into single-character
96
+ widgets that inherit the original props.
97
+ - Set `sameTime: false` and control `delayBetween` to create a wave; use
98
+ `sameTime: true` for a synchronized pop. `delayBetween` is in **seconds** —
99
+ `0.05` staggers the letters 50 ms apart, while `5` is a five-second wait per
100
+ character.
101
+ - The letters share one generated `@keyframes` rule and each carries its own delay,
102
+ so a staggered headline costs one rule, not one per character.
103
+ - `orientation: "column"` stacks the letters vertically for vertical-word effects.
104
+ - Each step is [AnimatedBox's](AnimatedBox.md) step, so a per-letter `duration`
105
+ belongs to the step, not to the widget.
100
106
 
101
107
  ## Accessibility
102
108
 
@@ -114,6 +114,7 @@ const inbox = Badge({
114
114
 
115
115
  - showZero: false hides the marker entirely when value is 0.
116
116
  - A borderWidth with a matching borderColor creates a clean cutout against the child.
117
+ - The wrapper is the child's box: it shrinks to the child and refuses a flex parent's cross-axis stretch, so every `position` anchors on a corner of the child and not on the edge of the row it happens to sit in.
117
118
 
118
119
  ## Related widgets
119
120
 
@@ -47,6 +47,7 @@ natural. Each group below maps to the same underlying style or behavior:
47
47
  | `flexDirection` | `direction` | CSS `flex-direction` |
48
48
  | `justifyContent` | `justify` | CSS `justify-content` |
49
49
  | `alignItems` | `align` | CSS `align-items` |
50
+ | `alignSelf` | `selfAlign` | CSS `align-self` (one child's own answer to `alignItems`) |
50
51
  | `flexWrap` | `wrap` | CSS `flex-wrap` |
51
52
  | `lineHeight` | `lineH` | CSS `line-height` |
52
53
  | `letterSpacing` | `letterSpace` | CSS `letter-spacing` |
@@ -70,9 +71,31 @@ These expand to two sides at once (numeric values are pixels):
70
71
  ## Layout and display
71
72
 
72
73
  `display`, `flexDirection` (`direction`), `justifyContent` (`justify`),
73
- `alignItems` (`align`), `flexWrap` (`wrap`), `flex`, `flexGrow`, `flexShrink`,
74
- `flexBasis`, `gap`, `boxSizing`, `overflow`, `overflowX`, `overflowY`,
75
- `position` (`pos`), `top`, `right`, `bottom`, `left`, `inset`, `zIndex` (`z`).
74
+ `alignItems` (`align`), `alignSelf` (`selfAlign`), `flexWrap` (`wrap`), `flex`,
75
+ `flexGrow`, `flexShrink`, `flexBasis`, `gap`, `boxSizing`, `overflow`,
76
+ `overflowX`, `overflowY`, `position` (`pos`), `top`, `right`, `bottom`, `left`,
77
+ `inset`, `zIndex` (`z`).
78
+
79
+ ### One child, its own alignment
80
+
81
+ `alignItems` is the parent's answer for every child; `alignSelf` is one child's
82
+ answer for itself. The values are CSS's: `'auto'` (inherit the parent),
83
+ `'flex-start'`, `'center'`, `'flex-end'`, `'baseline'`, `'stretch'`. It goes on
84
+ the child, on any widget — `alignSelf` is a `CommonProps` entry, not a layout
85
+ prop — and it is what keeps a short label from being stretched to the full height
86
+ of a row of inputs:
87
+
88
+ ```js
89
+ Row({
90
+ alignItems: 'stretch',
91
+ gap: 8,
92
+ children: [
93
+ Input({ label: 'Coupon', value: '', onChange: () => {} }),
94
+ // The row stretches its children; this one refuses.
95
+ Button({ text: 'Apply', onPress: () => {}, alignSelf: 'flex-end' }),
96
+ ],
97
+ });
98
+ ```
76
99
 
77
100
  ### Stack positioning
78
101
 
@@ -136,6 +136,7 @@ const trend = Chart({
136
136
  ## Behavior
137
137
 
138
138
  - The canvas redraws when data, labels, type or styling props change.
139
+ - Grid lines and their labels sit on one round step (1, 2 or 5 times a power of ten), so the numbers agree with the lines they name.
139
140
  - Candle charts read open, high, low and close in that order from each data object.
140
141
 
141
142
  ## Related widgets
@@ -28,6 +28,7 @@ const terms = Checkbox({
28
28
  | `size` | `number` | Overall size preset or pixel value, depending on the widget. |
29
29
  | `activeColor` | `Color` | Color used in the active state. |
30
30
  | `label` | `string` | Label or caption shown near the control. |
31
+ | `variant` | `'outlined' \| 'filled'` | How the checked state reads: `outlined` keeps the box hollow with the tick in `activeColor`, `filled` turns the box into an `activeColor` plate and takes the tick to whichever ink reads on it. |
31
32
 
32
33
  ### Common props
33
34
 
@@ -80,10 +81,11 @@ const remember = Checkbox({
80
81
  import { Checkbox } from "flet-box";
81
82
 
82
83
  const updates = Checkbox({
83
- checked: false,
84
+ checked: true,
84
85
  label: "Email me product updates",
85
86
  size: 24,
86
87
  activeColor: "#7c3aed",
88
+ variant: "filled",
87
89
  disabled: false,
88
90
  onCheck: (checked) => {
89
91
  console.log("updates:", checked);
@@ -95,6 +97,7 @@ const updates = Checkbox({
95
97
 
96
98
  - Use Checkbox inside forms where several options may be true together; use Switch for one immediate on/off setting.
97
99
  - onCheck fires with the new boolean, so write it straight into your state.
100
+ - A filled checkbox needs no second color: `activeColor` is the plate and the tick reads itself on it.
98
101
 
99
102
  ## Accessibility
100
103
 
@@ -104,6 +107,9 @@ const updates = Checkbox({
104
107
 
105
108
  - Uncontrolled when checked is omitted; the widget tracks its own state and reports changes through onCheck.
106
109
  - The returned element exposes setChecked(value) and getChecked() for programmatic control.
110
+ - `size` is the edge of the drawn box; the press target around it stays 40 units and does not shrink with it.
111
+ - The box carries a step of the radius scale (4 by default, 8 from `size: 28`), and a `borderRadius` rounds the box rather than the invisible target around it.
112
+ - `variant: 'filled'` changes the checked state only: the box becomes an `activeColor` plate and the tick takes whichever ink reads on it. An unchecked box keeps its rim, because a filled box that means off is a button.
107
113
 
108
114
  ## Related widgets
109
115
 
@@ -148,6 +148,8 @@ const chart = CircularChart({
148
148
  ## Behavior
149
149
 
150
150
  - centerContent accepts a string or a widget rendered in the donut hole.
151
+ - The hole is the page showing through, not a filled disc, so a donut reads
152
+ - as a ring on any surface.
151
153
  - onHover receives (slice | null, index) as the pointer moves between slices, and
152
154
  - onComplete fires when the entry sweep finishes.
153
155
 
@@ -116,6 +116,7 @@ openDrawer();
116
116
  - The drawer keeps itself sized to the viewport on resize (`dimensions.addListener`), and `destroy()` removes that listener and the `keydown` handler.
117
117
  - When you pass `drawer` to a [Scaffold](Scaffold.md), a `menu` icon in the [AppBar](AppBar.md) opens it, `closeDrawerOnNavigate` can close it on route change, and the shell tears it down with its `_cleanup`.
118
118
  - Keydown (`Escape`) and resize listeners are only active while the drawer exists or is open — no leaks on the normal lifecycle.
119
+ - The panel paints the screen edge to edge and pads its content by `env(safe-area-inset-*)`, so on a notched phone the `header` clears the status bar and the `footer` clears the home indicator without the background leaving a gap. Nothing has to be configured, and the `width`/`height` of the panel stay what you passed: the padding is inside the box (`border-box`). On a device with no inset the values are `0`, so the drawer looks exactly as it did.
119
120
 
120
121
  ## Related widgets
121
122
  - [DrawerItem](DrawerItem.md)
@@ -41,6 +41,7 @@ const grid = GridView({
41
41
  | `columns` | `number` | Number of grid columns. |
42
42
  | `spacing` | `number` | Space between grid items. |
43
43
  | `itemHeight` | `number` | Height of each grid cell. |
44
+ | `children` | `Widget[] \| Widget` | The cells themselves, laid out `columns` per row. The same contract as `data` + `renderItem` read the other way round: pass built widgets here, or the items plus a builder. `data` wins when both are given. |
44
45
 
45
46
  ### Common props
46
47
 
@@ -115,7 +116,7 @@ const grid = GridView({
115
116
 
116
117
  - `columns` sets the number of columns and `spacing` sets the gap between cells.
117
118
  - `itemHeight` fixes each cell's height, which is what makes virtualization possible.
118
- - It accepts the same `data` and `renderItem` contract as ListView.
119
+ - Give it `data` + `renderItem` when the cells are built from values, and `children` when you already have the widgets: the two are the same contract read from either end.
119
120
 
120
121
  ## Accessibility
121
122
 
@@ -124,6 +125,8 @@ const grid = GridView({
124
125
  ## Behavior
125
126
 
126
127
  - GridView is implemented as ListView with grid wrapping enabled, so it shares the same scrolling and update APIs.
128
+ - Each cell is stretched to fill its slot: `itemHeight` tall, with half of `spacing` on every side so two neighbours meet at exactly one gutter. A cell that asks for its own height keeps it.
129
+ - The number of columns is fixed, not a breakpoint: cells are percentages of the container, so the grid adapts to the width it is given.
127
130
  - Rows outside the viewport are removed, so keep per-tile state out of the DOM and in widget state when needed.
128
131
 
129
132
  ## Related widgets
@@ -127,6 +127,7 @@ const password = Input({
127
127
 
128
128
  - Match type to the data (email, password, search) so mobile keyboards adapt.
129
129
  - Set validation and showValidationMessage to get inline feedback without extra code.
130
+ - The trailing controls are Icon glyphs from the Material face the app links in its own index.html, drawn in the theme's secondary ink. An emoji in that slot ignores the colour, the font and the size the field sets.
130
131
 
131
132
  ## Accessibility
132
133
 
@@ -137,6 +138,7 @@ const password = Input({
137
138
 
138
139
  - onChange fires on every keystroke; onEnter fires when the user presses Enter.
139
140
  - clearable and passwordToggle add trailing controls that manage the value for you.
141
+ - passwordToggle swaps visibility for visibility_off and moves the button's accessible name with the state.
140
142
 
141
143
  ## Related widgets
142
144
 
@@ -23,6 +23,7 @@ const install = InstallButton({
23
23
  | Prop | Type | Description |
24
24
  | --- | --- | --- |
25
25
  | `text` | `string` | Visible text content. |
26
+ | `icon` | `string` | Name of the icon to render. |
26
27
  | `variant` | `'filled' \| 'outlined' \| 'text'` | Visual variation or style preset. |
27
28
  | `size` | `'small' \| 'medium' \| 'large'` | Overall size preset or pixel value, depending on the widget. |
28
29
  | `borderRadius` | `number` | Rounds the corners of the widget. |
@@ -161,6 +161,10 @@ const guide = Markdown({
161
161
  - CSS into each other.
162
162
  - Rendered HTML is sanitized before insert; allowDangerousHtml stays false by default
163
163
  - for untrusted input.
164
+ - Inline code writes the palette's readable red, so 12px words clear 4.5:1 on the code
165
+ - plate in both themes.
166
+ - A link takes the palette's readable blue, because in light `primary` is the page's
167
+ - own ink and the link would vanish into the sentence.
164
168
 
165
169
  ## Related widgets
166
170
 
@@ -117,6 +117,8 @@ const rating = Rating({
117
117
 
118
118
  - onChange receives the numeric value each time the user picks a rating.
119
119
  - readOnly disables onChange while keeping the current value visible.
120
+ - An unfilled star uses the palette's control grey and a filled one its readable
121
+ - amber, so both states clear 3:1 against the surface they sit on.
120
122
 
121
123
  ## Related widgets
122
124
 
@@ -123,6 +123,7 @@ const price = Slider({
123
123
 
124
124
  - onChanged fires continuously while dragging; use onChangeEnd for the value to commit, such as a filter request.
125
125
  - Set step to snap to meaningful increments instead of every integer.
126
+ - On a vertical slider, width is the rail's length and height its thickness.
126
127
 
127
128
  ## Accessibility
128
129
 
@@ -132,6 +133,7 @@ const price = Slider({
132
133
 
133
134
  - Dragging the track or thumb updates the value and fires onChanged; releasing fires onChangeEnd once.
134
135
  - The returned element exposes value, min, max, step, setValue() and getValue(), and disconnects its ResizeObserver on unmount.
136
+ - During a drag the fill follows the pointer with no easing; the 0.1s ease belongs to a value that changes by itself, from a key press or setValue().
135
137
 
136
138
  ## Related widgets
137
139
 
@@ -1,6 +1,6 @@
1
1
  # Stack
2
2
 
3
- Overlay container that lets children occupy the same box. It is relative by default so children can be positioned, and it supports a nine-point alignment.
3
+ Overlay container: every child after the first paints in the same box, anchored by a nine-point `alignment` or by its own edges.
4
4
 
5
5
  ## When to use it
6
6
 
@@ -101,9 +101,10 @@ const stack = Stack({
101
101
 
102
102
  ## Tips
103
103
 
104
- - Stack is `position: "relative"` by default, which makes it a positioning context for absolute children.
105
- - `alignment` anchors children at one of nine points from top-left to bottom-right.
104
+ - The first child stays in the flow and gives the stack its size; every child after it floats over that box.
105
+ - `alignment` anchors the children that name no edges, at one of nine points from top-left to bottom-right. Top-left is the default.
106
106
  - Later children paint on top, so order them from back to front.
107
+ - A child that names its own `top`/`right`/`bottom`/`left` keeps those, and a child that declares `position` is left alone — that is how a child stays in the flow.
107
108
 
108
109
  ## Accessibility
109
110
 
@@ -111,8 +112,8 @@ const stack = Stack({
111
112
 
112
113
  ## Behavior
113
114
 
114
- - The root is a plain block by default and children are laid out per `position` and `alignment`.
115
- - Set `width` and `height` (or `expand`) when the stack needs a definite size for layered content.
115
+ - The root is a flex column, so `alignment` places the in-flow child too; an explicit `justifyContent` or `alignItems` wins over it.
116
+ - The layers take no space. Give the stack a `width` and `height`, or make the first child the size you want the box to be, or there is nothing for them to sit on.
116
117
 
117
118
  ## Related widgets
118
119
 
@@ -122,6 +122,8 @@ const checkout = Stepper({
122
122
 
123
123
  - Steps are clickable, so users can jump around; use onStepChange to validate a step before allowing the move.
124
124
  - variant: icons pulls each step's icon; circles and numbers ignore it.
125
+ - A step's content is a live widget and a DOM node has one parent, so build the steps array where the Stepper is built: sharing one array between two steppers empties the first slot, and the widget warns when it happens.
126
+ - orientation: vertical puts each label beside its marker; horizontal puts it underneath. showLabels: false hides it in both.
125
127
 
126
128
  ## Accessibility
127
129
 
@@ -116,11 +116,12 @@ const tip = Tooltip({
116
116
 
117
117
  - Attach it to any widget via child; the tooltip is a separate portal, so overflow hidden ancestors do not cut it off.
118
118
  - delay controls how long the pointer must rest before the tooltip appears; lower it for dense toolbars, raise it for error-prone hints.
119
- - The bubble is clamped to the viewport edges, so top/left placements can sit slightly inside the window bounds.
119
+ - The bubble keeps to the device's safe area, so a placement is a request: an edge with no room for it gets the opposite edge instead.
120
120
 
121
121
  ## Accessibility
122
122
 
123
123
  - The wrapped child keeps its focus handlers, so keyboard users tabbing onto it get the same tooltip as mouse hover.
124
+ - On a device that cannot hover the bubble stays hidden. iOS answers the first tap with a synthetic mouseenter and never sends the matching mouseleave, so a tap-shown tooltip is a bubble nothing can dismiss. Anything a tooltip explains has to be explained by the control's own label too.
124
125
 
125
126
  ## Behavior
126
127
 
@@ -128,6 +129,7 @@ const tip = Tooltip({
128
129
  - The wrapped child gains showTooltip, hideTooltip, updateContent, and updatePosition helpers for imperative control.
129
130
  - A pointer that leaves before the wait is over never shows the bubble: leaving clears the timer.
130
131
  - tooltipDelay is the old name of delay. When both are given, delay wins.
132
+ - The requested position is a preference, not a promise: when the bubble would not fit between the trigger and the safe-area edge it is placed on the opposite side, and the arrow follows it.
131
133
 
132
134
  ## Related widgets
133
135
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flet-box",
3
- "version": "1.0.9",
3
+ "version": "1.1.1",
4
4
  "description": "Declarative, zero-dependency UI framework in vanilla JS — ship web, PWA, Android and iOS from one codebase",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -19,7 +19,7 @@
19
19
  "dev": "node bin/cli.js run --hot",
20
20
  "build": "node bin/cli.js createBundle",
21
21
  "release": "node bin/cli.js release",
22
- "test": "node scripts/check-widgets.mjs && node scripts/check-widget-scale.mjs && node scripts/check-prop-registry.mjs && node scripts/check-docs-props.mjs && node scripts/check-api-surface.mjs && node scripts/check-api-propscan.mjs && node scripts/check-router-base.mjs && node scripts/check-clean-ui.mjs && node scripts/check-ota-assets.mjs && node scripts/check-pwa-shell.mjs && node scripts/check-cli-install.mjs && python3 tools/snippets-creator-flet-box/build_snippets.py --check && node scripts/check-snippets.mjs",
22
+ "test": "node scripts/check-widgets.mjs && node scripts/check-widget-scale.mjs && node scripts/check-shared-helpers.mjs && node scripts/check-a11y.mjs && node scripts/check-prop-registry.mjs && node scripts/check-docs-props.mjs && node scripts/check-api-surface.mjs && node scripts/check-api-propscan.mjs && node scripts/check-time-helpers.mjs && node scripts/check-router-base.mjs && node scripts/check-clean-ui.mjs && node scripts/check-framework-ui.mjs && node scripts/check-skills.mjs && node scripts/check-app-types.mjs && node scripts/check-ota-assets.mjs && node scripts/check-pwa-shell.mjs && node scripts/check-cli-install.mjs && python3 tools/snippets-creator-flet-box/build_snippets.py --check && node scripts/check-snippets.mjs",
23
23
  "test:browser": "node scripts/run-browser-tests.mjs"
24
24
  },
25
25
  "keywords": [
@@ -47,12 +47,14 @@
47
47
  "devDependencies": {
48
48
  "chokidar": "^4.0.3",
49
49
  "esbuild": "^0.28.0",
50
+ "typescript": "^7.0.2",
50
51
  "ws": "^8.21.0"
51
52
  },
52
53
  "files": [
53
54
  "bin/",
54
55
  "src/",
55
56
  "docs/",
57
+ "skills/",
56
58
  "README.md",
57
59
  "LICENSE"
58
60
  ],