flet-box 1.0.6 → 1.0.8

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 (162) hide show
  1. package/README.md +28 -1
  2. package/bin/cli.js +90 -11
  3. package/bin/commands/auditFix.js +682 -0
  4. package/bin/commands/checkApi.js +20 -6
  5. package/bin/commands/checkUi.js +1369 -0
  6. package/bin/commands/create.js +86 -12
  7. package/bin/commands/createBundle.js +75 -1
  8. package/bin/commands/images.js +349 -0
  9. package/bin/commands/pruneAssets.js +170 -0
  10. package/bin/commands/release.js +34 -1
  11. package/bin/commands/runCloudflare.js +47 -9
  12. package/bin/commands/runServer.js +91 -25
  13. package/bin/commands/toPng.js +346 -0
  14. package/bin/commands/unpack.js +1173 -0
  15. package/bin/ota/loader.js +90 -0
  16. package/bin/utils/appSources.js +101 -0
  17. package/bin/utils/bundleConfig.js +166 -0
  18. package/bin/utils/dtsProps.js +147 -0
  19. package/bin/utils/imageTools.js +271 -0
  20. package/bin/utils/images/bmp.js +99 -0
  21. package/bin/utils/images/gif.js +244 -0
  22. package/bin/utils/images/index.js +163 -0
  23. package/bin/utils/images/jpeg.js +643 -0
  24. package/bin/utils/images/png.js +486 -0
  25. package/bin/utils/images/unsupported.js +29 -0
  26. package/bin/utils/neutralise.js +109 -6
  27. package/bin/utils/pwaShell.js +137 -0
  28. package/bin/utils/templates.js +1653 -471
  29. package/docs/README.md +2 -0
  30. package/docs/cli/README.md +144 -1
  31. package/docs/guides/README.md +2 -0
  32. package/docs/guides/clean-ui.md +211 -0
  33. package/docs/guides/minification-and-protection.md +1 -1
  34. package/docs/guides/online-updates.md +105 -9
  35. package/docs/tools/README.md +1 -1
  36. package/docs/tools/animation.md +1 -1
  37. package/docs/tools/markdown.md +3 -3
  38. package/docs/tools/theme.md +41 -1
  39. package/docs/widget/Accordion.md +4 -0
  40. package/docs/widget/AdMob.md +5 -1
  41. package/docs/widget/AdSense.md +4 -0
  42. package/docs/widget/AnimatedBox.md +7 -3
  43. package/docs/widget/AnimatedText.md +4 -0
  44. package/docs/widget/Audio.md +5 -1
  45. package/docs/widget/Avatar.md +15 -3
  46. package/docs/widget/Badge.md +4 -0
  47. package/docs/widget/Button.md +4 -0
  48. package/docs/widget/COMMON_PROPS.md +52 -0
  49. package/docs/widget/Card.md +4 -0
  50. package/docs/widget/Carousel.md +4 -0
  51. package/docs/widget/Chart.md +4 -0
  52. package/docs/widget/Checkbox.md +4 -0
  53. package/docs/widget/Chip.md +4 -0
  54. package/docs/widget/CircularBar.md +4 -0
  55. package/docs/widget/CircularChart.md +4 -0
  56. package/docs/widget/CodeViewer.md +4 -0
  57. package/docs/widget/Column.md +4 -0
  58. package/docs/widget/Container.md +4 -0
  59. package/docs/widget/DataTable.md +4 -0
  60. package/docs/widget/Divider.md +4 -0
  61. package/docs/widget/DraggBox.md +6 -2
  62. package/docs/widget/Dropdown.md +6 -0
  63. package/docs/widget/DroppBox.md +5 -1
  64. package/docs/widget/FloatingActionButton.md +4 -0
  65. package/docs/widget/GridView.md +4 -0
  66. package/docs/widget/Icon.md +4 -0
  67. package/docs/widget/Image.md +14 -3
  68. package/docs/widget/Input.md +4 -0
  69. package/docs/widget/Inspector.md +2 -2
  70. package/docs/widget/InstallButton.md +4 -0
  71. package/docs/widget/ListTile.md +4 -0
  72. package/docs/widget/ListView.md +4 -0
  73. package/docs/widget/Maps.md +191 -0
  74. package/docs/widget/Markdown.md +4 -0
  75. package/docs/widget/MatrixRain.md +4 -0
  76. package/docs/widget/Pagination.md +4 -0
  77. package/docs/widget/ParallaxBox.md +4 -0
  78. package/docs/widget/ProgressBar.md +4 -0
  79. package/docs/widget/QRCode.md +4 -0
  80. package/docs/widget/README.md +12 -10
  81. package/docs/widget/Radio.md +5 -1
  82. package/docs/widget/Rating.md +4 -0
  83. package/docs/widget/Row.md +4 -0
  84. package/docs/widget/Skeleton.md +4 -0
  85. package/docs/widget/Slider.md +4 -0
  86. package/docs/widget/Stack.md +4 -0
  87. package/docs/widget/Stepper.md +4 -0
  88. package/docs/widget/Switch.md +4 -0
  89. package/docs/widget/Tabs.md +5 -5
  90. package/docs/widget/Text.md +4 -0
  91. package/docs/widget/Tooltip.md +14 -13
  92. package/docs/widget/TreeView.md +4 -0
  93. package/docs/widget/Video.md +6 -2
  94. package/package.json +2 -2
  95. package/src/animations/AnimatedBox.js +4 -1
  96. package/src/animations/MatrixRain.js +3 -3
  97. package/src/core/hmr-client.js +129 -34
  98. package/src/index.d.ts +195 -7
  99. package/src/index.js +2 -0
  100. package/src/navigations/AppBar.js +22 -16
  101. package/src/navigations/BottomNavigation.js +8 -13
  102. package/src/navigations/CollapsibleSideBar.js +30 -8
  103. package/src/navigations/Drawer.js +25 -15
  104. package/src/navigations/DrawerItem.js +3 -1
  105. package/src/navigations/Scaffold.js +26 -12
  106. package/src/navigations/Tabs.js +104 -59
  107. package/src/tools/color.js +54 -0
  108. package/src/tools/index.js +9 -1
  109. package/src/tools/scale.js +41 -0
  110. package/src/tools/shadow.js +20 -0
  111. package/src/utils/index.js +1 -0
  112. package/src/utils/readableInk.js +19 -0
  113. package/src/utils/syntaxHighlight.js +78 -10
  114. package/src/utils/themes.js +217 -54
  115. package/src/utils/units.js +33 -11
  116. package/src/widget-factory/effects.js +56 -5
  117. package/src/widget-factory/processProps.js +10 -1
  118. package/src/widget-factory/remTool.js +15 -29
  119. package/src/widget-factory/stackPosition.js +18 -3
  120. package/src/widget-factory/translateProps.js +60 -1
  121. package/src/widgets/Accordion.js +20 -23
  122. package/src/widgets/AdMob.js +7 -2
  123. package/src/widgets/Avatar.js +236 -133
  124. package/src/widgets/Badge.js +3 -2
  125. package/src/widgets/BottomSheet.js +14 -13
  126. package/src/widgets/Button.js +69 -30
  127. package/src/widgets/Card.js +6 -2
  128. package/src/widgets/Carousel.js +12 -10
  129. package/src/widgets/Chart.js +28 -5
  130. package/src/widgets/Checkbox.js +76 -18
  131. package/src/widgets/Chip.js +31 -10
  132. package/src/widgets/CircularBar.js +7 -7
  133. package/src/widgets/CircularChart.js +26 -8
  134. package/src/widgets/CodeViewer.js +93 -20
  135. package/src/widgets/Container.js +13 -0
  136. package/src/widgets/DataTable.js +17 -11
  137. package/src/widgets/DraggBox.js +2 -1
  138. package/src/widgets/Dropdown.js +59 -14
  139. package/src/widgets/DroppBox.js +4 -2
  140. package/src/widgets/FloatingActionButton.js +13 -7
  141. package/src/widgets/Icon.js +3 -1
  142. package/src/widgets/Image.js +102 -3
  143. package/src/widgets/Input.js +30 -23
  144. package/src/widgets/Inspector.js +54 -1
  145. package/src/widgets/InstallButton.js +6 -4
  146. package/src/widgets/ListTile.js +4 -3
  147. package/src/widgets/ListView.js +30 -12
  148. package/src/widgets/Maps.js +1495 -0
  149. package/src/widgets/Markdown.js +37 -26
  150. package/src/widgets/Modal.js +14 -11
  151. package/src/widgets/Pagination.js +16 -11
  152. package/src/widgets/ProgressBar.js +6 -4
  153. package/src/widgets/Radio.js +73 -15
  154. package/src/widgets/Rating.js +24 -6
  155. package/src/widgets/Skeleton.js +31 -31
  156. package/src/widgets/Slider.js +29 -18
  157. package/src/widgets/SnackBar.js +35 -39
  158. package/src/widgets/Stepper.js +13 -9
  159. package/src/widgets/Switch.js +59 -18
  160. package/src/widgets/Tooltip.js +24 -12
  161. package/src/widgets/TreeView.js +27 -15
  162. package/src/widgets/index.js +1 -0
