flet-box 0.0.0-stage → 1.0.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/LICENSE +21 -0
- package/README.md +190 -2
- package/bin/cli.js +394 -0
- package/bin/commands/buildAndroid.js +200 -0
- package/bin/commands/buildIOS.js +237 -0
- package/bin/commands/create.js +406 -0
- package/bin/commands/createBundle.js +375 -0
- package/bin/commands/createComponent.js +115 -0
- package/bin/commands/createScreen.js +161 -0
- package/bin/commands/killServer.js +107 -0
- package/bin/commands/package.js +131 -0
- package/bin/commands/release.js +189 -0
- package/bin/commands/runBundle.js +62 -0
- package/bin/commands/runCloudflare.js +89 -0
- package/bin/commands/runNative.js +109 -0
- package/bin/commands/runServer.js +390 -0
- package/bin/ota/loader.js +361 -0
- package/bin/utils/bundleConfig.js +178 -0
- package/bin/utils/capacitor.js +137 -0
- package/bin/utils/colors.js +215 -0
- package/bin/utils/helpers.js +80 -0
- package/bin/utils/spinner.js +96 -0
- package/bin/utils/templates.js +1824 -0
- package/bin/utils/version.js +108 -0
- package/package.json +58 -4
- package/src/animations/AnimatedBox.js +607 -0
- package/src/animations/AnimatedText.js +105 -0
- package/src/animations/MatrixRain.js +220 -0
- package/src/animations/ParallaxBox.js +369 -0
- package/src/animations/animate.js +332 -0
- package/src/animations/index.js +25 -0
- package/src/app.js +41 -0
- package/src/assets/icon-1024-maskable.png +0 -0
- package/src/assets/icon-192-maskable.png +0 -0
- package/src/assets/icon-192.png +0 -0
- package/src/assets/icon-512-maskable.png +0 -0
- package/src/assets/icon-512.png +0 -0
- package/src/assets/logo.png +0 -0
- package/src/core/App.js +44 -0
- package/src/core/hmr-client.js +142 -0
- package/src/core/index.js +34 -0
- package/src/core/pwa.js +100 -0
- package/src/core/runApp.js +430 -0
- package/src/create-index.sh +237 -0
- package/src/fonts/MaterialIcons-Regular.woff2 +0 -0
- package/src/fonts/icons.css +23 -0
- package/src/index.d.ts +1930 -0
- package/src/index.js +288 -0
- package/src/navigations/AdaptiveScaffold.js +149 -0
- package/src/navigations/AppBar.js +331 -0
- package/src/navigations/BottomNavigation.js +298 -0
- package/src/navigations/CollapsibleSideBar.js +139 -0
- package/src/navigations/Drawer.js +333 -0
- package/src/navigations/DrawerItem.js +260 -0
- package/src/navigations/Router.js +475 -0
- package/src/navigations/Scaffold.js +537 -0
- package/src/navigations/Tabs.js +384 -0
- package/src/navigations/index.js +57 -0
- package/src/services/RamStore.js +213 -0
- package/src/services/Session.js +310 -0
- package/src/services/Storage.js +309 -0
- package/src/services/http.js +350 -0
- package/src/services/index.js +57 -0
- package/src/tools/animation.js +42 -0
- package/src/tools/array.js +95 -0
- package/src/tools/border.js +28 -0
- package/src/tools/clipboard.js +104 -0
- package/src/tools/color.js +329 -0
- package/src/tools/createList.js +215 -0
- package/src/tools/delay.js +55 -0
- package/src/tools/device.js +138 -0
- package/src/tools/dict.js +297 -0
- package/src/tools/dimensions.js +118 -0
- package/src/tools/filter.js +41 -0
- package/src/tools/flex.js +26 -0
- package/src/tools/gradient.js +34 -0
- package/src/tools/grid.js +57 -0
- package/src/tools/index.js +38 -0
- package/src/tools/mapList.js +107 -0
- package/src/tools/margin.js +93 -0
- package/src/tools/memo.js +60 -0
- package/src/tools/os.js +104 -0
- package/src/tools/padding.js +93 -0
- package/src/tools/print.js +69 -0
- package/src/tools/random.js +299 -0
- package/src/tools/ref.js +70 -0
- package/src/tools/rgba.js +4 -0
- package/src/tools/shadow.js +23 -0
- package/src/tools/string.js +85 -0
- package/src/tools/time.js +674 -0
- package/src/tools/transform.js +68 -0
- package/src/tools/transition.js +48 -0
- package/src/tools/useState.js +268 -0
- package/src/tools/uuid.js +42 -0
- package/src/utils/TextInputValidator.js +256 -0
- package/src/utils/composeUpdate.js +65 -0
- package/src/utils/getWidgetProps.js +35 -0
- package/src/utils/index.js +76 -0
- package/src/utils/markdownParser.js +368 -0
- package/src/utils/mediaTime.js +79 -0
- package/src/utils/navigation.js +263 -0
- package/src/utils/scriptInjector.js +73 -0
- package/src/utils/stackPosition.js +31 -0
- package/src/utils/stopWebRefresh.js +77 -0
- package/src/utils/styleInjector.js +38 -0
- package/src/utils/syntaxHighlight.js +339 -0
- package/src/utils/themes.js +347 -0
- package/src/utils/units.js +87 -0
- package/src/utils/visualEffects.js +169 -0
- package/src/widget-factory/addChildren.js +114 -0
- package/src/widget-factory/assignProps.js +256 -0
- package/src/widget-factory/createWidget.js +28 -0
- package/src/widget-factory/effects.js +132 -0
- package/src/widget-factory/index.js +7 -0
- package/src/widget-factory/lifecycle.js +210 -0
- package/src/widget-factory/makeParentable.js +89 -0
- package/src/widget-factory/pixTool.js +40 -0
- package/src/widget-factory/processProps.js +122 -0
- package/src/widget-factory/reactivity.js +98 -0
- package/src/widget-factory/remTool.js +34 -0
- package/src/widget-factory/stackPosition.js +87 -0
- package/src/widget-factory/stringTool.js +26 -0
- package/src/widget-factory/tools.js +249 -0
- package/src/widget-factory/translateProps.js +1049 -0
- package/src/widget-factory/warnings.js +284 -0
- package/src/widget-factory/widgetFactory.js +200 -0
- package/src/widgets/Accordion.js +562 -0
- package/src/widgets/AdMob.js +315 -0
- package/src/widgets/AdSense.js +268 -0
- package/src/widgets/AlertDialog.js +162 -0
- package/src/widgets/Audio.js +311 -0
- package/src/widgets/Avatar.js +210 -0
- package/src/widgets/Badge.js +189 -0
- package/src/widgets/BottomSheet.js +287 -0
- package/src/widgets/Button.js +247 -0
- package/src/widgets/Card.js +33 -0
- package/src/widgets/Carousel.js +301 -0
- package/src/widgets/Chart.js +632 -0
- package/src/widgets/Checkbox.js +106 -0
- package/src/widgets/Chip.js +153 -0
- package/src/widgets/CircularBar.js +352 -0
- package/src/widgets/CircularChart.js +549 -0
- package/src/widgets/CodeViewer.js +236 -0
- package/src/widgets/Column.js +29 -0
- package/src/widgets/Container.js +31 -0
- package/src/widgets/DataTable.js +257 -0
- package/src/widgets/Divider.js +93 -0
- package/src/widgets/DraggBox.js +115 -0
- package/src/widgets/Dropdown.js +505 -0
- package/src/widgets/DroppBox.js +180 -0
- package/src/widgets/FloatingActionButton.js +91 -0
- package/src/widgets/GridView.js +20 -0
- package/src/widgets/Icon.js +33 -0
- package/src/widgets/Image.js +15 -0
- package/src/widgets/Input.js +565 -0
- package/src/widgets/Inspector.js +254 -0
- package/src/widgets/InstallButton.js +110 -0
- package/src/widgets/ListTile.js +224 -0
- package/src/widgets/ListView.js +503 -0
- package/src/widgets/Markdown.js +1236 -0
- package/src/widgets/Modal.js +496 -0
- package/src/widgets/Pagination.js +195 -0
- package/src/widgets/ProgressBar.js +178 -0
- package/src/widgets/QRCode.js +467 -0
- package/src/widgets/Radio.js +132 -0
- package/src/widgets/Rating.js +261 -0
- package/src/widgets/Row.js +29 -0
- package/src/widgets/Skeleton.js +243 -0
- package/src/widgets/Slider.js +527 -0
- package/src/widgets/SnackBar.js +227 -0
- package/src/widgets/Stack.js +29 -0
- package/src/widgets/Stepper.js +296 -0
- package/src/widgets/Switch.js +102 -0
- package/src/widgets/Text.js +98 -0
- package/src/widgets/Tooltip.js +267 -0
- package/src/widgets/TreeView.js +370 -0
- package/src/widgets/Video.js +92 -0
- package/src/widgets/index.js +52 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Badge.js
|
|
3
|
+
* @description An overlay badge that renders a small indicator on top of a
|
|
4
|
+
* child element. Commonly used for notification counts, status dots, or
|
|
5
|
+
* "new item" markers on icons and avatars.
|
|
6
|
+
*
|
|
7
|
+
* Returns the child element directly when the badge should not be displayed
|
|
8
|
+
* (value is zero and `showZero` is false, or value is absent). Otherwise
|
|
9
|
+
* wraps the child in a Stack container with the badge positioned absolutely.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { WidgetFactory } from "../widget-factory/index.js";
|
|
13
|
+
import { Stack } from "./Stack.js";
|
|
14
|
+
import { Text } from "./Text.js";
|
|
15
|
+
import { colors, subscribeTheme } from "../utils/themes.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Creates a Badge widget that overlays a small count or status indicator on
|
|
19
|
+
* top of any child element.
|
|
20
|
+
*
|
|
21
|
+
* When `value` is falsy (and `showZero` is false), the child is returned
|
|
22
|
+
* unwrapped — no DOM overhead is added for hidden badges.
|
|
23
|
+
*
|
|
24
|
+
* @param {Object} props - Configuration for the badge.
|
|
25
|
+
* @param {number|string} props.value - The value to display. Numbers are formatted; strings are shown verbatim.
|
|
26
|
+
* @param {HTMLElement} props.child - The element the badge is anchored to.
|
|
27
|
+
* @param {string} [props.bgColor=colors.secondary] - Badge background color.
|
|
28
|
+
* @param {string} [props.color=colors.text] - Badge text/icon color.
|
|
29
|
+
* @param {number} [props.size=20] - Badge diameter in pixels; also controls font size proportionally.
|
|
30
|
+
* @param {'top-right'|'top-left'|'bottom-right'|'bottom-left'} [props.position='top-right'] - Where to anchor the badge relative to the child.
|
|
31
|
+
* @param {number} [props.borderWidth=2] - Width of the contrasting border around the badge in pixels.
|
|
32
|
+
* @param {string} [props.borderColor=colors.surface] - Color of the border around the badge (typically matches the parent background).
|
|
33
|
+
* @param {boolean} [props.showZero=false] - When true, the badge is visible even when `value` is 0.
|
|
34
|
+
* @param {number} [props.max=99] - When `value` exceeds this, the badge shows `"{max}+"` instead of the raw number.
|
|
35
|
+
* @param {number} [props.offset=0] - Additional pixel offset applied to the badge position for fine-tuning.
|
|
36
|
+
* @returns {HTMLElement} The wrapped Stack element, or the child element directly if the badge is hidden.
|
|
37
|
+
*/
|
|
38
|
+
export const Badge = (props) => {
|
|
39
|
+
const {
|
|
40
|
+
value,
|
|
41
|
+
child,
|
|
42
|
+
bgColor = colors.secondary,
|
|
43
|
+
color = colors.text,
|
|
44
|
+
size = 20,
|
|
45
|
+
position = "top-right",
|
|
46
|
+
borderWidth = 2,
|
|
47
|
+
borderColor = colors.surface,
|
|
48
|
+
showZero = false,
|
|
49
|
+
max = 99,
|
|
50
|
+
offset = 0,
|
|
51
|
+
...rest
|
|
52
|
+
} = props;
|
|
53
|
+
|
|
54
|
+
// Use the supplied bgColor; fall back to danger red if neither bgColor nor color is set
|
|
55
|
+
const finalBgColor = bgColor || color || colors.danger;
|
|
56
|
+
|
|
57
|
+
let unsubscribeTheme = null;
|
|
58
|
+
|
|
59
|
+
// Decide whether to show the badge at all
|
|
60
|
+
const hasValue = value !== undefined && value !== null && value !== "";
|
|
61
|
+
const showBadge =
|
|
62
|
+
hasValue &&
|
|
63
|
+
(showZero || (typeof value === "number" ? value > 0 : value !== ""));
|
|
64
|
+
|
|
65
|
+
// Return the child unwrapped to avoid unnecessary DOM nesting
|
|
66
|
+
if (!showBadge) {
|
|
67
|
+
return child || null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Truncate numbers that exceed the `max` cap
|
|
71
|
+
let displayValue = value;
|
|
72
|
+
if (typeof value === "number" && max && value > max) {
|
|
73
|
+
displayValue = `${max}+`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Pixel offsets for each anchor position — the transform handles the corner alignment
|
|
77
|
+
const positions = {
|
|
78
|
+
"top-right": {
|
|
79
|
+
top: -offset,
|
|
80
|
+
right: -offset,
|
|
81
|
+
transform: "translate(25%, -25%)",
|
|
82
|
+
},
|
|
83
|
+
"top-left": {
|
|
84
|
+
top: -offset,
|
|
85
|
+
left: -offset,
|
|
86
|
+
transform: "translate(-25%, -25%)",
|
|
87
|
+
},
|
|
88
|
+
"bottom-right": {
|
|
89
|
+
bottom: -offset,
|
|
90
|
+
right: -offset,
|
|
91
|
+
transform: "translate(25%, 25%)",
|
|
92
|
+
},
|
|
93
|
+
"bottom-left": {
|
|
94
|
+
bottom: -offset,
|
|
95
|
+
left: -offset,
|
|
96
|
+
transform: "translate(-25%, 25%)",
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const pos = positions[position] || positions["top-right"];
|
|
101
|
+
|
|
102
|
+
// The badge pill element itself
|
|
103
|
+
const badgeElement = WidgetFactory({
|
|
104
|
+
widgetName: "Badge",
|
|
105
|
+
position: "absolute",
|
|
106
|
+
top: pos.top,
|
|
107
|
+
right: pos.right,
|
|
108
|
+
bottom: pos.bottom,
|
|
109
|
+
left: pos.left,
|
|
110
|
+
transform: pos.transform,
|
|
111
|
+
backgroundColor: finalBgColor,
|
|
112
|
+
borderRadius: size,
|
|
113
|
+
minWidth: size,
|
|
114
|
+
height: size,
|
|
115
|
+
// Extra horizontal padding for two-digit numbers so they don't clip
|
|
116
|
+
padding: size > 20 ? `0 ${size / 3}px` : 0,
|
|
117
|
+
display: "flex",
|
|
118
|
+
alignItems: "center",
|
|
119
|
+
justifyContent: "center",
|
|
120
|
+
borderWidth: borderWidth,
|
|
121
|
+
borderColor: borderColor,
|
|
122
|
+
zIndex: 1,
|
|
123
|
+
boxSizing: "border-box",
|
|
124
|
+
boxShadow: "0 1px 3px rgba(0,0,0,0.2)",
|
|
125
|
+
child: Text({
|
|
126
|
+
text: String(displayValue),
|
|
127
|
+
size: size * 0.55,
|
|
128
|
+
color: color,
|
|
129
|
+
weight: "bold",
|
|
130
|
+
align: "center",
|
|
131
|
+
lineHeight: 1,
|
|
132
|
+
}),
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Subscribe to theme changes to keep the badge color in sync when no explicit
|
|
136
|
+
// bgColor/color was provided by the caller
|
|
137
|
+
if (!bgColor && !color) {
|
|
138
|
+
unsubscribeTheme = subscribeTheme(() => {
|
|
139
|
+
badgeElement.style.backgroundColor = colors.danger;
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const validChild = child instanceof HTMLElement ? child : null;
|
|
144
|
+
|
|
145
|
+
if (!validChild) {
|
|
146
|
+
console.warn("Badge: child must be an HTMLElement");
|
|
147
|
+
if (unsubscribeTheme) unsubscribeTheme();
|
|
148
|
+
return badgeElement;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Wrap the child and the badge in a Stack so the badge is positioned relative to the child
|
|
152
|
+
const badgeStack = Stack({
|
|
153
|
+
position: "relative",
|
|
154
|
+
display: "inline-block",
|
|
155
|
+
children: [validChild, badgeElement],
|
|
156
|
+
...rest,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Unsubscribe from theme updates when the component is removed from the DOM
|
|
160
|
+
badgeStack.onUnmount(() => {
|
|
161
|
+
if (unsubscribeTheme) unsubscribeTheme();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Updates the displayed badge value without re-rendering the component.
|
|
166
|
+
* Hides the badge automatically when the new value is zero (unless `showZero` is true).
|
|
167
|
+
*
|
|
168
|
+
* @param {number|string} newValue - The new badge value.
|
|
169
|
+
*/
|
|
170
|
+
badgeStack.updateValue = (newValue) => {
|
|
171
|
+
let newDisplayValue = newValue;
|
|
172
|
+
if (typeof newValue === "number" && max && newValue > max) {
|
|
173
|
+
newDisplayValue = `${max}+`;
|
|
174
|
+
}
|
|
175
|
+
const textElement = badgeElement.querySelector("span:not(.material-icons)");
|
|
176
|
+
if (textElement) {
|
|
177
|
+
textElement.textContent = String(newDisplayValue);
|
|
178
|
+
}
|
|
179
|
+
const shouldShow =
|
|
180
|
+
newValue &&
|
|
181
|
+
(showZero ||
|
|
182
|
+
(typeof newValue === "number" ? newValue > 0 : newValue !== ""));
|
|
183
|
+
badgeElement.style.display = shouldShow ? "flex" : "none";
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
return badgeStack;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
export default Badge;
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file BottomSheet.js
|
|
3
|
+
* @description Bottom sheet controller returned to callers (`open`/`close`/`toggle`/`destroy`).
|
|
4
|
+
*/
|
|
5
|
+
import { WidgetFactory } from "../widget-factory/index.js";
|
|
6
|
+
import { colors } from "../utils/themes.js";
|
|
7
|
+
import { Row } from "./Row.js";
|
|
8
|
+
import { Column } from "./Column.js";
|
|
9
|
+
import { Text } from "./Text.js";
|
|
10
|
+
import { Icon } from "./Icon.js";
|
|
11
|
+
import { Container } from "./Container.js";
|
|
12
|
+
|
|
13
|
+
export const BottomSheet = (props) => {
|
|
14
|
+
const {
|
|
15
|
+
content,
|
|
16
|
+
title,
|
|
17
|
+
actions = [],
|
|
18
|
+
height = "auto",
|
|
19
|
+
maxHeight = "80%",
|
|
20
|
+
showDragHandle = true,
|
|
21
|
+
closeOnOverlayClick = true,
|
|
22
|
+
closeOnDragDown = true,
|
|
23
|
+
showCloseButton = true,
|
|
24
|
+
|
|
25
|
+
// Colors
|
|
26
|
+
backgroundColor = colors.surface,
|
|
27
|
+
overlayColor = "rgba(0, 0, 0, 0.5)",
|
|
28
|
+
dragHandleColor = colors.border,
|
|
29
|
+
headerTextColor = colors.text,
|
|
30
|
+
headerBorderColor = colors.border,
|
|
31
|
+
actionBorderColor = colors.border,
|
|
32
|
+
|
|
33
|
+
// Borders and shadows
|
|
34
|
+
borderRadius = 24,
|
|
35
|
+
shadow = "0 -4px 12px rgba(0,0,0,0.1)",
|
|
36
|
+
|
|
37
|
+
// Spacing
|
|
38
|
+
headerPadding = "0 16px 8px 16px",
|
|
39
|
+
contentPadding = "0 16px",
|
|
40
|
+
actionPadding = "12px 16px",
|
|
41
|
+
dragHandlePadding = "14px 0 8px 0",
|
|
42
|
+
dragHandleWidth = 40,
|
|
43
|
+
dragHandleHeight = 4,
|
|
44
|
+
|
|
45
|
+
// Animation
|
|
46
|
+
animationDuration = 300,
|
|
47
|
+
|
|
48
|
+
// Z-index
|
|
49
|
+
zIndex = 9999,
|
|
50
|
+
overlayZIndex = 9998,
|
|
51
|
+
|
|
52
|
+
// Callbacks
|
|
53
|
+
onOpen,
|
|
54
|
+
onClose,
|
|
55
|
+
|
|
56
|
+
...rest
|
|
57
|
+
} = props;
|
|
58
|
+
|
|
59
|
+
let isOpen = false;
|
|
60
|
+
let overlay = null;
|
|
61
|
+
let sheetContainer = null;
|
|
62
|
+
let startY = 0;
|
|
63
|
+
let currentY = 0;
|
|
64
|
+
|
|
65
|
+
// ✅ Overlay with onclick
|
|
66
|
+
overlay = WidgetFactory({
|
|
67
|
+
position: "fixed",
|
|
68
|
+
top: 0,
|
|
69
|
+
left: 0,
|
|
70
|
+
width: "100%",
|
|
71
|
+
height: "100%",
|
|
72
|
+
backgroundColor: overlayColor,
|
|
73
|
+
zIndex: overlayZIndex,
|
|
74
|
+
opacity: 0,
|
|
75
|
+
visibility: "hidden",
|
|
76
|
+
transition: `opacity ${animationDuration}ms ease, visibility ${animationDuration}ms ease`,
|
|
77
|
+
// onclick: (e) => {
|
|
78
|
+
// // ✅ Only close if the click was directly on the overlay (not on the sheet)
|
|
79
|
+
// if (e.target === overlay && closeOnOverlayClick) {
|
|
80
|
+
// close();
|
|
81
|
+
// }
|
|
82
|
+
// },
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// ✅ Drag handle with stopPropagation
|
|
86
|
+
const dragHandle = showDragHandle
|
|
87
|
+
? WidgetFactory({
|
|
88
|
+
display: "flex",
|
|
89
|
+
justifyContent: "center",
|
|
90
|
+
padding: dragHandlePadding,
|
|
91
|
+
cursor: "grab",
|
|
92
|
+
onclick: (e) => e.stopPropagation(), // ✅ Prevents the click from reaching the overlay
|
|
93
|
+
child: WidgetFactory({
|
|
94
|
+
width: dragHandleWidth,
|
|
95
|
+
height: dragHandleHeight,
|
|
96
|
+
backgroundColor: dragHandleColor,
|
|
97
|
+
borderRadius: dragHandleHeight / 2,
|
|
98
|
+
}),
|
|
99
|
+
})
|
|
100
|
+
: null;
|
|
101
|
+
|
|
102
|
+
// Header
|
|
103
|
+
const headerChildren = [];
|
|
104
|
+
if (title) {
|
|
105
|
+
headerChildren.push(
|
|
106
|
+
Text({
|
|
107
|
+
text: title,
|
|
108
|
+
size: 18,
|
|
109
|
+
weight: "bold",
|
|
110
|
+
color: headerTextColor,
|
|
111
|
+
flex: 1,
|
|
112
|
+
}),
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
if (showCloseButton) {
|
|
116
|
+
const closeBtn = Icon({
|
|
117
|
+
name: "close",
|
|
118
|
+
size: 22,
|
|
119
|
+
color: colors.textSecondary,
|
|
120
|
+
cursor: "pointer",
|
|
121
|
+
});
|
|
122
|
+
closeBtn.onclick = () => close();
|
|
123
|
+
headerChildren.push(closeBtn);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const header =
|
|
127
|
+
title || showCloseButton
|
|
128
|
+
? Row({
|
|
129
|
+
alignItems: "center",
|
|
130
|
+
justifyContent: "space-between",
|
|
131
|
+
padding: headerPadding,
|
|
132
|
+
borderBottom: title ? `1px solid ${headerBorderColor}` : "none",
|
|
133
|
+
children: headerChildren,
|
|
134
|
+
})
|
|
135
|
+
: null;
|
|
136
|
+
|
|
137
|
+
// Actions footer
|
|
138
|
+
const actionsElement =
|
|
139
|
+
actions.length > 0
|
|
140
|
+
? Row({
|
|
141
|
+
alignItems: "center",
|
|
142
|
+
justifyContent: "flex-end",
|
|
143
|
+
gap: 8,
|
|
144
|
+
padding: actionPadding,
|
|
145
|
+
borderTop: `1px solid ${actionBorderColor}`,
|
|
146
|
+
marginTop: "auto",
|
|
147
|
+
children: actions,
|
|
148
|
+
})
|
|
149
|
+
: null;
|
|
150
|
+
|
|
151
|
+
// Content wrapper
|
|
152
|
+
const contentWrapper = Container({
|
|
153
|
+
flex: 1,
|
|
154
|
+
overflow: "auto",
|
|
155
|
+
padding: contentPadding,
|
|
156
|
+
child: content,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// ✅ SheetContainer with stopPropagation
|
|
160
|
+
sheetContainer = WidgetFactory({
|
|
161
|
+
position: "fixed",
|
|
162
|
+
bottom: 0,
|
|
163
|
+
left: 0,
|
|
164
|
+
right: 0,
|
|
165
|
+
paddingBottom: 12,
|
|
166
|
+
backgroundColor: backgroundColor,
|
|
167
|
+
borderRadius: `${borderRadius}px ${borderRadius}px 0 0`,
|
|
168
|
+
boxShadow: shadow,
|
|
169
|
+
transform: "translateY(100%)",
|
|
170
|
+
transition: `transform ${animationDuration}ms ease`,
|
|
171
|
+
maxHeight: maxHeight,
|
|
172
|
+
height: height === "auto" ? "auto" : height,
|
|
173
|
+
display: "flex",
|
|
174
|
+
flexDirection: "column",
|
|
175
|
+
zIndex: zIndex,
|
|
176
|
+
overflow: "hidden",
|
|
177
|
+
// onclick: (e) => e.stopPropagation(), // ✅ Prevents the click from reaching the overlay
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Body
|
|
181
|
+
const sheetBody = Column({
|
|
182
|
+
height: "100%",
|
|
183
|
+
gap: 12,
|
|
184
|
+
children: [dragHandle, header, contentWrapper, actionsElement].filter(
|
|
185
|
+
Boolean,
|
|
186
|
+
),
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
sheetContainer.appendChild(sheetBody);
|
|
190
|
+
overlay.appendChild(sheetContainer);
|
|
191
|
+
document.body.appendChild(overlay);
|
|
192
|
+
|
|
193
|
+
// Drag handlers
|
|
194
|
+
const onTouchStart = (e) => {
|
|
195
|
+
// ✅ Prevent the drag event from propagating to the overlay
|
|
196
|
+
e.stopPropagation();
|
|
197
|
+
startY = e.touches ? e.touches[0].clientY : e.clientY;
|
|
198
|
+
currentY = startY;
|
|
199
|
+
sheetContainer.style.transition = "none";
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const onTouchMove = (e) => {
|
|
203
|
+
if (!closeOnDragDown) return;
|
|
204
|
+
e.preventDefault();
|
|
205
|
+
const moveY = e.touches ? e.touches[0].clientY : e.clientY;
|
|
206
|
+
const delta = moveY - startY;
|
|
207
|
+
if (delta > 0) {
|
|
208
|
+
currentY = moveY;
|
|
209
|
+
sheetContainer.style.transform = `translateY(${delta}px)`;
|
|
210
|
+
const opacity = 1 - delta / sheetContainer.offsetHeight;
|
|
211
|
+
overlay.style.opacity = Math.max(0, Math.min(1, opacity));
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const onTouchEnd = (e) => {
|
|
216
|
+
e.stopPropagation();
|
|
217
|
+
sheetContainer.style.transition = `transform ${animationDuration}ms ease`;
|
|
218
|
+
const delta = currentY - startY;
|
|
219
|
+
if (delta > 100) {
|
|
220
|
+
close();
|
|
221
|
+
} else {
|
|
222
|
+
sheetContainer.style.transform = "translateY(0)";
|
|
223
|
+
overlay.style.opacity = "1";
|
|
224
|
+
}
|
|
225
|
+
startY = 0;
|
|
226
|
+
currentY = 0;
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const open = () => {
|
|
230
|
+
if (isOpen) return;
|
|
231
|
+
isOpen = true;
|
|
232
|
+
overlay.style.visibility = "visible";
|
|
233
|
+
overlay.style.opacity = "1";
|
|
234
|
+
sheetContainer.style.transform = "translateY(0)";
|
|
235
|
+
if (onOpen) onOpen();
|
|
236
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
237
|
+
|
|
238
|
+
if (closeOnDragDown && dragHandle) {
|
|
239
|
+
dragHandle.addEventListener("mousedown", onTouchStart);
|
|
240
|
+
// dragHandle.addEventListener("mousemove", onTouchMove);
|
|
241
|
+
dragHandle.addEventListener("mouseup", onTouchEnd);
|
|
242
|
+
dragHandle.addEventListener("touchstart", onTouchStart);
|
|
243
|
+
dragHandle.addEventListener("touchmove", onTouchMove);
|
|
244
|
+
dragHandle.addEventListener("touchend", onTouchEnd);
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const close = () => {
|
|
249
|
+
if (!isOpen) return;
|
|
250
|
+
isOpen = false;
|
|
251
|
+
sheetContainer.style.transform = "translateY(100%)";
|
|
252
|
+
overlay.style.opacity = "0";
|
|
253
|
+
setTimeout(() => {
|
|
254
|
+
overlay.style.visibility = "hidden";
|
|
255
|
+
}, animationDuration);
|
|
256
|
+
if (onClose) onClose();
|
|
257
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
258
|
+
|
|
259
|
+
if (closeOnDragDown && dragHandle) {
|
|
260
|
+
dragHandle.removeEventListener("mousedown", onTouchStart);
|
|
261
|
+
// dragHandle.removeEventListener("mousemove", onTouchMove);
|
|
262
|
+
dragHandle.removeEventListener("mouseup", onTouchEnd);
|
|
263
|
+
dragHandle.removeEventListener("touchstart", onTouchStart);
|
|
264
|
+
dragHandle.removeEventListener("touchmove", onTouchMove);
|
|
265
|
+
dragHandle.removeEventListener("touchend", onTouchEnd);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const toggle = () => {
|
|
270
|
+
if (isOpen) close();
|
|
271
|
+
else open();
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const destroy = () => {
|
|
275
|
+
if (overlay && overlay.parentNode) overlay.parentNode.removeChild(overlay);
|
|
276
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const handleKeyDown = (e) => {
|
|
280
|
+
if (e.key === "Escape" && isOpen) close();
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const bottomSheet = { open, close, toggle, destroy };
|
|
284
|
+
return bottomSheet;
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
export default BottomSheet;
|