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.
Files changed (178) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +190 -2
  3. package/bin/cli.js +394 -0
  4. package/bin/commands/buildAndroid.js +200 -0
  5. package/bin/commands/buildIOS.js +237 -0
  6. package/bin/commands/create.js +406 -0
  7. package/bin/commands/createBundle.js +375 -0
  8. package/bin/commands/createComponent.js +115 -0
  9. package/bin/commands/createScreen.js +161 -0
  10. package/bin/commands/killServer.js +107 -0
  11. package/bin/commands/package.js +131 -0
  12. package/bin/commands/release.js +189 -0
  13. package/bin/commands/runBundle.js +62 -0
  14. package/bin/commands/runCloudflare.js +89 -0
  15. package/bin/commands/runNative.js +109 -0
  16. package/bin/commands/runServer.js +390 -0
  17. package/bin/ota/loader.js +361 -0
  18. package/bin/utils/bundleConfig.js +178 -0
  19. package/bin/utils/capacitor.js +137 -0
  20. package/bin/utils/colors.js +215 -0
  21. package/bin/utils/helpers.js +80 -0
  22. package/bin/utils/spinner.js +96 -0
  23. package/bin/utils/templates.js +1824 -0
  24. package/bin/utils/version.js +108 -0
  25. package/package.json +58 -4
  26. package/src/animations/AnimatedBox.js +607 -0
  27. package/src/animations/AnimatedText.js +105 -0
  28. package/src/animations/MatrixRain.js +220 -0
  29. package/src/animations/ParallaxBox.js +369 -0
  30. package/src/animations/animate.js +332 -0
  31. package/src/animations/index.js +25 -0
  32. package/src/app.js +41 -0
  33. package/src/assets/icon-1024-maskable.png +0 -0
  34. package/src/assets/icon-192-maskable.png +0 -0
  35. package/src/assets/icon-192.png +0 -0
  36. package/src/assets/icon-512-maskable.png +0 -0
  37. package/src/assets/icon-512.png +0 -0
  38. package/src/assets/logo.png +0 -0
  39. package/src/core/App.js +44 -0
  40. package/src/core/hmr-client.js +142 -0
  41. package/src/core/index.js +34 -0
  42. package/src/core/pwa.js +100 -0
  43. package/src/core/runApp.js +430 -0
  44. package/src/create-index.sh +237 -0
  45. package/src/fonts/MaterialIcons-Regular.woff2 +0 -0
  46. package/src/fonts/icons.css +23 -0
  47. package/src/index.d.ts +1930 -0
  48. package/src/index.js +288 -0
  49. package/src/navigations/AdaptiveScaffold.js +149 -0
  50. package/src/navigations/AppBar.js +331 -0
  51. package/src/navigations/BottomNavigation.js +298 -0
  52. package/src/navigations/CollapsibleSideBar.js +139 -0
  53. package/src/navigations/Drawer.js +333 -0
  54. package/src/navigations/DrawerItem.js +260 -0
  55. package/src/navigations/Router.js +475 -0
  56. package/src/navigations/Scaffold.js +537 -0
  57. package/src/navigations/Tabs.js +384 -0
  58. package/src/navigations/index.js +57 -0
  59. package/src/services/RamStore.js +213 -0
  60. package/src/services/Session.js +310 -0
  61. package/src/services/Storage.js +309 -0
  62. package/src/services/http.js +350 -0
  63. package/src/services/index.js +57 -0
  64. package/src/tools/animation.js +42 -0
  65. package/src/tools/array.js +95 -0
  66. package/src/tools/border.js +28 -0
  67. package/src/tools/clipboard.js +104 -0
  68. package/src/tools/color.js +329 -0
  69. package/src/tools/createList.js +215 -0
  70. package/src/tools/delay.js +55 -0
  71. package/src/tools/device.js +138 -0
  72. package/src/tools/dict.js +297 -0
  73. package/src/tools/dimensions.js +118 -0
  74. package/src/tools/filter.js +41 -0
  75. package/src/tools/flex.js +26 -0
  76. package/src/tools/gradient.js +34 -0
  77. package/src/tools/grid.js +57 -0
  78. package/src/tools/index.js +38 -0
  79. package/src/tools/mapList.js +107 -0
  80. package/src/tools/margin.js +93 -0
  81. package/src/tools/memo.js +60 -0
  82. package/src/tools/os.js +104 -0
  83. package/src/tools/padding.js +93 -0
  84. package/src/tools/print.js +69 -0
  85. package/src/tools/random.js +299 -0
  86. package/src/tools/ref.js +70 -0
  87. package/src/tools/rgba.js +4 -0
  88. package/src/tools/shadow.js +23 -0
  89. package/src/tools/string.js +85 -0
  90. package/src/tools/time.js +674 -0
  91. package/src/tools/transform.js +68 -0
  92. package/src/tools/transition.js +48 -0
  93. package/src/tools/useState.js +268 -0
  94. package/src/tools/uuid.js +42 -0
  95. package/src/utils/TextInputValidator.js +256 -0
  96. package/src/utils/composeUpdate.js +65 -0
  97. package/src/utils/getWidgetProps.js +35 -0
  98. package/src/utils/index.js +76 -0
  99. package/src/utils/markdownParser.js +368 -0
  100. package/src/utils/mediaTime.js +79 -0
  101. package/src/utils/navigation.js +263 -0
  102. package/src/utils/scriptInjector.js +73 -0
  103. package/src/utils/stackPosition.js +31 -0
  104. package/src/utils/stopWebRefresh.js +77 -0
  105. package/src/utils/styleInjector.js +38 -0
  106. package/src/utils/syntaxHighlight.js +339 -0
  107. package/src/utils/themes.js +347 -0
  108. package/src/utils/units.js +87 -0
  109. package/src/utils/visualEffects.js +169 -0
  110. package/src/widget-factory/addChildren.js +114 -0
  111. package/src/widget-factory/assignProps.js +256 -0
  112. package/src/widget-factory/createWidget.js +28 -0
  113. package/src/widget-factory/effects.js +132 -0
  114. package/src/widget-factory/index.js +7 -0
  115. package/src/widget-factory/lifecycle.js +210 -0
  116. package/src/widget-factory/makeParentable.js +89 -0
  117. package/src/widget-factory/pixTool.js +40 -0
  118. package/src/widget-factory/processProps.js +122 -0
  119. package/src/widget-factory/reactivity.js +98 -0
  120. package/src/widget-factory/remTool.js +34 -0
  121. package/src/widget-factory/stackPosition.js +87 -0
  122. package/src/widget-factory/stringTool.js +26 -0
  123. package/src/widget-factory/tools.js +249 -0
  124. package/src/widget-factory/translateProps.js +1049 -0
  125. package/src/widget-factory/warnings.js +284 -0
  126. package/src/widget-factory/widgetFactory.js +200 -0
  127. package/src/widgets/Accordion.js +562 -0
  128. package/src/widgets/AdMob.js +315 -0
  129. package/src/widgets/AdSense.js +268 -0
  130. package/src/widgets/AlertDialog.js +162 -0
  131. package/src/widgets/Audio.js +311 -0
  132. package/src/widgets/Avatar.js +210 -0
  133. package/src/widgets/Badge.js +189 -0
  134. package/src/widgets/BottomSheet.js +287 -0
  135. package/src/widgets/Button.js +247 -0
  136. package/src/widgets/Card.js +33 -0
  137. package/src/widgets/Carousel.js +301 -0
  138. package/src/widgets/Chart.js +632 -0
  139. package/src/widgets/Checkbox.js +106 -0
  140. package/src/widgets/Chip.js +153 -0
  141. package/src/widgets/CircularBar.js +352 -0
  142. package/src/widgets/CircularChart.js +549 -0
  143. package/src/widgets/CodeViewer.js +236 -0
  144. package/src/widgets/Column.js +29 -0
  145. package/src/widgets/Container.js +31 -0
  146. package/src/widgets/DataTable.js +257 -0
  147. package/src/widgets/Divider.js +93 -0
  148. package/src/widgets/DraggBox.js +115 -0
  149. package/src/widgets/Dropdown.js +505 -0
  150. package/src/widgets/DroppBox.js +180 -0
  151. package/src/widgets/FloatingActionButton.js +91 -0
  152. package/src/widgets/GridView.js +20 -0
  153. package/src/widgets/Icon.js +33 -0
  154. package/src/widgets/Image.js +15 -0
  155. package/src/widgets/Input.js +565 -0
  156. package/src/widgets/Inspector.js +254 -0
  157. package/src/widgets/InstallButton.js +110 -0
  158. package/src/widgets/ListTile.js +224 -0
  159. package/src/widgets/ListView.js +503 -0
  160. package/src/widgets/Markdown.js +1236 -0
  161. package/src/widgets/Modal.js +496 -0
  162. package/src/widgets/Pagination.js +195 -0
  163. package/src/widgets/ProgressBar.js +178 -0
  164. package/src/widgets/QRCode.js +467 -0
  165. package/src/widgets/Radio.js +132 -0
  166. package/src/widgets/Rating.js +261 -0
  167. package/src/widgets/Row.js +29 -0
  168. package/src/widgets/Skeleton.js +243 -0
  169. package/src/widgets/Slider.js +527 -0
  170. package/src/widgets/SnackBar.js +227 -0
  171. package/src/widgets/Stack.js +29 -0
  172. package/src/widgets/Stepper.js +296 -0
  173. package/src/widgets/Switch.js +102 -0
  174. package/src/widgets/Text.js +98 -0
  175. package/src/widgets/Tooltip.js +267 -0
  176. package/src/widgets/TreeView.js +370 -0
  177. package/src/widgets/Video.js +92 -0
  178. 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;