package/src/index.d.ts CHANGED
@@ -30,10 +30,48 @@ declare module 'flet-box' {
30
30
  _cleanup(): void;
31
31
  }
32
32
 
33
+ // =========================================================================
34
+ // Posicionamiento
35
+ // =========================================================================
36
+ /**
37
+ * Positional props accepted by every widget.
38
+ *
39
+ * `position: 'stack'` takes the widget out of normal flow and places it
40
+ * against its nearest positioned ancestor; the `center*` / `full*` shorthands
41
+ * only take effect in that mode. Numeric offsets are framework units (pixels
42
+ * on the 16px base, emitted as rem).
43
+ *
44
+ * A few widgets give `position` their own meaning — the toast/badge/tooltip
45
+ * anchor — and therefore omit this declaration, as does `Maps` for `center`.
46
+ */
47
+ export interface StackPositionProps {
48
+ position?: 'static' | 'relative' | 'absolute' | 'fixed' | 'sticky' | 'stack';
49
+ /** Alias of `position`. */
50
+ pos?: 'static' | 'relative' | 'absolute' | 'fixed' | 'sticky' | 'stack';
51
+ top?: Size;
52
+ right?: Size;
53
+ bottom?: Size;
54
+ left?: Size;
55
+ inset?: Size;
56
+ /** Logical spelling of `left`. */
57
+ start?: Size;
58
+ /** Logical spelling of `right`. */
59
+ end?: Size;
60
+ /** Alias of `zIndex`. */
61
+ z?: number;
62
+ zIndex?: number;
63
+ centerX?: boolean;
64
+ centerY?: boolean;
65
+ center?: boolean;
66
+ fullWidth?: boolean;
67
+ fullHeight?: boolean;
68
+ full?: boolean;
69
+ }
70
+
33
71
  // =========================================================================
