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
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// src/tools/fonts.js
|
|
2
|
+
// Webfont loading, so a `fontFamily` prop has something to name.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `fontFamily` is a CSS family name; if the bytes of that face are not on the
|
|
6
|
+
* machine, the browser silently paints its fallback and the app ships a typeface
|
|
7
|
+
* nobody chose. This module is the missing half: ask for the family, get a
|
|
8
|
+
* promise that settles when the request is done, and set the prop from it.
|
|
9
|
+
*
|
|
10
|
+
* One thing this cannot promise: a webfont is a network request. A self-hosted
|
|
11
|
+
* same-origin stylesheet survives an offline launch; a provider URL does not.
|
|
12
|
+
* `loadFont` therefore never fails a screen — it resolves `false` when the face
|
|
13
|
+
* could not be had, and the app keeps its fallback.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** @type {Map<string, Promise<boolean>>} One request per family, however many callers ask. */
|
|
17
|
+
const loading = new Map();
|
|
18
|
+
|
|
19
|
+
const toCssTime = (weights) => weights.map((weight) => Number(weight) || 0).join(";");
|
|
20
|
+
|
|
21
|
+
const googleUrl = (family, weights, display) =>
|
|
22
|
+
`https://fonts.googleapis.com/css2?family=${encodeURIComponent(family)}${
|
|
23
|
+
weights.length ? `:wght@${toCssTime(weights)}` : ""
|
|
24
|
+
}&display=${display}`;
|
|
25
|
+
|
|
26
|
+
const appendLink = (rel, href, attributes = {}) => {
|
|
27
|
+
const link = document.createElement("link");
|
|
28
|
+
link.rel = rel;
|
|
29
|
+
if (href) link.href = href;
|
|
30
|
+
for (const [name, value] of Object.entries(attributes)) link.setAttribute(name, value);
|
|
31
|
+
document.head.appendChild(link);
|
|
32
|
+
return link;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// A cross-origin stylesheet wants its origin warm before the request, or the face
|
|
36
|
+
// arrives a round trip late. Once per origin: every family on the same host would
|
|
37
|
+
// otherwise add its own pair.
|
|
38
|
+
const preconnect = (href) => {
|
|
39
|
+
if (document.head.querySelector(`link[rel="preconnect"][href="${href}"]`)) return;
|
|
40
|
+
appendLink("preconnect", href, { crossorigin: "anonymous" });
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Load a webfont family.
|
|
45
|
+
*
|
|
46
|
+
* @param {string} family - The CSS family name, e.g. `"Inter"`.
|
|
47
|
+
* @param {object} [options]
|
|
48
|
+
* @param {number[]} [options.weights] - Weights to fetch (`[400, 600]`). A Google
|
|
49
|
+
* family with no weight list arrives at 400 only, which is a face that cannot
|
|
50
|
+
* carry a heading.
|
|
51
|
+
* @param {string} [options.url] - A stylesheet to load instead of Google's: a
|
|
52
|
+
* self-hosted `@font-face` file, or any other provider.
|
|
53
|
+
* @param {"auto" | "block" | "swap" | "fallback" | "optional"} [options.display="swap"]
|
|
54
|
+
* CSS `font-display`, Google URLs only. `swap` paints the fallback at once and
|
|
55
|
+
* replaces it when the face lands; `optional` may skip it on a slow connection.
|
|
56
|
+
* @returns {Promise<boolean>} `true` when the stylesheet landed, `false` when it
|
|
57
|
+
* did not — and an immediate `false` where there is no document to write to, so
|
|
58
|
+
* importing this module in Node never throws.
|
|
59
|
+
*/
|
|
60
|
+
export const loadFont = (family, options = {}) => {
|
|
61
|
+
if (typeof document === "undefined" || !family) return Promise.resolve(false);
|
|
62
|
+
const key = String(family);
|
|
63
|
+
if (loading.has(key)) return loading.get(key);
|
|
64
|
+
|
|
65
|
+
const run = new Promise((resolve) => {
|
|
66
|
+
const { weights = [], url, display = "swap" } = options;
|
|
67
|
+
const href = url || googleUrl(key, weights, display);
|
|
68
|
+
if (!url) {
|
|
69
|
+
preconnect("https://fonts.googleapis.com");
|
|
70
|
+
preconnect("https://fonts.gstatic.com");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const link = appendLink("stylesheet", href, { "data-fb-font": key });
|
|
74
|
+
// The face itself still has to paint; `document.fonts.ready` is the point
|
|
75
|
+
// where the engine has finished everything it started, loaded or not.
|
|
76
|
+
const settle = () => {
|
|
77
|
+
if (typeof document.fonts?.ready?.then !== "function") return resolve(true);
|
|
78
|
+
document.fonts.ready.then(() => resolve(true), () => resolve(false));
|
|
79
|
+
};
|
|
80
|
+
link.addEventListener("load", settle);
|
|
81
|
+
link.addEventListener("error", () => resolve(false));
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
loading.set(key, run);
|
|
85
|
+
return run;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Wait for every face the document has already started, without asking for one.
|
|
90
|
+
* Use it before a canvas paint or an export, where text that reflows after the
|
|
91
|
+
* drawing is a visible bug.
|
|
92
|
+
*
|
|
93
|
+
* @returns {Promise<boolean>} `true` when the font engine settled, `false` where
|
|
94
|
+
* there is none.
|
|
95
|
+
*/
|
|
96
|
+
export const fontsReady = () => {
|
|
97
|
+
if (typeof document === "undefined" || !document.fonts) return Promise.resolve(false);
|
|
98
|
+
return document.fonts.ready.then(() => true, () => false);
|
|
99
|
+
};
|
package/src/tools/index.js
CHANGED
|
@@ -4,6 +4,16 @@ export { shuffle, reverse, sort, unique, chunk } from "./array.js";
|
|
|
4
4
|
export { border } from "./border.js";
|
|
5
5
|
export { clipboard } from "./clipboard.js";
|
|
6
6
|
export { color } from "./color.js";
|
|
7
|
+
export {
|
|
8
|
+
BREAKPOINTS,
|
|
9
|
+
BREAKPOINT_NAMES,
|
|
10
|
+
breakpointForWidth,
|
|
11
|
+
currentBreakpoint,
|
|
12
|
+
atLeast,
|
|
13
|
+
pick,
|
|
14
|
+
onBreakpointChange,
|
|
15
|
+
} from "./breakpoints.js";
|
|
16
|
+
export { loadFont, fontsReady } from "./fonts.js";
|
|
7
17
|
export { createList } from "./createList.js";
|
|
8
18
|
export { delay, withMinDelay, retry } from "./delay.js";
|
|
9
19
|
export { device } from "./device.js";
|
|
@@ -36,8 +46,71 @@ export {
|
|
|
36
46
|
upperCase,
|
|
37
47
|
reverseString,
|
|
38
48
|
truncate,
|
|
49
|
+
startsWith,
|
|
50
|
+
endsWith,
|
|
51
|
+
includes,
|
|
52
|
+
split,
|
|
53
|
+
join,
|
|
54
|
+
trim,
|
|
39
55
|
} from "./string.js";
|
|
40
|
-
export {
|
|
56
|
+
export {
|
|
57
|
+
// units
|
|
58
|
+
seconds,
|
|
59
|
+
minutes,
|
|
60
|
+
hours,
|
|
61
|
+
days,
|
|
62
|
+
weeks,
|
|
63
|
+
months,
|
|
64
|
+
years,
|
|
65
|
+
// current
|
|
66
|
+
now,
|
|
67
|
+
today,
|
|
68
|
+
yesterday,
|
|
69
|
+
tomorrow,
|
|
70
|
+
// info
|
|
71
|
+
dayOfWeek,
|
|
72
|
+
monthName,
|
|
73
|
+
isToday,
|
|
74
|
+
isYesterday,
|
|
75
|
+
isTomorrow,
|
|
76
|
+
isWeekend,
|
|
77
|
+
isLeapYear,
|
|
78
|
+
// format
|
|
79
|
+
formatDate,
|
|
80
|
+
formatDateLong,
|
|
81
|
+
formatDateShort,
|
|
82
|
+
formatTime,
|
|
83
|
+
formatTimeAmPm,
|
|
84
|
+
// parse & diff
|
|
85
|
+
parseDate,
|
|
86
|
+
dateDiff,
|
|
87
|
+
// manipulate
|
|
88
|
+
addDays,
|
|
89
|
+
addHours,
|
|
90
|
+
addMinutes,
|
|
91
|
+
addSeconds,
|
|
92
|
+
subtractDays,
|
|
93
|
+
subtractHours,
|
|
94
|
+
subtractMinutes,
|
|
95
|
+
subtractSeconds,
|
|
96
|
+
// relative
|
|
97
|
+
timeAgo,
|
|
98
|
+
relativeTime,
|
|
99
|
+
timeAgoLocale,
|
|
100
|
+
// wait
|
|
101
|
+
waitFor,
|
|
102
|
+
wait,
|
|
103
|
+
sleep,
|
|
104
|
+
waitUntil,
|
|
105
|
+
waitForElement,
|
|
106
|
+
// timer & interval
|
|
107
|
+
timer,
|
|
108
|
+
every,
|
|
109
|
+
everySecond,
|
|
110
|
+
everyMinute,
|
|
111
|
+
everyHour,
|
|
112
|
+
everyDay,
|
|
113
|
+
} from "./time.js";
|
|
41
114
|
export { transform } from "./transform.js";
|
|
42
115
|
export { transition } from "./transition.js";
|
|
43
116
|
export { useState, useWatchState } from "./useState.js";
|
package/src/tools/margin.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// tools/margin.js
|
|
2
|
+
import { boxEdges } from "../helpers/boxEdges.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Generates a CSS `margin` shorthand string from a number, a plain CSS string,
|
|
@@ -14,6 +15,9 @@
|
|
|
14
15
|
* - `vertical` — applies to `top` and `bottom`.
|
|
15
16
|
* - `top`, `right`, `bottom`, `left` — individual side overrides.
|
|
16
17
|
*
|
|
18
|
+
* The grammar is shared with `padding()` and lives in `helpers/boxEdges.js`, so
|
|
19
|
+
* the two cannot disagree about how a side is resolved.
|
|
20
|
+
*
|
|
17
21
|
* @param {number|string|Object} value
|
|
18
22
|
* A number (converted to `px`), a raw CSS string, or an options object.
|
|
19
23
|
* @returns {string} CSS margin value.
|
|
@@ -26,68 +30,6 @@
|
|
|
26
30
|
* margin({ left: 20, right: 20 }) // "0px 20px"
|
|
27
31
|
* margin({ top: 10, bottom: 10 }) // "10px 0px"
|
|
28
32
|
*/
|
|
29
|
-
export const margin =
|
|
30
|
-
// A number or raw CSS string applies uniformly to all sides
|
|
31
|
-
if (typeof value === "number" || typeof value === "string") {
|
|
32
|
-
const px = typeof value === "number" ? `${value}px` : value;
|
|
33
|
-
return px;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Object form — resolve each side independently
|
|
37
|
-
if (typeof value === "object") {
|
|
38
|
-
const top =
|
|
39
|
-
value.top !== undefined
|
|
40
|
-
? value.top
|
|
41
|
-
: value.vertical !== undefined
|
|
42
|
-
? value.vertical
|
|
43
|
-
: value.all !== undefined
|
|
44
|
-
? value.all
|
|
45
|
-
: 0;
|
|
46
|
-
const right =
|
|
47
|
-
value.right !== undefined
|
|
48
|
-
? value.right
|
|
49
|
-
: value.horizontal !== undefined
|
|
50
|
-
? value.horizontal
|
|
51
|
-
: value.all !== undefined
|
|
52
|
-
? value.all
|
|
53
|
-
: 0;
|
|
54
|
-
const bottom =
|
|
55
|
-
value.bottom !== undefined
|
|
56
|
-
? value.bottom
|
|
57
|
-
: value.vertical !== undefined
|
|
58
|
-
? value.vertical
|
|
59
|
-
: value.all !== undefined
|
|
60
|
-
? value.all
|
|
61
|
-
: 0;
|
|
62
|
-
const left =
|
|
63
|
-
value.left !== undefined
|
|
64
|
-
? value.left
|
|
65
|
-
: value.horizontal !== undefined
|
|
66
|
-
? value.horizontal
|
|
67
|
-
: value.all !== undefined
|
|
68
|
-
? value.all
|
|
69
|
-
: 0;
|
|
70
|
-
|
|
71
|
-
const topPx = typeof top === "number" ? `${top}px` : top;
|
|
72
|
-
const rightPx = typeof right === "number" ? `${right}px` : right;
|
|
73
|
-
const bottomPx = typeof bottom === "number" ? `${bottom}px` : bottom;
|
|
74
|
-
const leftPx = typeof left === "number" ? `${left}px` : left;
|
|
75
|
-
|
|
76
|
-
// All sides equal — return a single value
|
|
77
|
-
if (topPx === rightPx && rightPx === bottomPx && bottomPx === leftPx) {
|
|
78
|
-
return topPx;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Only horizontal and vertical differ — use two-value shorthand
|
|
82
|
-
if (topPx === bottomPx && rightPx === leftPx) {
|
|
83
|
-
return `${topPx} ${rightPx}`;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Full four-value form
|
|
87
|
-
return `${topPx} ${rightPx} ${bottomPx} ${leftPx}`;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return "0px";
|
|
91
|
-
};
|
|
33
|
+
export const margin = boxEdges;
|
|
92
34
|
|
|
93
35
|
export default margin;
|
package/src/tools/padding.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// tools/padding.js
|
|
2
|
+
import { boxEdges } from "../helpers/boxEdges.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Generates a CSS `padding` shorthand string from a number, a plain CSS string,
|
|
@@ -14,6 +15,9 @@
|
|
|
14
15
|
* - `vertical` — applies to `top` and `bottom`.
|
|
15
16
|
* - `top`, `right`, `bottom`, `left` — individual side overrides.
|
|
16
17
|
*
|
|
18
|
+
* The grammar is shared with `margin()` and lives in `helpers/boxEdges.js`, so
|
|
19
|
+
* the two cannot disagree about how a side is resolved.
|
|
20
|
+
*
|
|
17
21
|
* @param {number|string|Object} value
|
|
18
22
|
* A number (converted to `px`), a raw CSS string, or an options object.
|
|
19
23
|
* @returns {string} CSS padding value.
|
|
@@ -26,68 +30,6 @@
|
|
|
26
30
|
* padding({ left: 20, right: 20 }) // "0px 20px"
|
|
27
31
|
* padding({ top: 10, bottom: 10 }) // "10px 0px"
|
|
28
32
|
*/
|
|
29
|
-
export const padding =
|
|
30
|
-
// A number or raw CSS string applies uniformly to all sides
|
|
31
|
-
if (typeof value === "number" || typeof value === "string") {
|
|
32
|
-
const px = typeof value === "number" ? `${value}px` : value;
|
|
33
|
-
return px;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Object form — resolve each side independently
|
|
37
|
-
if (typeof value === "object") {
|
|
38
|
-
const top =
|
|
39
|
-
value.top !== undefined
|
|
40
|
-
? value.top
|
|
41
|
-
: value.vertical !== undefined
|
|
42
|
-
? value.vertical
|
|
43
|
-
: value.all !== undefined
|
|
44
|
-
? value.all
|
|
45
|
-
: 0;
|
|
46
|
-
const right =
|
|
47
|
-
value.right !== undefined
|
|
48
|
-
? value.right
|
|
49
|
-
: value.horizontal !== undefined
|
|
50
|
-
? value.horizontal
|
|
51
|
-
: value.all !== undefined
|
|
52
|
-
? value.all
|
|
53
|
-
: 0;
|
|
54
|
-
const bottom =
|
|
55
|
-
value.bottom !== undefined
|
|
56
|
-
? value.bottom
|
|
57
|
-
: value.vertical !== undefined
|
|
58
|
-
? value.vertical
|
|
59
|
-
: value.all !== undefined
|
|
60
|
-
? value.all
|
|
61
|
-
: 0;
|
|
62
|
-
const left =
|
|
63
|
-
value.left !== undefined
|
|
64
|
-
? value.left
|
|
65
|
-
: value.horizontal !== undefined
|
|
66
|
-
? value.horizontal
|
|
67
|
-
: value.all !== undefined
|
|
68
|
-
? value.all
|
|
69
|
-
: 0;
|
|
70
|
-
|
|
71
|
-
const topPx = typeof top === "number" ? `${top}px` : top;
|
|
72
|
-
const rightPx = typeof right === "number" ? `${right}px` : right;
|
|
73
|
-
const bottomPx = typeof bottom === "number" ? `${bottom}px` : bottom;
|
|
74
|
-
const leftPx = typeof left === "number" ? `${left}px` : left;
|
|
75
|
-
|
|
76
|
-
// All sides equal — return a single value
|
|
77
|
-
if (topPx === rightPx && rightPx === bottomPx && bottomPx === leftPx) {
|
|
78
|
-
return topPx;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Only horizontal and vertical differ — use two-value shorthand
|
|
82
|
-
if (topPx === bottomPx && rightPx === leftPx) {
|
|
83
|
-
return `${topPx} ${rightPx}`;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Full four-value form
|
|
87
|
-
return `${topPx} ${rightPx} ${bottomPx} ${leftPx}`;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return "0px";
|
|
91
|
-
};
|
|
33
|
+
export const padding = boxEdges;
|
|
92
34
|
|
|
93
35
|
export default padding;
|
package/src/tools/print.js
CHANGED
|
@@ -58,12 +58,4 @@ export const print = (message, type = "log", options = {}) => {
|
|
|
58
58
|
console[config.method](finalMessage);
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
-
// Shorthand aliases (uncomment to enable individual exports):
|
|
62
|
-
// export const log = (msg) => print(msg, "log");
|
|
63
|
-
// export const warn = (msg) => print(msg, "warn");
|
|
64
|
-
// export const error = (msg) => print(msg, "error");
|
|
65
|
-
// export const info = (msg) => print(msg, "info");
|
|
66
|
-
// export const success = (msg) => print(msg, "success");
|
|
67
|
-
// export const debug = (msg) => print(msg, "debug");
|
|
68
|
-
|
|
69
61
|
export default print;
|
package/src/tools/scale.js
CHANGED
|
@@ -28,8 +28,15 @@ export const PILL = 999;
|
|
|
28
28
|
/** The 8-based rhythm, with the two steps a real layout needs (18 for field text, 96 for hero gutters). */
|
|
29
29
|
export const SPACING_SCALE = new Set([0, 4, 8, 12, 16, 18, 24, 32, 40, 48, 64, 96]);
|
|
30
30
|
|
|
31
|
-
/**
|
|
32
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Corner radii. `999` is a pill, `0` a flush edge — both are decisions, not omissions.
|
|
33
|
+
*
|
|
34
|
+
* The scale reaches 24 on purpose. The nesting rule (`outer = inner + padding`) is
|
|
35
|
+
* arithmetic, not taste: a 16-radius tile inside 8 of padding *needs* a 24 card, so a
|
|
36
|
+
* scale that stops at 16 makes the correct radius illegal and pushes the screen to a
|
|
37
|
+
* pill it did not ask for.
|
|
38
|
+
*/
|
|
39
|
+
export const RADIUS_SCALE = new Set([0, 4, 8, 12, 16, 20, 24, PILL]);
|
|
33
40
|
|
|
34
41
|
/** Type sizes. Two steps per jump: a 15 next to a 16 reads as a mistake, not as hierarchy. */
|
|
35
42
|
export const TYPE_SCALE = new Set([12, 14, 16, 18, 24, 32, 40, 48]);
|
package/src/tools/time.js
CHANGED
|
@@ -6,6 +6,35 @@
|
|
|
6
6
|
* ============================================================
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
// ========== INPUT ==========
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Turns any accepted date input into a `Date`, reading a bare calendar date as a
|
|
13
|
+
* calendar date.
|
|
14
|
+
*
|
|
15
|
+
* `new Date("2024-01-15")` is not January the fifteenth: the spec parses a
|
|
16
|
+
* date-only string as UTC midnight, and every getter then reads it back in the
|
|
17
|
+
* local zone. West of Greenwich that is the fourteenth, which is how
|
|
18
|
+
* `isToday(today())` came back `false` and `dayOfWeek("2024-01-15")` said
|
|
19
|
+
* "Sunday" — the module disagreeing with itself, on a date the module produced.
|
|
20
|
+
*
|
|
21
|
+
* A value that carries a time, an offset or a `Z` is a timestamp, not a day, and
|
|
22
|
+
* `new Date` already places it; only `YYYY-MM-DD` needs the local reading.
|
|
23
|
+
*
|
|
24
|
+
* @param {Date|number|string} date - Any value a time helper accepts.
|
|
25
|
+
* @returns {Date}
|
|
26
|
+
*/
|
|
27
|
+
const toDate = (date) => {
|
|
28
|
+
if (typeof date === "string") {
|
|
29
|
+
const calendarDay = /^(\d{4})-(\d{2})-(\d{2})$/.exec(date);
|
|
30
|
+
if (calendarDay) {
|
|
31
|
+
const [, year, month, day] = calendarDay;
|
|
32
|
+
return new Date(Number(year), Number(month) - 1, Number(day));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return new Date(date);
|
|
36
|
+
};
|
|
37
|
+
|
|
9
38
|
// ========== TIME UNITS ==========
|
|
10
39
|
|
|
11
40
|
/**
|
|
@@ -100,7 +129,7 @@ export const dayOfWeek = (date) => {
|
|
|
100
129
|
"Friday",
|
|
101
130
|
"Saturday",
|
|
102
131
|
];
|
|
103
|
-
return days[
|
|
132
|
+
return days[toDate(date).getDay()];
|
|
104
133
|
};
|
|
105
134
|
|
|
106
135
|
/**
|
|
@@ -123,7 +152,7 @@ export const monthName = (date) => {
|
|
|
123
152
|
"November",
|
|
124
153
|
"December",
|
|
125
154
|
];
|
|
126
|
-
return months[
|
|
155
|
+
return months[toDate(date).getMonth()];
|
|
127
156
|
};
|
|
128
157
|
|
|
129
158
|
/**
|
|
@@ -152,7 +181,7 @@ export const isTomorrow = (date) => formatDate(date) === tomorrow();
|
|
|
152
181
|
* @param {Date|number|string} date - Date to check.
|
|
153
182
|
* @returns {boolean}
|
|
154
183
|
*/
|
|
155
|
-
export const isWeekend = (date) => [0, 6].includes(
|
|
184
|
+
export const isWeekend = (date) => [0, 6].includes(toDate(date).getDay());
|
|
156
185
|
|
|
157
186
|
/**
|
|
158
187
|
* Returns `true` if the given year is a leap year.
|
|
@@ -179,7 +208,7 @@ export const isLeapYear = (year) => {
|
|
|
179
208
|
* formatDate(new Date(), "YYYY-MM-DD HH:mm:ss") // "2024-01-15 09:30:00"
|
|
180
209
|
*/
|
|
181
210
|
export const formatDate = (date, format = "YYYY-MM-DD") => {
|
|
182
|
-
const d =
|
|
211
|
+
const d = toDate(date);
|
|
183
212
|
const year = d.getFullYear();
|
|
184
213
|
const month = String(d.getMonth() + 1).padStart(2, "0");
|
|
185
214
|
const day = String(d.getDate()).padStart(2, "0");
|
|
@@ -204,7 +233,7 @@ export const formatDate = (date, format = "YYYY-MM-DD") => {
|
|
|
204
233
|
* @returns {string} Long date string, e.g. `"Monday, January 15, 2024"`.
|
|
205
234
|
*/
|
|
206
235
|
export const formatDateLong = (date, locale = "en-US") => {
|
|
207
|
-
return
|
|
236
|
+
return toDate(date).toLocaleDateString(locale, {
|
|
208
237
|
weekday: "long",
|
|
209
238
|
year: "numeric",
|
|
210
239
|
month: "long",
|
|
@@ -220,7 +249,7 @@ export const formatDateLong = (date, locale = "en-US") => {
|
|
|
220
249
|
* @returns {string} Short date string, e.g. `"Jan 15, 2024"`.
|
|
221
250
|
*/
|
|
222
251
|
export const formatDateShort = (date, locale = "en-US") => {
|
|
223
|
-
return
|
|
252
|
+
return toDate(date).toLocaleDateString(locale, {
|
|
224
253
|
year: "numeric",
|
|
225
254
|
month: "short",
|
|
226
255
|
day: "numeric",
|
|
@@ -240,7 +269,7 @@ export const formatTime = (date) => formatDate(date, "HH:mm:ss");
|
|
|
240
269
|
* @returns {string} Time string, e.g. `"02:05:30 PM"`.
|
|
241
270
|
*/
|
|
242
271
|
export const formatTimeAmPm = (date) => {
|
|
243
|
-
const d =
|
|
272
|
+
const d = toDate(date);
|
|
244
273
|
let hours = d.getHours();
|
|
245
274
|
const ampm = hours >= 12 ? "PM" : "AM";
|
|
246
275
|
hours = hours % 12 || 12;
|
|
@@ -304,7 +333,7 @@ export const parseDate = (str, format = "YYYY-MM-DD") => {
|
|
|
304
333
|
* dateDiff("2024-01-01", "2024-01-15", "days") // 14
|
|
305
334
|
*/
|
|
306
335
|
export const dateDiff = (date1, date2, unit = "days") => {
|
|
307
|
-
const diff = Math.abs(
|
|
336
|
+
const diff = Math.abs(toDate(date1) - toDate(date2));
|
|
308
337
|
const units = {
|
|
309
338
|
milliseconds: 1,
|
|
310
339
|
seconds: 1000,
|
|
@@ -322,12 +351,24 @@ export const dateDiff = (date1, date2, unit = "days") => {
|
|
|
322
351
|
|
|
323
352
|
/**
|
|
324
353
|
* Adds `n` days to a date and returns a new `Date`.
|
|
354
|
+
*
|
|
355
|
+
* Whole days are counted on the calendar, not in milliseconds: a 24h step lands
|
|
356
|
+
* an hour off the wall clock either side of a DST change, so
|
|
357
|
+
* `addDays("2024-10-26", 7)` in Madrid would print November 1. A fractional `n`
|
|
358
|
+
* is elapsed time and keeps the millisecond walk.
|
|
359
|
+
*
|
|
325
360
|
* @param {Date|number|string} date - Base date.
|
|
326
361
|
* @param {number} n - Number of days to add (negative to subtract).
|
|
327
362
|
* @returns {Date}
|
|
328
363
|
*/
|
|
329
|
-
export const addDays = (date, n) =>
|
|
330
|
-
|
|
364
|
+
export const addDays = (date, n) => {
|
|
365
|
+
const d = toDate(date);
|
|
366
|
+
if (Number.isInteger(n)) {
|
|
367
|
+
d.setDate(d.getDate() + n);
|
|
368
|
+
return d;
|
|
369
|
+
}
|
|
370
|
+
return new Date(d.getTime() + days(n));
|
|
371
|
+
};
|
|
331
372
|
|
|
332
373
|
/**
|
|
333
374
|
* Adds `n` hours to a date and returns a new `Date`.
|
|
@@ -336,7 +377,7 @@ export const addDays = (date, n) =>
|
|
|
336
377
|
* @returns {Date}
|
|
337
378
|
*/
|
|
338
379
|
export const addHours = (date, n) =>
|
|
339
|
-
new Date(
|
|
380
|
+
new Date(toDate(date).getTime() + hours(n));
|
|
340
381
|
|
|
341
382
|
/**
|
|
342
383
|
* Adds `n` minutes to a date and returns a new `Date`.
|
|
@@ -345,7 +386,7 @@ export const addHours = (date, n) =>
|
|
|
345
386
|
* @returns {Date}
|
|
346
387
|
*/
|
|
347
388
|
export const addMinutes = (date, n) =>
|
|
348
|
-
new Date(
|
|
389
|
+
new Date(toDate(date).getTime() + minutes(n));
|
|
349
390
|
|
|
350
391
|
/**
|
|
351
392
|
* Adds `n` seconds to a date and returns a new `Date`.
|
|
@@ -354,7 +395,7 @@ export const addMinutes = (date, n) =>
|
|
|
354
395
|
* @returns {Date}
|
|
355
396
|
*/
|
|
356
397
|
export const addSeconds = (date, n) =>
|
|
357
|
-
new Date(
|
|
398
|
+
new Date(toDate(date).getTime() + seconds(n));
|
|
358
399
|
|
|
359
400
|
/**
|
|
360
401
|
* Subtracts `n` days from a date and returns a new `Date`.
|
|
@@ -402,7 +443,7 @@ export const subtractSeconds = (date, n) => addSeconds(date, -n);
|
|
|
402
443
|
*/
|
|
403
444
|
export const timeAgo = (date) => {
|
|
404
445
|
const now = new Date();
|
|
405
|
-
const d =
|
|
446
|
+
const d = toDate(date);
|
|
406
447
|
const diff = Math.floor((now - d) / 1000);
|
|
407
448
|
|
|
408
449
|
const intervals = [
|
|
@@ -439,7 +480,7 @@ export const relativeTime = timeAgo;
|
|
|
439
480
|
* timeAgoLocale(Date.now() - 3600000, "es") // "hace 1 hora"
|
|
440
481
|
*/
|
|
441
482
|
export const timeAgoLocale = (date, locale = "en") => {
|
|
442
|
-
const d =
|
|
483
|
+
const d = toDate(date);
|
|
443
484
|
const now = new Date();
|
|
444
485
|
const diff = Math.floor((now - d) / 1000);
|
|
445
486
|
|
package/src/tools/useState.js
CHANGED
|
@@ -3,7 +3,6 @@ import { saveRam, getRam, subscribeRam } from "../services/RamStore.js";
|
|
|
3
3
|
import {
|
|
4
4
|
createReactiveValue,
|
|
5
5
|
isStructuralRead,
|
|
6
|
-
resetStructuralReads,
|
|
7
6
|
} from "../widget-factory/reactiveValue.js";
|
|
8
7
|
|
|
9
8
|
// Store callbacks for each state key
|
|
@@ -57,7 +56,7 @@ const ensureRamDispatcher = () => {
|
|
|
57
56
|
if (ramDispatcherReady) return;
|
|
58
57
|
ramDispatcherReady = true;
|
|
59
58
|
|
|
60
|
-
|
|
59
|
+
subscribeRam((changedKey, newValue, oldValue) => {
|
|
61
60
|
const callbacks = subscribers.get(changedKey);
|
|
62
61
|
// A key nobody ever passed to `useState` has no listeners: ignore it.
|
|
63
62
|
if (!callbacks) return;
|
|
@@ -83,11 +82,6 @@ const ensureRamDispatcher = () => {
|
|
|
83
82
|
// list): the surgical patch above cannot change what the component computes.
|
|
84
83
|
if (handled === 0 || isStructuralRead(changedKey)) triggerRender();
|
|
85
84
|
});
|
|
86
|
-
|
|
87
|
-
if (typeof window !== "undefined") {
|
|
88
|
-
if (!window._useStateCleanups) window._useStateCleanups = [];
|
|
89
|
-
window._useStateCleanups.push(unsubscribe);
|
|
90
|
-
}
|
|
91
85
|
};
|
|
92
86
|
|
|
93
87
|
/**
|
|
@@ -251,53 +245,6 @@ export const useWatchState = (key, callback) => {
|
|
|
251
245
|
};
|
|
252
246
|
};
|
|
253
247
|
|
|
254
|
-
/**
|
|
255
|
-
* Cleans up all state subscriptions, widget registrations, and pending render
|
|
256
|
-
* timers. Should be called during hot-reload to reset the module to a clean state.
|
|
257
|
-
*
|
|
258
|
-
* @returns {void}
|
|
259
|
-
*/
|
|
260
|
-
export const cleanupUseState = () => {
|
|
261
|
-
if (window._useStateCleanups) {
|
|
262
|
-
window._useStateCleanups.forEach((cleanup) => {
|
|
263
|
-
if (typeof cleanup === "function") cleanup();
|
|
264
|
-
});
|
|
265
|
-
window._useStateCleanups = [];
|
|
266
|
-
}
|
|
267
|
-
subscribers.clear();
|
|
268
|
-
widgetsByKey.clear();
|
|
269
|
-
resetStructuralReads();
|
|
270
|
-
// The dispatcher was unsubscribed above, so a later call must reinstall it.
|
|
271
|
-
ramDispatcherReady = false;
|
|
272
|
-
globalRender = null;
|
|
273
|
-
if (renderTimeout) clearTimeout(renderTimeout);
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Returns diagnostic statistics about the current state of the useState module.
|
|
278
|
-
* Useful for debugging subscription leaks or widget registration issues.
|
|
279
|
-
*
|
|
280
|
-
* @returns {{ stateKeys: number, boundWidgets: number, watchers: number, globalRender: boolean }}
|
|
281
|
-
*/
|
|
282
|
-
export const getUseStateStats = () => {
|
|
283
|
-
// `widgetsByKey` maps key -> widget -> prop names, so its size is the number of
|
|
284
|
-
// watched *keys*, not of widgets. Count one per widget.
|
|
285
|
-
let boundWidgets = 0;
|
|
286
|
-
let watchers = 0;
|
|
287
|
-
widgetsByKey.forEach((perWidget) => {
|
|
288
|
-
boundWidgets += perWidget.size;
|
|
289
|
-
});
|
|
290
|
-
subscribers.forEach((callbacks) => {
|
|
291
|
-
watchers += callbacks.size;
|
|
292
|
-
});
|
|
293
|
-
return {
|
|
294
|
-
stateKeys: subscribers.size,
|
|
295
|
-
boundWidgets,
|
|
296
|
-
watchers,
|
|
297
|
-
globalRender: !!globalRender,
|
|
298
|
-
};
|
|
299
|
-
};
|
|
300
|
-
|
|
301
248
|
// Re-export RamStore functions under convenience aliases
|
|
302
249
|
export {
|
|
303
250
|
saveRam as saveState,
|