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,139 @@
|
|
|
1
|
+
// navigations/CollapsibleSideBar.js
|
|
2
|
+
import { WidgetFactory } from "../widget-factory/index.js";
|
|
3
|
+
import { Container } from "../widgets/Container.js";
|
|
4
|
+
import { Column } from "../widgets/Column.js";
|
|
5
|
+
import { Icon } from "../widgets/Icon.js";
|
|
6
|
+
import { useState } from "../tools/useState.js";
|
|
7
|
+
import { colors } from "../utils/themes.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {Object} CollapsibleSideBarProps
|
|
11
|
+
* @property {HTMLElement} [children] - Content widget rendered inside the scrollable area.
|
|
12
|
+
* @property {boolean} [expanded=true] - Initial expanded state.
|
|
13
|
+
* @property {string} [id] - Stable identifier used to persist the expanded state across
|
|
14
|
+
* page navigations via `useState`. When omitted, all CollapsibleSideBar instances
|
|
15
|
+
* share the same state key, so provide an id when using multiple sidebars.
|
|
16
|
+
* @property {number} [widthExpanded=260] - Width in pixels when fully expanded.
|
|
17
|
+
* @property {number} [widthCollapsed=60] - Width in pixels when collapsed (icon-only).
|
|
18
|
+
* @property {number} [iconSize=24] - Size of the collapse/expand toggle icon.
|
|
19
|
+
* @property {Function} [onToggle] - Callback fired with the new `expanded` boolean after each toggle.
|
|
20
|
+
* @property {string} [bgColor] - Background color of the sidebar.
|
|
21
|
+
* @property {string} [borderRight] - CSS border value applied to the right edge.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* CollapsibleSideBar is a vertically scrollable side panel that can be
|
|
26
|
+
* toggled between an expanded state (showing full content) and a collapsed
|
|
27
|
+
* state (icon-only, narrow strip).
|
|
28
|
+
*
|
|
29
|
+
* The expanded state is persisted via `useState` so it survives page
|
|
30
|
+
* navigations within a session. Provide a unique `id` when rendering
|
|
31
|
+
* multiple instances to avoid state collisions.
|
|
32
|
+
*
|
|
33
|
+
* The toggle button renders a `chevron_left` icon when expanded and
|
|
34
|
+
* `chevron_right` when collapsed. The content area fades out when collapsed
|
|
35
|
+
* and has `pointer-events: none` applied, preventing accidental interaction
|
|
36
|
+
* with hidden content.
|
|
37
|
+
*
|
|
38
|
+
* Width transitions are driven by CSS `transition: width 0.3s ease` for a
|
|
39
|
+
* smooth animation without JavaScript timers.
|
|
40
|
+
*
|
|
41
|
+
* @param {CollapsibleSideBarProps} props
|
|
42
|
+
* @returns {HTMLElement} The sidebar container element, augmented with:
|
|
43
|
+
* - `updateWidth(w: number|string)` — resize the sidebar programmatically.
|
|
44
|
+
*/
|
|
45
|
+
export const CollapsibleSideBar = ({
|
|
46
|
+
children,
|
|
47
|
+
expanded = true,
|
|
48
|
+
id,
|
|
49
|
+
widthExpanded = 260,
|
|
50
|
+
widthCollapsed = 60,
|
|
51
|
+
iconSize = 24,
|
|
52
|
+
onToggle,
|
|
53
|
+
bgColor = colors.surface,
|
|
54
|
+
borderRight = `1px solid ${colors.border}`,
|
|
55
|
+
...rest
|
|
56
|
+
}) => {
|
|
57
|
+
// Persist the expanded state so collapsing/expanding survives navigation.
|
|
58
|
+
// A namespaced key is used when `id` is provided to allow multiple
|
|
59
|
+
// independent sidebars on the same page.
|
|
60
|
+
const [stateValue, setIsExpanded] = useState(
|
|
61
|
+
id ? `collapsible-sidebar:${id}` : "collapsible-sidebar",
|
|
62
|
+
expanded,
|
|
63
|
+
);
|
|
64
|
+
let expandedState = Boolean(stateValue.valueOf());
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Toggle the sidebar between expanded and collapsed states.
|
|
68
|
+
* Updates the persisted state and fires the `onToggle` callback.
|
|
69
|
+
*/
|
|
70
|
+
const toggle = () => {
|
|
71
|
+
const newState = !expandedState;
|
|
72
|
+
expandedState = newState;
|
|
73
|
+
setIsExpanded(newState);
|
|
74
|
+
if (onToggle) onToggle(newState);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const currentWidth = expandedState ? widthExpanded : widthCollapsed;
|
|
78
|
+
|
|
79
|
+
// Main sidebar container — fixed width with smooth CSS width transition.
|
|
80
|
+
const container = WidgetFactory({
|
|
81
|
+
tag: "div",
|
|
82
|
+
id,
|
|
83
|
+
width:
|
|
84
|
+
typeof currentWidth === "number" ? `${currentWidth}px` : currentWidth,
|
|
85
|
+
height: "100%",
|
|
86
|
+
display: "flex",
|
|
87
|
+
flexDirection: "column",
|
|
88
|
+
backgroundColor: bgColor,
|
|
89
|
+
borderRight: borderRight,
|
|
90
|
+
transition: "width 0.3s ease",
|
|
91
|
+
overflow: "hidden",
|
|
92
|
+
flexShrink: 0,
|
|
93
|
+
...rest,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Toggle button — always visible so the user can expand/collapse regardless
|
|
97
|
+
// of the current state. The icon flips direction to reflect the state.
|
|
98
|
+
const toggleButton = Container({
|
|
99
|
+
padding: 16,
|
|
100
|
+
cursor: "pointer",
|
|
101
|
+
onclick: toggle,
|
|
102
|
+
child: Icon({
|
|
103
|
+
name: expandedState ? "chevron_left" : "chevron_right",
|
|
104
|
+
size: iconSize,
|
|
105
|
+
color: colors.textSecondary,
|
|
106
|
+
}),
|
|
107
|
+
});
|
|
108
|
+
container.appendChild(toggleButton);
|
|
109
|
+
|
|
110
|
+
// Content wrapper — fades and becomes non-interactive when collapsed.
|
|
111
|
+
// Opacity + pointer-events together ensure collapsed content cannot be
|
|
112
|
+
// focused or clicked, even though the DOM nodes remain mounted.
|
|
113
|
+
const contentWrapper = Container({
|
|
114
|
+
flex: 1,
|
|
115
|
+
width: "100%",
|
|
116
|
+
opacity: expandedState ? 1 : 0,
|
|
117
|
+
transition: "opacity 0.2s ease",
|
|
118
|
+
pointerEvents: expandedState ? "auto" : "none",
|
|
119
|
+
overflow: "auto",
|
|
120
|
+
child: children,
|
|
121
|
+
});
|
|
122
|
+
container.appendChild(contentWrapper);
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Resize the sidebar to a new width without triggering a toggle.
|
|
126
|
+
* Useful when the parent layout needs to adjust sidebar proportions
|
|
127
|
+
* dynamically (e.g. responsive breakpoint changes).
|
|
128
|
+
*
|
|
129
|
+
* @param {number|string} newWidth - New width (number = pixels, string = CSS value).
|
|
130
|
+
*/
|
|
131
|
+
container.updateWidth = (newWidth) => {
|
|
132
|
+
container.style.width =
|
|
133
|
+
typeof newWidth === "number" ? `${newWidth}px` : newWidth;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
return container;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export default CollapsibleSideBar;
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
// src/navigations/Drawer.js
|
|
2
|
+
import { Container } from "../widgets/Container.js";
|
|
3
|
+
import { Column } from "../widgets/Column.js";
|
|
4
|
+
import { dimensions } from "../tools/dimensions.js";
|
|
5
|
+
import { colors } from "../utils/themes.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Module-level singleton reference to the most recently created Drawer.
|
|
9
|
+
* The global helpers (openDrawer, closeDrawer, toggleDrawer, destroyDrawer)
|
|
10
|
+
* operate on this instance, which means only one drawer is reachable via the
|
|
11
|
+
* global API at a time. Use the returned instance object directly when you
|
|
12
|
+
* need to manage multiple drawers.
|
|
13
|
+
*
|
|
14
|
+
* @type {{open: Function, close: Function, toggle: Function, destroy: Function, element: HTMLElement}|null}
|
|
15
|
+
*/
|
|
16
|
+
let drawerInstance = null;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {Object} DrawerProps
|
|
20
|
+
* @property {HTMLElement|null} [header] - Widget rendered at the top of the drawer panel.
|
|
21
|
+
* @property {HTMLElement[]} [body=[]] - Array of widgets filling the scrollable middle section.
|
|
22
|
+
* @property {HTMLElement|null} [footer] - Widget rendered at the bottom of the drawer panel.
|
|
23
|
+
* @property {"left"|"right"} [position="left"] - Which edge the drawer slides in from.
|
|
24
|
+
* @property {number|string} [width=280] - Width of the drawer panel (number = pixels).
|
|
25
|
+
* @property {Function} [onClose] - Callback fired after the close animation completes.
|
|
26
|
+
* @property {Function} [onOpen] - Callback fired immediately when the drawer opens.
|
|
27
|
+
* @property {boolean} [blur=true] - Whether to apply a backdrop blur behind the overlay.
|
|
28
|
+
* @property {number} [blurIntensity=4] - Blur radius in pixels (used when blur is true).
|
|
29
|
+
* @property {string} [bgColor] - Background color of the drawer panel.
|
|
30
|
+
* @property {number} [elevation=4] - Box-shadow depth for the panel edge.
|
|
31
|
+
* @property {boolean} [closeOnOverlayClick=true] - Whether a tap on the backdrop closes the drawer.
|
|
32
|
+
* @property {boolean} [closeOnEsc=true] - Whether the Escape key closes the drawer.
|
|
33
|
+
* @property {number} [borderRadius=24] - Corner radius applied to the panel's outer corners.
|
|
34
|
+
* @property {number} [margin=0] - Offset from the screen edge (pushes panel inward).
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @typedef {Object} DrawerInstance
|
|
39
|
+
* @property {Function} open - Slide the drawer into view.
|
|
40
|
+
* @property {Function} close - Slide the drawer out of view.
|
|
41
|
+
* @property {Function} toggle - Toggle between open and closed states.
|
|
42
|
+
* @property {Function} destroy - Remove the drawer from the DOM and clean up all listeners.
|
|
43
|
+
* @property {HTMLElement} element - The outer wrapper element appended to document.body.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Drawer creates a slide-in panel anchored to the left or right edge of the
|
|
48
|
+
* screen. It is appended directly to `document.body` and uses a full-screen
|
|
49
|
+
* wrapper so the overlay backdrop can cover the entire viewport.
|
|
50
|
+
*
|
|
51
|
+
* Corner radius is applied only to the outer corners of the panel (i.e. the
|
|
52
|
+
* two corners that face away from the screen edge) to maintain a flush
|
|
53
|
+
* attachment at the edge side.
|
|
54
|
+
*
|
|
55
|
+
* The module keeps a singleton reference so the standalone helper functions
|
|
56
|
+
* (openDrawer, closeDrawer, etc.) can be called without passing an instance.
|
|
57
|
+
*
|
|
58
|
+
* @param {DrawerProps} [props={}]
|
|
59
|
+
* @returns {DrawerInstance}
|
|
60
|
+
*/
|
|
61
|
+
export const Drawer = (props = {}) => {
|
|
62
|
+
const {
|
|
63
|
+
header,
|
|
64
|
+
body = [],
|
|
65
|
+
footer,
|
|
66
|
+
position = "left",
|
|
67
|
+
width = 280,
|
|
68
|
+
onClose,
|
|
69
|
+
onOpen,
|
|
70
|
+
blur = true,
|
|
71
|
+
blurIntensity = 4,
|
|
72
|
+
bgColor = colors?.surface || "#ffffff",
|
|
73
|
+
elevation = 4,
|
|
74
|
+
closeOnOverlayClick = true,
|
|
75
|
+
closeOnEsc = true,
|
|
76
|
+
borderRadius = 24,
|
|
77
|
+
margin = 0,
|
|
78
|
+
...rest
|
|
79
|
+
} = props;
|
|
80
|
+
|
|
81
|
+
const element = document.createElement("div");
|
|
82
|
+
let isOpen = false;
|
|
83
|
+
let closeTimer = null;
|
|
84
|
+
|
|
85
|
+
// The outer wrapper covers the full screen so the backdrop sits above all
|
|
86
|
+
// other content. It starts hidden and becomes visible on open().
|
|
87
|
+
element.style.position = "fixed";
|
|
88
|
+
element.style.top = "0";
|
|
89
|
+
element.style.left = "0";
|
|
90
|
+
element.style.width = `${dimensions.width}px`;
|
|
91
|
+
element.style.height = `${dimensions.height}px`;
|
|
92
|
+
element.style.zIndex = "10000";
|
|
93
|
+
element.style.display = "none";
|
|
94
|
+
|
|
95
|
+
// Overlay backdrop — semi-transparent with optional blur effect.
|
|
96
|
+
const overlay = document.createElement("div");
|
|
97
|
+
overlay.style.position = "absolute";
|
|
98
|
+
overlay.style.top = "0";
|
|
99
|
+
overlay.style.left = "0";
|
|
100
|
+
overlay.style.width = "100%";
|
|
101
|
+
overlay.style.height = "100%";
|
|
102
|
+
overlay.style.backdropFilter = blur ? `blur(${blurIntensity}px)` : "none";
|
|
103
|
+
overlay.style.backgroundColor = "rgba(0,0,0,0.3)";
|
|
104
|
+
overlay.style.transition = "opacity 0.3s ease";
|
|
105
|
+
overlay.style.opacity = "0";
|
|
106
|
+
|
|
107
|
+
// Sliding drawer panel.
|
|
108
|
+
const drawerPanel = document.createElement("div");
|
|
109
|
+
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)`;
|
|
113
|
+
drawerPanel.style.display = "flex";
|
|
114
|
+
drawerPanel.style.flexDirection = "column";
|
|
115
|
+
drawerPanel.style.backgroundColor = bgColor;
|
|
116
|
+
drawerPanel.style.overflow = "hidden";
|
|
117
|
+
|
|
118
|
+
// Apply border radius only to the corners that face away from the screen edge.
|
|
119
|
+
// A left drawer gets rounded right corners; a right drawer gets rounded left corners.
|
|
120
|
+
if (position === "left") {
|
|
121
|
+
drawerPanel.style.left = `${margin}px`;
|
|
122
|
+
drawerPanel.style.right = "auto";
|
|
123
|
+
drawerPanel.style.borderTopRightRadius = `${borderRadius}px`;
|
|
124
|
+
drawerPanel.style.borderBottomRightRadius = `${borderRadius}px`;
|
|
125
|
+
drawerPanel.style.borderTopLeftRadius = "0";
|
|
126
|
+
drawerPanel.style.borderBottomLeftRadius = "0";
|
|
127
|
+
} else {
|
|
128
|
+
drawerPanel.style.right = `${margin}px`;
|
|
129
|
+
drawerPanel.style.left = "auto";
|
|
130
|
+
drawerPanel.style.borderTopLeftRadius = `${borderRadius}px`;
|
|
131
|
+
drawerPanel.style.borderBottomLeftRadius = `${borderRadius}px`;
|
|
132
|
+
drawerPanel.style.borderTopRightRadius = "0";
|
|
133
|
+
drawerPanel.style.borderBottomRightRadius = "0";
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
drawerPanel.style.width = typeof width === "number" ? `${width}px` : width;
|
|
137
|
+
// Start the panel off-screen so the entrance animation slides it in.
|
|
138
|
+
drawerPanel.style.transform =
|
|
139
|
+
position === "left" ? "translateX(-100%)" : "translateX(100%)";
|
|
140
|
+
drawerPanel.style.transition = "transform 0.3s ease-out";
|
|
141
|
+
|
|
142
|
+
// Shadow faces inward, away from the attachment edge.
|
|
143
|
+
if (elevation > 0) {
|
|
144
|
+
if (position === "left") {
|
|
145
|
+
drawerPanel.style.boxShadow = `${elevation}px 0 ${elevation * 2}px rgba(0,0,0,0.15)`;
|
|
146
|
+
} else {
|
|
147
|
+
drawerPanel.style.boxShadow = `-${elevation}px 0 ${elevation * 2}px rgba(0,0,0,0.15)`;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ========== BUILD PANEL CONTENT ==========
|
|
152
|
+
// The header and footer containers intentionally only round the corners
|
|
153
|
+
// that align with the panel's outer edge, so content fills flush at the
|
|
154
|
+
// attachment side.
|
|
155
|
+
const contentContainer = Column({
|
|
156
|
+
height: "100%",
|
|
157
|
+
display: "flex",
|
|
158
|
+
flexDirection: "column",
|
|
159
|
+
children: [
|
|
160
|
+
header &&
|
|
161
|
+
Container({
|
|
162
|
+
flexShrink: 0,
|
|
163
|
+
borderTopLeftRadius: position === "right" ? `${borderRadius}px` : "0",
|
|
164
|
+
borderTopRightRadius: position === "left" ? `${borderRadius}px` : "0",
|
|
165
|
+
overflow: "hidden",
|
|
166
|
+
child: header,
|
|
167
|
+
}),
|
|
168
|
+
|
|
169
|
+
body.length > 0 &&
|
|
170
|
+
Column({
|
|
171
|
+
flex: 1,
|
|
172
|
+
paddingTop: 8,
|
|
173
|
+
paddingBottom: 8,
|
|
174
|
+
overflow: "auto",
|
|
175
|
+
backgroundColor: bgColor,
|
|
176
|
+
children: body,
|
|
177
|
+
}),
|
|
178
|
+
|
|
179
|
+
footer &&
|
|
180
|
+
Container({
|
|
181
|
+
flexShrink: 0,
|
|
182
|
+
borderBottomLeftRadius:
|
|
183
|
+
position === "right" ? `${borderRadius}px` : "0",
|
|
184
|
+
borderBottomRightRadius:
|
|
185
|
+
position === "left" ? `${borderRadius}px` : "0",
|
|
186
|
+
overflow: "hidden",
|
|
187
|
+
child: footer,
|
|
188
|
+
}),
|
|
189
|
+
].filter(Boolean),
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
drawerPanel.appendChild(contentContainer);
|
|
193
|
+
element.appendChild(overlay);
|
|
194
|
+
element.appendChild(drawerPanel);
|
|
195
|
+
|
|
196
|
+
// ========== KEYBOARD HANDLER ==========
|
|
197
|
+
const handleKeyDown = (e) => {
|
|
198
|
+
if (closeOnEsc && e.key === "Escape" && isOpen) {
|
|
199
|
+
close();
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// ========== CLOSE ==========
|
|
204
|
+
/**
|
|
205
|
+
* Slide the drawer out of view with a CSS transition.
|
|
206
|
+
* The outer wrapper is hidden after the transition completes (300ms) to
|
|
207
|
+
* remove it from the accessibility tree. Re-entrant calls are ignored.
|
|
208
|
+
*/
|
|
209
|
+
const close = () => {
|
|
210
|
+
if (!isOpen) return;
|
|
211
|
+
isOpen = false;
|
|
212
|
+
// Cancel any pending hide timer from a previous close call.
|
|
213
|
+
if (closeTimer) {
|
|
214
|
+
clearTimeout(closeTimer);
|
|
215
|
+
closeTimer = null;
|
|
216
|
+
}
|
|
217
|
+
drawerPanel.style.transform =
|
|
218
|
+
position === "left" ? "translateX(-100%)" : "translateX(100%)";
|
|
219
|
+
overlay.style.opacity = "0";
|
|
220
|
+
closeTimer = setTimeout(() => {
|
|
221
|
+
element.style.display = "none";
|
|
222
|
+
closeTimer = null;
|
|
223
|
+
if (onClose) onClose();
|
|
224
|
+
}, 300);
|
|
225
|
+
if (closeOnEsc) document.removeEventListener("keydown", handleKeyDown);
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// ========== OPEN ==========
|
|
229
|
+
/**
|
|
230
|
+
* Slide the drawer into view.
|
|
231
|
+
* Forces a reflow (offsetHeight read) before applying the transition so
|
|
232
|
+
* the animation plays even when the element was just made visible.
|
|
233
|
+
* Re-entrant calls are ignored.
|
|
234
|
+
*/
|
|
235
|
+
const open = () => {
|
|
236
|
+
if (isOpen) return;
|
|
237
|
+
isOpen = true;
|
|
238
|
+
if (closeTimer) {
|
|
239
|
+
clearTimeout(closeTimer);
|
|
240
|
+
closeTimer = null;
|
|
241
|
+
}
|
|
242
|
+
element.style.display = "block";
|
|
243
|
+
// Trigger a reflow so the browser registers the initial transform value
|
|
244
|
+
// before the transition starts; without this the animation is skipped.
|
|
245
|
+
void element.offsetHeight;
|
|
246
|
+
overlay.style.opacity = "1";
|
|
247
|
+
drawerPanel.style.transform = "translateX(0)";
|
|
248
|
+
if (onOpen) onOpen();
|
|
249
|
+
if (closeOnEsc) document.addEventListener("keydown", handleKeyDown);
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Toggle between open and closed states.
|
|
254
|
+
*/
|
|
255
|
+
const toggle = () => {
|
|
256
|
+
if (isOpen) close();
|
|
257
|
+
else open();
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Remove the drawer element from the DOM and release all listeners.
|
|
262
|
+
* After destroy() the instance is no longer usable.
|
|
263
|
+
*/
|
|
264
|
+
const destroy = () => {
|
|
265
|
+
if (closeTimer) clearTimeout(closeTimer);
|
|
266
|
+
if (closeOnEsc) document.removeEventListener("keydown", handleKeyDown);
|
|
267
|
+
removeResizeListener();
|
|
268
|
+
if (element.parentNode) element.parentNode.removeChild(element);
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
if (closeOnOverlayClick) {
|
|
272
|
+
overlay.addEventListener("click", close);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Keep the wrapper dimensions in sync with the viewport when the window
|
|
276
|
+
// is resized (e.g. orientation change on mobile).
|
|
277
|
+
const handleResize = () => {
|
|
278
|
+
element.style.width = `${dimensions.width}px`;
|
|
279
|
+
element.style.height = `${dimensions.height}px`;
|
|
280
|
+
};
|
|
281
|
+
const removeResizeListener = dimensions.addListener(handleResize);
|
|
282
|
+
|
|
283
|
+
const instance = { open, close, toggle, destroy, element };
|
|
284
|
+
// Register as the module-level singleton so global helpers work.
|
|
285
|
+
drawerInstance = instance;
|
|
286
|
+
document.body.appendChild(element);
|
|
287
|
+
|
|
288
|
+
return instance;
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// ========== GLOBAL HELPER FUNCTIONS ==========
|
|
292
|
+
// These operate on the most recently created Drawer instance.
|
|
293
|
+
// Use the instance returned by Drawer() directly when managing multiple drawers.
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Open the most recently created Drawer.
|
|
297
|
+
* Logs a warning if no Drawer has been initialised yet.
|
|
298
|
+
*/
|
|
299
|
+
export const openDrawer = () => {
|
|
300
|
+
if (drawerInstance) drawerInstance.open();
|
|
301
|
+
else console.warn("Drawer not initialized");
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Close the most recently created Drawer.
|
|
306
|
+
* Logs a warning if no Drawer has been initialised yet.
|
|
307
|
+
*/
|
|
308
|
+
export const closeDrawer = () => {
|
|
309
|
+
if (drawerInstance) drawerInstance.close();
|
|
310
|
+
else console.warn("Drawer not initialized");
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Toggle the most recently created Drawer.
|
|
315
|
+
* Logs a warning if no Drawer has been initialised yet.
|
|
316
|
+
*/
|
|
317
|
+
export const toggleDrawer = () => {
|
|
318
|
+
if (drawerInstance) drawerInstance.toggle();
|
|
319
|
+
else console.warn("Drawer not initialized");
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Destroy the most recently created Drawer and clear the singleton reference.
|
|
324
|
+
* Safe to call even when no drawer exists.
|
|
325
|
+
*/
|
|
326
|
+
export const destroyDrawer = () => {
|
|
327
|
+
if (drawerInstance) {
|
|
328
|
+
drawerInstance.destroy();
|
|
329
|
+
drawerInstance = null;
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
export default Drawer;
|