34
72
  // Props comunes (heredadas by todos the widgets)
35
73
  // =========================================================================
36
- export interface CommonProps {
74
+ export interface CommonProps extends StackPositionProps {
37
75
  width?: Size;
38
76
  height?: Size;
39
77
  // `Size`, not `number`: for a Text this is a font size and for a Button it is
@@ -49,6 +87,12 @@ declare module 'flet-box' {
49
87
  borderRadius?: number | string;
50
88
  elevation?: number;
51
89
  shadow?: string;
90
+ // Glass and filters. A CSS value string, not an object: `backdropFilter` is
91
+ // what makes a translucent card frosted rather than merely see-through, and
92
+ // `maskImage` is what fades a list out at its end.
93
+ filter?: string;
94
+ backdropFilter?: string;
95
+ maskImage?: string;
52
96
  opacity?: number;
53
97
  visible?: boolean;
54
98
  disabled?: boolean;
@@ -242,7 +286,7 @@ declare module 'flet-box' {
242
286
 
243
287
  export interface StackProps extends CommonProps {
244
288
  children?: Widget[];
245
- position?: 'relative' | 'absolute';
289
+ position?: 'relative' | 'absolute' | 'stack';
246
290
  alignment?: 'top-left' | 'top-center' | 'top-right' | 'center-left' | 'center' | 'center-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
247
291
  }
248
292
  export function Stack(props: StackProps): Widget;
@@ -341,6 +385,8 @@ declare module 'flet-box' {
341
385
  height?: Size;
342
386
  fit?: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down';
343
387
  circular?: boolean;
388
+ downloadable?: boolean;
389
+ downloadFileName?: string;
344
390
  onLoad?: (widget: Widget) => void;
345
391
  onError?: (widget: Widget) => void;
346
392
  onPress?: (widget: Widget) => void;
@@ -355,6 +401,8 @@ declare module 'flet-box' {
355
401
  bgColor?: Color;
356
402
  textColor?: Color;
357
403
  shape?: 'circle' | 'rounded' | 'square';
404
+ online?: boolean;
405
+ badgeColor?: Color;
358
406
  onPress?: (widget: Widget) => void;
359
407
  }
360
408
  export function Avatar(props: AvatarProps): Widget & {
@@ -485,7 +533,7 @@ declare module 'flet-box' {
485
533
  getElement(): Widget;
486
534
  };
487
535
 
488
- export interface AdMobProps extends CommonProps {
536
+ export interface AdMobProps extends Omit<CommonProps, 'position'> {
489
537
  adId?: string;
490
538
  isTesting?: boolean;
491
539
  position?: 'top' | 'bottom';
@@ -516,7 +564,7 @@ declare module 'flet-box' {
516
564
  getPlugin(): object | null;
517
565
  };
518
566
 
519
- export interface BadgeProps extends CommonProps {
567
+ export interface BadgeProps extends Omit<CommonProps, 'position'> {
520
568
  value: string | number;
521
569
  child: Widget;
522
570
  bgColor?: Color;
@@ -1278,9 +1326,14 @@ declare module 'flet-box' {
1278
1326
  };
1279
1327
 
1280
1328
  export interface InspectorProps {
1329
+ /** The widget whose call is reconstructed and painted. */
1281
1330
  widget: Widget;
1331
+ /** Starting indentation depth of the printout. */
1282
1332
  indent?: number;
1283
1333
  }
1334
+ /** Paints the reconstructed call of a widget tree. */
1335
+ export function Inspector(props: InspectorProps): Widget;
1336
+ /** Rebuilds the call that made a widget, as a string. */
1284
1337
  export function Inspector(widget: Widget, indent?: number): string;
1285
1338
  export function printWidgetCode(widget: Widget): void;
1286
1339
  export function inspectWidget(widget: Widget): Widget;
@@ -1358,11 +1411,13 @@ declare module 'flet-box' {
1358
1411
  getCurrentIndex(): number;
1359
1412
  };
1360
1413
 
1361
- export interface TooltipProps extends CommonProps {
1414
+ export interface TooltipProps extends Omit<CommonProps, 'position'> {
1362
1415
  text: string;
1363
1416
  child: Widget;
1364
1417
  position?: 'top' | 'bottom' | 'left' | 'right';
1365
1418
  delay?: number;
1419
+ /** @deprecated Alias of `delay`. */
1420
+ tooltipDelay?: number;
1366
1421
  bgColor?: Color;
1367
1422
  textColor?: Color;
1368
1423
  fontSize?: number;
@@ -1700,6 +1755,132 @@ declare module 'flet-box' {
1700
1755
  getSource(): string;
1701
1756
  };
1702
1757
 
1758
+ // =========================================================================
1759
+ // Maps
1760
+ // =========================================================================
1761
+
1762
+ /** A point on the map: `[lat, lng]`, `{lat, lng}` or `{latitude, longitude}`. */
1763
+ export type LatLng = [number, number] | { lat: number; lng: number } | { latitude: number; longitude: number };
1764
+
1765
+ /** `[[south, west], [north, east]]`, a named box, or the points to enclose. */
1766
+ export type MapBounds = [[number, number], [number, number]] | { south: number; west: number; north: number; east: number } | LatLng[];
1767
+
1768
+ /** A pin. A bare `LatLng` is also accepted wherever a marker is expected. */
1769
+ export interface MapMarker {
1770
+ position: LatLng;
1771
+ label?: string;
1772
+ color?: Color;
1773
+ /** Keeps the pooled pin node across updates; auto-generated when omitted. */
1774
+ id?: string | number;
1775
+ /** Degrees clockwise from north, the bearing a GPS fix reports. Turns the pin around its own tip. */
1776
+ rotation?: number;
1777
+ /** `false` lets a tap fall through to the map: a tracked vehicle must not block picking a point. */
1778
+ clickable?: boolean;
1779
+ data?: any;
1780
+ }
1781
+
1782
+ /** Payload of `onMapClick`: the geographic point under the cursor. */
1783
+ export interface MapClick {
1784
+ lat: number;
1785
+ lng: number;
1786
+ zoom: number;
1787
+ }
1788
+
1789
+ /** Payload of `onMoveEnd`: the view once it stops changing. */
1790
+ export interface MapState {
1791
+ center: { lat: number; lng: number };
1792
+ zoom: number;
1793
+ bounds: { south: number; west: number; north: number; east: number };
1794
+ }
1795
+
1796
+ /**
1797
+ * A stroked path — a route, a track, a boundary. Needs at least two usable
1798
+ * points. A bare `LatLng[]` is also accepted wherever a polyline is expected.
1799
+ */
1800
+ export interface MapPolyline {
1801
+ points: LatLng[];
1802
+ /** Stroke colour. Defaults to `polylineColor`. */
1803
+ color?: Color;
1804
+ /** Stroke width in CSS pixels. Defaults to `4`. */
1805
+ width?: number;
1806
+ opacity?: number;
1807
+ dashed?: boolean;
1808
+ /** Identifies the line to `removePolyline()`; auto-generated when omitted. */
1809
+ id?: string | number;
1810
+ }
1811
+
1812
+ /** Payload of `onUserUpdate`: one fix from the watcher, in degrees and metres. */
1813
+ export interface MapFix {
1814
+ lat: number;
1815
+ lng: number;
1816
+ /** Radius of 68% confidence in metres, or null when the browser hides it. */
1817
+ accuracy: number | null;
1818
+ /** Degrees clockwise from true north, or null when the device reports none. */
1819
+ heading: number | null;
1820
+ }
1821
+
1822
+ export interface MapsProps extends Omit<CommonProps, 'center'> {
1823
+ /** Initial view centre. Defaults to `[20, 0]`. */
1824
+ center?: LatLng;
1825
+ /** Integer zoom level, 0 = the whole world. Defaults to `2`. */
1826
+ zoom?: number;
1827
+ minZoom?: number;
1828
+ maxZoom?: number;
1829
+ /** Positions and/or `{position, label, color, id, data}` records. */
1830
+ markers?: Array<MapMarker | LatLng>;
1831
+ /** Colour of the pins that do not set their own. Defaults to `colors.primary`. */
1832
+ markerColor?: Color;
1833
+ /** Position lists and/or `{points, color, width, opacity, dashed, id}` records. */
1834
+ polylines?: Array<MapPolyline | LatLng[]>;
1835
+ /** Stroke of the lines that do not set their own. Defaults to `colors.primary`. */
1836
+ polylineColor?: Color;
1837
+ /** `{z}/{x}/{y}` tile template. Defaults to OpenStreetMap. */
1838
+ tileUrl?: string;
1839
+ /** Credit line; most providers require one. */
1840
+ attribution?: string;
1841
+ showAttribution?: boolean;
1842
+ showZoomControl?: boolean;
1843
+ /** When false the view only moves through the imperative API. */
1844
+ interactive?: boolean;
1845
+ scrollZoom?: boolean;
1846
+ doubleClickZoom?: boolean;
1847
+ /** Draws the browser position; asks for permission on the first map paint. */
1848
+ showUserLocation?: boolean;
1849
+ /**
1850
+ * Keeps the view centred on the browser position. Implies the dot and asks
1851
+ * the browser for fresh high-accuracy fixes instead of a cached one.
1852
+ */
1853
+ followUser?: boolean;
1854
+ onMapClick?: (point: MapClick, event: PointerEvent) => void;
1855
+ onMarkerClick?: (marker: MapMarker, event: MouseEvent) => void;
1856
+ onMoveEnd?: (state: MapState) => void;
1857
+ /** Fires on each watched fix, including the ones while `followUser` is off. */
1858
+ onUserUpdate?: (fix: MapFix) => void;
1859
+ }
1860
+ export function Maps(props: MapsProps): Widget & {
1861
+ setCenter(position: LatLng, zoom?: number): Widget;
1862
+ getCenter(): { lat: number; lng: number };
1863
+ getZoom(): number;
1864
+ zoomTo(zoom: number): Widget;
1865
+ panBy(dx: number, dy: number): Widget;
1866
+ fitBounds(bounds: MapBounds, padding?: number): Widget;
1867
+ getState(): MapState;
1868
+ addMarker(marker: MapMarker | LatLng): MapMarker | null;
1869
+ /** Replace the marker with the same id, or append it when that id is new. */
1870
+ setMarker(marker: MapMarker | LatLng): MapMarker | null;
1871
+ removeMarker(idOrMarker: string | number | MapMarker): Widget;
1872
+ clearMarkers(): Widget;
1873
+ getMarkers(): MapMarker[];
1874
+ addPolyline(line: MapPolyline | LatLng[]): MapPolyline | null;
1875
+ removePolyline(idOrLine: string | number | MapPolyline): Widget;
1876
+ clearPolylines(): Widget;
1877
+ getPolylines(): MapPolyline[];
1878
+ setFollowUser(on: boolean): Widget;
1879
+ locate(settings?: { setCenter?: boolean; zoom?: number; timeout?: number; maximumAge?: number; highAccuracy?: boolean }): Promise<{ lat: number; lng: number }> | null;
1880
+ setUserPosition(position: LatLng | number[] | null, settings?: { setCenter?: boolean; zoom?: number }): Widget;
1881
+ render(): Widget;
1882
+ };
1883
+
1703
1884
  // =========================================================================
1704
1885
  // Core
1705
1886
  // =========================================================================
@@ -1752,6 +1933,10 @@ declare module 'flet-box' {
1752
1933
  export const colors: Record<string, string>;
1753
1934
  export const palettes: Record<string, Record<string, string>>;
1754
1935
  export function setTheme(themeName: 'light' | 'dark'): Record<string, string>;
1936
+ export function registerPalette(
1937
+ themeName: string,
1938
+ palette: Record<string, string>
1939
+ ): Record<string, string>;
1755
1940
  export function getTheme(): string;
1756
1941
  export function toggleTheme(): string;
1757
1942
  export function getColor(colorName: string, opacity?: number): string;
@@ -1952,9 +2137,12 @@ declare module 'flet-box' {
1952
2137
  isDark(): boolean;
1953
2138
  isLight(): boolean;
1954
2139
  contrast(): string;
2140
+ luminance(): number;
2141
+ contrastRatio(other: string | ColorInstance): number;
2142
+ meetsContrast(other: string | ColorInstance, minimum?: number): boolean;
1955
2143
  }
1956
2144
 
1957
- export const color: (value: string) => ColorInstance;
2145
+ export const color: (value: string | ColorInstance) => ColorInstance;
1958
2146
  export function rgba(r: number, g: number, b: number, a?: number): string;
1959
2147
  export function stackPosition(widget: Widget, props: { top?: any; right?: any; bottom?: any; left?: any }): Widget;
1960
2148
  export function flex(grow: number | { grow?: number; shrink?: number; basis?: string }, shrink?: number, basis?: string): string;
@@ -2063,5 +2251,5 @@ declare module 'flet-box' {
2063
2251
  // =========================================================================
2064
2252
  export const highlightColors: Record<string, string>;
2065
2253
  export function tokenize(code: string): Array<{ type: string; value: string }>;
2066
- export function generateHighlightedHtml(code: string): string;
2254
+ export function generateHighlightedHtml(code: string, palette?: Record<string, string>): string;
2067
2255
  }
package/src/index.js CHANGED
@@ -87,6 +87,7 @@ export {
87
87
  export {
88
88
  colors,
89
89
  setTheme,
90
+ registerPalette,
90
91
  getTheme,
91
92
  toggleTheme,
92
93
  subscribeTheme,
@@ -175,6 +176,7 @@ export {
175
176
  TreeView,
176
177
  Chart,
177
178
  QRCode,
179
+ Maps,
178
180
  DraggBox,
179
181
  DroppBox,
180
182
  DataTable,
@@ -59,6 +59,9 @@ import { goBack, goTo } from "./Router.js";
59
59
  * - `setBackgroundColor(color: string)` — swap the background color or gradient.
60
60
  * - `show()` / `hide()` — toggle bar visibility.
61
61
  */
62
+ import { toREM } from "../utils/units.js";
63
+ import { SHADOW_LEVELS } from "../tools/shadow.js";
64
+
62
65
  export const AppBar = (props) => {
63
66
  const {
64
67
  title,
@@ -119,18 +122,10 @@ export const AppBar = (props) => {
119
122
  const finalPaddingRight = paddingRight || padding;
120
123
 
121
124
  const finalBorderRadius =
122
- typeof borderRadius === "number" ? `${borderRadius}px` : borderRadius;
125
+ toREM(borderRadius);
123
126
 
124
- // Material Design elevation → box-shadow lookup table.
125
- const shadows = {
126
- 0: "none",
127
- 1: "0 1px 3px rgba(0,0,0,0.12)",
128
- 2: "0 3px 6px rgba(0,0,0,0.16)",
129
- 3: "0 6px 12px rgba(0,0,0,0.2)",
130
- 4: "0 10px 20px rgba(0,0,0,0.25)",
131
- 5: "0 15px 30px rgba(0,0,0,0.3)",
132
- };
133
- const boxShadow = shadow === true ? shadows[elevation] : shadow || "none";
127
+ // The framework's depth scale, not a local copy of it (src/tools/shadow.js).
128
+ const boxShadow = shadow === true ? SHADOW_LEVELS[elevation] : shadow || "none";
134
129
 
135
130
  // ========== LEADING SECTION ==========
136
131
  // Auto-create a back button if requested and no custom leading was supplied.
@@ -173,7 +168,7 @@ export const AppBar = (props) => {
173
168
  marginBottom: finalMarginBottom,
174
169
  marginLeft: finalMarginLeft,
175
170
  marginRight: finalMarginRight,
176
- paddingTop: `calc(${finalPaddingTop}px + env(safe-area-inset-top, 0px))`,
171
+ paddingTop: `calc(${toREM(finalPaddingTop)} + env(safe-area-inset-top, 0px))`,
177
172
  paddingBottom: finalPaddingBottom,
178
173
  paddingLeft: finalPaddingLeft,
179
174
  paddingRight: finalPaddingRight,
@@ -189,15 +184,20 @@ export const AppBar = (props) => {
189
184
 
190
185
  // ========== LEFT SECTION ==========
191
186
  // Fixed minimum width ensures the center title stays visually centered
192
- // even when no leading widget is present.
187
+ // even when no leading widget is present. That balance is only meaningful
188
+ // for a centered title: with `centerTitle: false` the title is flush-left,
189
+ // so reserving the empty 48px zone would push it past the page's content
190
+ // edge. Same reasoning for the title zone's 8px gutter.
191
+ const hasLeading = finalLeading instanceof HTMLElement;
192
+ const balanceZones = centerTitle || hasLeading;
193
193
  const leftSection = Container({
194
194
  display: "flex",
195
195
  alignItems: "center",
196
196
  bgColor: "transparent",
197
197
  justifyContent: "flex-start",
198
- minWidth: 48,
198
+ minWidth: balanceZones ? 48 : 0,
199
199
  flexShrink: 0,
200
- child: finalLeading instanceof HTMLElement ? finalLeading : null,
200
+ child: hasLeading ? finalLeading : null,
201
201
  });
202
202
  appBar.appendChild(leftSection);
203
203
 
@@ -224,9 +224,15 @@ export const AppBar = (props) => {
224
224
  // pushes the actions past the viewport edge on narrow screens.
225
225
  flex: 1,
226
226
  display: "flex",
227
+ // Container defaults to `flex-direction: column`, which silently rewrites
228
+ // the two axes: `alignItems: center` becomes horizontal centring and
229
+ // `justifyContent` (the flag that carries `centerTitle`) becomes vertical.
230
+ // A string title hid this because its Text fills the width; an
231
+ // auto-width widget title got centred no matter what `centerTitle` said.
232
+ flexDirection: "row",
227
233
  alignItems: "center",
228
234
  justifyContent: centerTitle ? "center" : "flex-start",
229
- padding: "0 8px",
235
+ padding: balanceZones ? "0 8px" : "0",
230
236
  bgColor: "transparent",
231
237
  overflow: "hidden",
232
238
  minWidth: 0,
@@ -53,6 +53,9 @@ import { getCurrentPath, subscribe, goTo } from "./Router.js";
53
53
  * - `getActiveIndex(): number` — returns the currently active tab index.
54
54
  * - `getCurrentRoute(): string|null` — returns the current router path (router mode only).
55
55
  */
56
+ import { toREM } from "../utils/units.js";
57
+ import { SHADOW_LEVELS } from "../tools/shadow.js";
58
+
56
59
  export const BottomNavigation = (props) => {
57
60
  const {
58
61
  items = [],
@@ -90,24 +93,16 @@ export const BottomNavigation = (props) => {
90
93
  const finalMarginLeft = marginLeft || margin;
91
94
  const finalMarginRight = marginRight || margin;
92
95
  const finalBorderRadius =
93
- typeof borderRadius === "number" ? `${borderRadius}px` : borderRadius;
94
- const finalPadding = typeof padding === "number" ? `${padding}px` : padding;
96
+ toREM(borderRadius);
97
+ const finalPadding = toREM(padding);
95
98
 
96
- // Material Design elevation → box-shadow lookup table.
97
- const shadows = {
98
- 0: "none",
99
- 1: "0 1px 3px rgba(0,0,0,0.12)",
100
- 2: "0 3px 6px rgba(0,0,0,0.16)",
101
- 3: "0 6px 12px rgba(0,0,0,0.2)",
102
- 4: "0 10px 20px rgba(0,0,0,0.25)",
103
- 5: "0 15px 30px rgba(0,0,0,0.3)",
104
- };
105
- const boxShadow = shadow === true ? shadows[elevation] : shadow || "none";
99
+ // The framework's depth scale, not a local copy of it (src/tools/shadow.js).
100
+ const boxShadow = shadow === true ? SHADOW_LEVELS[elevation] : shadow || "none";
106
101
 
107
102
  const container = WidgetFactory({
108
103
  tag: "nav",
109
104
  display: "flex",
110
- width: `calc(100% - ${finalMarginLeft}px - ${finalMarginRight}px)`,
105
+ width: `calc(100% - ${toREM(finalMarginLeft)} - ${toREM(finalMarginRight)})`,
111
106
  height: height,
112
107
  backgroundColor: backgroundColor,
113
108
  borderRadius: finalBorderRadius,
@@ -42,6 +42,8 @@ import { colors } from "../utils/themes.js";
42
42
  * @returns {HTMLElement} The sidebar container element, augmented with:
43
43
  * - `updateWidth(w: number|string)` — resize the sidebar programmatically.
44
44
  */
45
+ import { toREM } from "../utils/units.js";
46
+
45
47
  export const CollapsibleSideBar = ({
46
48
  children,
47
49
  expanded = true,
@@ -63,14 +65,33 @@ export const CollapsibleSideBar = ({
63
65
  );
64
66
  let expandedState = Boolean(stateValue.valueOf());
65
67
 
68
+ /**
69
+ * Paint the expanded/collapsed state onto the elements built below.
70
+ *
71
+ * The toggle has to do more than record the state: the width, the chevron
72
+ * glyph and the content's interactivity are all written once at build time,
73
+ * so without this the bar never moved.
74
+ *
75
+ * @param {boolean} isExpanded
76
+ * @returns {void}
77
+ */
78
+ const paint = (isExpanded) => {
79
+ container.style.width = toREM(isExpanded ? widthExpanded : widthCollapsed);
80
+ chevron.textContent = isExpanded ? "chevron_left" : "chevron_right";
81
+ contentWrapper.style.opacity = isExpanded ? "1" : "0";
82
+ contentWrapper.style.pointerEvents = isExpanded ? "auto" : "none";
83
+ };
84
+
66
85
  /**
67
86
  * Toggle the sidebar between expanded and collapsed states.
68
- * Updates the persisted state and fires the `onToggle` callback.
87
+ * Updates the persisted state, repaints the bar and fires the `onToggle`
88
+ * callback.
69
89
  */
70
90
  const toggle = () => {
71
91
  const newState = !expandedState;
72
92
  expandedState = newState;
73
93
  setIsExpanded(newState);
94
+ paint(newState);
74
95
  if (onToggle) onToggle(newState);
75
96
  };
76
97
 
@@ -81,7 +102,7 @@ export const CollapsibleSideBar = ({
81
102
  tag: "div",
82
103
  id,
83
104
  width:
84
- typeof currentWidth === "number" ? `${currentWidth}px` : currentWidth,
105
+ toREM(currentWidth),
85
106
  height: "100%",
86
107
  display: "flex",
87
108
  flexDirection: "column",
@@ -95,15 +116,16 @@ export const CollapsibleSideBar = ({
95
116
 
96
117
  // Toggle button — always visible so the user can expand/collapse regardless
97
118
  // of the current state. The icon flips direction to reflect the state.
119
+ const chevron = Icon({
120
+ name: expandedState ? "chevron_left" : "chevron_right",
121
+ size: iconSize,
122
+ color: colors.textSecondary,
123
+ });
98
124
  const toggleButton = Container({
99
125
  padding: 16,
100
126
  cursor: "pointer",
101
127
  onclick: toggle,
102
- child: Icon({
103
- name: expandedState ? "chevron_left" : "chevron_right",
104
- size: iconSize,
105
- color: colors.textSecondary,
106
- }),
128
+ child: chevron,
107
129
  });
108
130
  container.appendChild(toggleButton);
109
131
 
@@ -130,7 +152,7 @@ export const CollapsibleSideBar = ({
130
152
  */
131
153
  container.updateWidth = (newWidth) => {
132
154
  container.style.width =
133
- typeof newWidth === "number" ? `${newWidth}px` : newWidth;
155
+ toREM(newWidth);
134
156
  };
135
157
 
136
158
  return container;
@@ -58,6 +58,8 @@ let drawerInstance = null;
58
58
  * @param {DrawerProps} [props={}]
59
59
  * @returns {DrawerInstance}
60
60
  */
61
+ import { toREM } from "../utils/units.js";
62
+
61
63
  export const Drawer = (props = {}) => {
62
64
  const {
63
65
  header,
@@ -107,9 +109,9 @@ export const Drawer = (props = {}) => {
107
109
  // Sliding drawer panel.
108
110
  const drawerPanel = document.createElement("div");
109
111
  drawerPanel.style.position = "absolute";
110
- drawerPanel.style.top = `${margin}px`;
111
- drawerPanel.style.bottom = `${margin}px`;
112
- drawerPanel.style.height = `calc(100% - ${margin * 2}px)`;
112
+ drawerPanel.style.top = toREM(margin);
113
+ drawerPanel.style.bottom = toREM(margin);
114
+ drawerPanel.style.height = `calc(100% - ${toREM(margin * 2)})`;
113
115
  drawerPanel.style.display = "flex";
114
116
  drawerPanel.style.flexDirection = "column";
115
117
  drawerPanel.style.backgroundColor = bgColor;
@@ -118,22 +120,22 @@ export const Drawer = (props = {}) => {
118
120
  // Apply border radius only to the corners that face away from the screen edge.
119
121
  // A left drawer gets rounded right corners; a right drawer gets rounded left corners.
120
122
  if (position === "left") {
121
- drawerPanel.style.left = `${margin}px`;
123
+ drawerPanel.style.left = toREM(margin);
122
124
  drawerPanel.style.right = "auto";
123
- drawerPanel.style.borderTopRightRadius = `${borderRadius}px`;
124
- drawerPanel.style.borderBottomRightRadius = `${borderRadius}px`;
125
+ drawerPanel.style.borderTopRightRadius = toREM(borderRadius);
126
+ drawerPanel.style.borderBottomRightRadius = toREM(borderRadius);
125
127
  drawerPanel.style.borderTopLeftRadius = "0";
126
128
  drawerPanel.style.borderBottomLeftRadius = "0";
127
129
  } else {
128
- drawerPanel.style.right = `${margin}px`;
130
+ drawerPanel.style.right = toREM(margin);
129
131
  drawerPanel.style.left = "auto";
130
- drawerPanel.style.borderTopLeftRadius = `${borderRadius}px`;
131
- drawerPanel.style.borderBottomLeftRadius = `${borderRadius}px`;
132
+ drawerPanel.style.borderTopLeftRadius = toREM(borderRadius);
133
+ drawerPanel.style.borderBottomLeftRadius = toREM(borderRadius);
132
134
  drawerPanel.style.borderTopRightRadius = "0";
133
135
  drawerPanel.style.borderBottomRightRadius = "0";
134
136
  }
135
137
 
136
- drawerPanel.style.width = typeof width === "number" ? `${width}px` : width;
138
+ drawerPanel.style.width = toREM(width);
137
139
  // Start the panel off-screen so the entrance animation slides it in.
138
140
  drawerPanel.style.transform =
139
141
  position === "left" ? "translateX(-100%)" : "translateX(100%)";
@@ -160,8 +162,8 @@ export const Drawer = (props = {}) => {
160
162
  header &&
161
163
  Container({
162
164
  flexShrink: 0,
163
- borderTopLeftRadius: position === "right" ? `${borderRadius}px` : "0",
164
- borderTopRightRadius: position === "left" ? `${borderRadius}px` : "0",
165
+ borderTopLeftRadius: position === "right" ? toREM(borderRadius) : "0",
166
+ borderTopRightRadius: position === "left" ? toREM(borderRadius) : "0",
165
167
  overflow: "hidden",
166
168
  child: header,
167
169
  }),
@@ -173,16 +175,24 @@ export const Drawer = (props = {}) => {
173
175
  paddingBottom: 8,
174
176
  overflow: "auto",
175
177
  backgroundColor: bgColor,
176
- children: body,
178
+ // This body is a scroll container inside a fixed-height flex column,
179
+ // and every child defaults to `flex-shrink: 1`. Left alone, a short
180
+ // drawer squeezed its rows down to their own padding (a DrawerItem
181
+ // collapsed to ~24px) instead of overflowing. Pinning the children
182
+ // keeps them at content height so the body scrolls as intended.
183
+ children: body.map((child) => {
184
+ if (child && child.style) child.style.flexShrink = "0";
185
+ return child;
186
+ }),
177
187
  }),
178
188
 
179
189
  footer &&
180
190
  Container({
181
191
  flexShrink: 0,
182
192
  borderBottomLeftRadius:
183
- position === "right" ? `${borderRadius}px` : "0",
193
+ position === "right" ? toREM(borderRadius) : "0",
184
194
  borderBottomRightRadius:
185
- position === "left" ? `${borderRadius}px` : "0",
195
+ position === "left" ? toREM(borderRadius) : "0",
186
196
  overflow: "hidden",
187
197
  child: footer,
188
198
  }),
@@ -143,6 +143,8 @@ export const DrawerItem = (props) => {
143
143
  * Order of operations: mark selected → fire callbacks → navigate → close drawer.
144
144
  */
145
145
  const handleClick = () => {
146
+ // `onSelect` marks the transition into the selected state — it is what an
147
+ // app counts to open a section once. `onPress` fires on every tap.
146
148
  if (!isSelected) {
147
149
  isSelected = true;
148
150
  updateUI();
@@ -150,8 +152,8 @@ export const DrawerItem = (props) => {
150
152
  selectionEvent.dispatchEvent(
151
153
  new CustomEvent("drawer-item-selected", { detail: { id: label } }),
152
154
  );
155
+ if (onSelect) onSelect();
153
156
  }
154
- if (onSelect) onSelect();
155
157
  if (onPress) onPress();
156
158
  if (route) goTo(route);
157
159
  if (closeOnPress) closeDrawer();