@versini/ui-panel 8.1.4 → 9.0.0

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 (2) hide show
  1. package/dist/index.js +198 -9
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  /*!
2
- @versini/ui-panel v8.1.4
2
+ @versini/ui-panel v9.0.0
3
3
  © 2025 gizmette.com
4
4
  */
5
5
 
6
6
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
- import { Modal, ModalClose, ModalContent, ModalDescription, ModalHeading } from "@versini/ui-modal";
8
- import { useEffect, useRef, useState } from "react";
7
+ import { cloneElement, createContext, forwardRef, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
8
+ import { FloatingFocusManager, FloatingNode, FloatingOverlay, FloatingPortal, FloatingTree, useClick, useDismiss, useFloating, useFloatingNodeId, useFloatingParentNodeId, useInteractions, useMergeRefs, useRole } from "@floating-ui/react";
9
9
  import clsx from "clsx";
10
10
 
11
11
  ;// CONCATENATED MODULE: ./src/common/constants.ts
@@ -22,12 +22,201 @@ const NONE = "none";
22
22
 
23
23
  ;// CONCATENATED MODULE: external "react/jsx-runtime"
24
24
 
25
- ;// CONCATENATED MODULE: external "@versini/ui-modal"
26
-
27
25
  ;// CONCATENATED MODULE: external "react"
28
26
 
27
+ ;// CONCATENATED MODULE: external "@floating-ui/react"
28
+
29
29
  ;// CONCATENATED MODULE: external "clsx"
30
30
 
31
+ ;// CONCATENATED MODULE: ./src/components/Modal/ModalContext.tsx
32
+
33
+ const ModalContext = /*#__PURE__*/ createContext(null);
34
+
35
+ ;// CONCATENATED MODULE: ./src/components/Modal/ModalHooks.tsx
36
+
37
+
38
+
39
+ function useModal({ initialOpen = false, open: controlledOpen, onOpenChange: setControlledOpen, initialFocus } = {}) {
40
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(initialOpen);
41
+ const [labelId, setLabelId] = useState();
42
+ const [descriptionId, setDescriptionId] = useState();
43
+ /* v8 ignore start */ const open = controlledOpen ?? uncontrolledOpen;
44
+ const setOpen = setControlledOpen ?? setUncontrolledOpen;
45
+ /* v8 ignore stop */ // Subscribe this modal to the FloatingTree for nested modal support
46
+ const nodeId = useFloatingNodeId();
47
+ const data = useFloating({
48
+ nodeId,
49
+ open,
50
+ onOpenChange: setOpen
51
+ });
52
+ const context = data.context;
53
+ const click = useClick(context, {
54
+ enabled: controlledOpen == null
55
+ });
56
+ const dismiss = useDismiss(context, {
57
+ outsidePress: false,
58
+ outsidePressEvent: "mousedown",
59
+ bubbles: {
60
+ escapeKey: false
61
+ }
62
+ });
63
+ const role = useRole(context);
64
+ const interactions = useInteractions([
65
+ click,
66
+ dismiss,
67
+ role
68
+ ]);
69
+ return useMemo(()=>({
70
+ open,
71
+ setOpen,
72
+ ...interactions,
73
+ ...data,
74
+ labelId,
75
+ descriptionId,
76
+ setLabelId,
77
+ setDescriptionId,
78
+ initialFocus,
79
+ nodeId
80
+ }), [
81
+ open,
82
+ setOpen,
83
+ interactions,
84
+ data,
85
+ labelId,
86
+ descriptionId,
87
+ initialFocus,
88
+ nodeId
89
+ ]);
90
+ }
91
+ const useModalContext = ()=>{
92
+ const context = useContext(ModalContext);
93
+ /* v8 ignore start */ if (context == null) {
94
+ throw new Error("Modal components must be wrapped in <Modal />");
95
+ }
96
+ /* v8 ignore stop */ return context;
97
+ };
98
+
99
+ ;// CONCATENATED MODULE: ./src/components/Modal/Modal.tsx
100
+
101
+
102
+
103
+
104
+
105
+
106
+
107
+ function ModalComponent({ children, ...options }) {
108
+ const dialog = useModal(options);
109
+ return /*#__PURE__*/ jsx(ModalContext.Provider, {
110
+ value: dialog,
111
+ children: children
112
+ });
113
+ }
114
+ /**
115
+ * Modal component that supports proper nesting with ESC key handling.
116
+ * Automatically wraps root modals in FloatingTree for nested modal support.
117
+ */ function Modal(props) {
118
+ const parentId = useFloatingParentNodeId();
119
+ /* v8 ignore start - Branch coverage for nested modal case */ // If this is a root modal (no parent), wrap with FloatingTree
120
+ if (parentId === null) {
121
+ /* v8 ignore stop */ return /*#__PURE__*/ jsx(FloatingTree, {
122
+ children: /*#__PURE__*/ jsx(ModalComponent, {
123
+ ...props
124
+ })
125
+ });
126
+ }
127
+ /* v8 ignore start - Nested modal case requires complex floating-ui setup */ // Nested modal - FloatingTree already exists
128
+ return /*#__PURE__*/ jsx(ModalComponent, {
129
+ ...props
130
+ });
131
+ /* v8 ignore stop */ }
132
+ const Modal_ModalContent = /*#__PURE__*/ forwardRef(function ModalContent(props, propRef) {
133
+ const { context: floatingContext, nodeId, ...context } = useModalContext();
134
+ const ref = useMergeRefs([
135
+ context.refs.setFloating,
136
+ propRef
137
+ ]);
138
+ /* v8 ignore start */ if (!floatingContext.open) {
139
+ return null;
140
+ }
141
+ /* v8 ignore stop */ const { overlayBackground, ...rest } = props;
142
+ const overlayClass = clsx("grid place-items-center", {
143
+ "bg-black sm:bg-black/80": !overlayBackground
144
+ });
145
+ return /*#__PURE__*/ jsx(FloatingNode, {
146
+ id: nodeId,
147
+ children: /*#__PURE__*/ jsx(FloatingPortal, {
148
+ children: /*#__PURE__*/ jsx(FloatingOverlay, {
149
+ className: overlayClass,
150
+ lockScroll: true,
151
+ children: /*#__PURE__*/ jsx(FloatingFocusManager, {
152
+ context: floatingContext,
153
+ initialFocus: context.initialFocus,
154
+ children: /*#__PURE__*/ jsx("div", {
155
+ ref: ref,
156
+ "aria-labelledby": context.labelId,
157
+ "aria-describedby": context.descriptionId,
158
+ ...context.getFloatingProps(rest),
159
+ children: rest.children
160
+ })
161
+ })
162
+ })
163
+ })
164
+ });
165
+ });
166
+ const Modal_ModalHeading = /*#__PURE__*/ forwardRef(function ModalHeading({ children, ...props }, ref) {
167
+ const { setLabelId } = useModalContext();
168
+ const id = useId();
169
+ // Only sets `aria-labelledby` on the Modal root element
170
+ // if this component is mounted inside it.
171
+ useLayoutEffect(()=>{
172
+ setLabelId(id);
173
+ return ()=>setLabelId(undefined);
174
+ }, [
175
+ id,
176
+ setLabelId
177
+ ]);
178
+ return /*#__PURE__*/ jsx("h1", {
179
+ ...props,
180
+ ref: ref,
181
+ id: id,
182
+ children: children
183
+ });
184
+ });
185
+ const Modal_ModalDescription = /*#__PURE__*/ forwardRef(function ModalDescription({ children, ...props }, ref) {
186
+ const { setDescriptionId } = useModalContext();
187
+ const id = useId();
188
+ // Only sets `aria-describedby` on the Modal root element
189
+ // if this component is mounted inside it.
190
+ useLayoutEffect(()=>{
191
+ setDescriptionId(id);
192
+ return ()=>setDescriptionId(undefined);
193
+ }, [
194
+ id,
195
+ setDescriptionId
196
+ ]);
197
+ return /*#__PURE__*/ jsx("div", {
198
+ ...props,
199
+ ref: ref,
200
+ id: id,
201
+ children: children
202
+ });
203
+ });
204
+ const Modal_ModalClose = /*#__PURE__*/ forwardRef(function ModalClose(props, ref) {
205
+ const { setOpen } = useModalContext();
206
+ const { trigger, className, ...rest } = props;
207
+ const handleClose = useCallback(()=>setOpen(false), [
208
+ setOpen
209
+ ]);
210
+ return /*#__PURE__*/ jsx("div", {
211
+ className: className,
212
+ children: /*#__PURE__*/ cloneElement(trigger, {
213
+ ref,
214
+ onClick: handleClose,
215
+ ...rest
216
+ })
217
+ });
218
+ });
219
+
31
220
  ;// CONCATENATED MODULE: ./src/components/Panel/utilities.ts
32
221
 
33
222
 
@@ -178,18 +367,18 @@ const Panel = ({ open, onOpenChange, title, children, footer, borderMode = "ligh
178
367
  open: open,
179
368
  onOpenChange: onOpenChange,
180
369
  initialFocus: initialFocus,
181
- children: /*#__PURE__*/ jsx(ModalContent, {
370
+ children: /*#__PURE__*/ jsx(Modal_ModalContent, {
182
371
  className: panelClassName.outerWrapper,
183
372
  style: {
184
373
  ...animationStyles
185
374
  },
186
- children: /*#__PURE__*/ jsxs(ModalDescription, {
375
+ children: /*#__PURE__*/ jsxs(Modal_ModalDescription, {
187
376
  className: panelClassName.innerWrapper,
188
377
  children: [
189
378
  /*#__PURE__*/ jsxs("div", {
190
379
  className: panelClassName.header,
191
380
  children: [
192
- /*#__PURE__*/ jsx(ModalClose, {
381
+ /*#__PURE__*/ jsx(Modal_ModalClose, {
193
382
  className: panelClassName.closeWrapper,
194
383
  trigger: /*#__PURE__*/ jsx("button", {
195
384
  className: panelClassName.closeButton,
@@ -212,7 +401,7 @@ const Panel = ({ open, onOpenChange, title, children, footer, borderMode = "ligh
212
401
  })
213
402
  })
214
403
  }),
215
- /*#__PURE__*/ jsx(ModalHeading, {
404
+ /*#__PURE__*/ jsx(Modal_ModalHeading, {
216
405
  className: panelClassName.title,
217
406
  children: title
218
407
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@versini/ui-panel",
3
- "version": "8.1.4",
3
+ "version": "9.0.0",
4
4
  "license": "MIT",
5
5
  "author": "Arno Versini",
6
6
  "publishConfig": {
@@ -42,19 +42,19 @@
42
42
  "test": "vitest run"
43
43
  },
44
44
  "devDependencies": {
45
- "@testing-library/jest-dom": "6.9.1"
45
+ "@testing-library/jest-dom": "6.9.1",
46
+ "@versini/ui-types": "8.1.1"
46
47
  },
47
48
  "dependencies": {
48
49
  "@tailwindcss/typography": "0.5.19",
49
- "@versini/ui-modal": "3.3.3",
50
50
  "clsx": "2.1.1",
51
51
  "tailwindcss": "4.1.18"
52
52
  },
53
53
  "peerDependencies": {
54
- "@floating-ui/react": ">=0.27.16"
54
+ "@floating-ui/react": "0.27.16"
55
55
  },
56
56
  "sideEffects": [
57
57
  "**/*.css"
58
58
  ],
59
- "gitHead": "9fedab179e688f845d193b1f0b5365639dd4beb0"
59
+ "gitHead": "6c08c752fd230a10f88c1bf315be99d714d55b08"
60
60
  }