@carlonicora/nextjs-jsonapi 1.130.0 → 1.131.1
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/dist/{BlockNoteEditor-AI5C4PBZ.js → BlockNoteEditor-BR3IRGJW.js} +9 -9
- package/dist/{BlockNoteEditor-AI5C4PBZ.js.map → BlockNoteEditor-BR3IRGJW.js.map} +1 -1
- package/dist/{BlockNoteEditor-KARBUMFE.mjs → BlockNoteEditor-PVBZYPMF.mjs} +2 -2
- package/dist/billing/index.js +310 -310
- package/dist/billing/index.mjs +1 -1
- package/dist/{chunk-BHZVFSDR.js → chunk-5SQFK35K.js} +45 -30
- package/dist/chunk-5SQFK35K.js.map +1 -0
- package/dist/{chunk-NXHNJCY2.mjs → chunk-MDXT47V4.mjs} +45 -30
- package/dist/chunk-MDXT47V4.mjs.map +1 -0
- package/dist/client/index.js +2 -2
- package/dist/client/index.mjs +1 -1
- package/dist/components/index.js +2 -2
- package/dist/components/index.mjs +1 -1
- package/dist/contexts/index.d.mts +2 -0
- package/dist/contexts/index.d.ts +2 -0
- package/dist/contexts/index.js +2 -2
- package/dist/contexts/index.mjs +1 -1
- package/dist/features/help/index.js +31 -31
- package/dist/features/help/index.mjs +1 -1
- package/package.json +1 -1
- package/src/components/containers/RoundPageContainerTitle.tsx +47 -40
- package/src/components/forms/__tests__/useEditorDialog.test.ts +66 -0
- package/src/components/forms/useEditorDialog.ts +25 -0
- package/src/contexts/SharedContext.tsx +2 -0
- package/src/shadcnui/ui/sheet.tsx +7 -1
- package/dist/chunk-BHZVFSDR.js.map +0 -1
- package/dist/chunk-NXHNJCY2.mjs.map +0 -1
- /package/dist/{BlockNoteEditor-KARBUMFE.mjs.map → BlockNoteEditor-PVBZYPMF.mjs.map} +0 -0
|
@@ -168,4 +168,70 @@ describe("useEditorDialog", () => {
|
|
|
168
168
|
expect(result.current.discardDialogProps.open).toBe(false);
|
|
169
169
|
});
|
|
170
170
|
});
|
|
171
|
+
|
|
172
|
+
describe("escape key with nested editors", () => {
|
|
173
|
+
const pressEscape = () =>
|
|
174
|
+
act(() => {
|
|
175
|
+
document.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", bubbles: true, cancelable: true }));
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("should close only the topmost editor, leaving the parent open", () => {
|
|
179
|
+
// Each open editor adds its OWN document-level capture listener, and
|
|
180
|
+
// stopPropagation() does not suppress sibling listeners on the same node.
|
|
181
|
+
// Without a stack guard one Escape closes the parent too — e.g. a nested
|
|
182
|
+
// create dialog takes the editor that opened it down with it.
|
|
183
|
+
const outer = renderHook(() => useEditorDialog(() => false));
|
|
184
|
+
act(() => outer.result.current.setOpen(true));
|
|
185
|
+
|
|
186
|
+
const inner = renderHook(() => useEditorDialog(() => false));
|
|
187
|
+
act(() => inner.result.current.setOpen(true));
|
|
188
|
+
|
|
189
|
+
pressEscape();
|
|
190
|
+
|
|
191
|
+
expect(inner.result.current.open).toBe(false);
|
|
192
|
+
expect(outer.result.current.open).toBe(true);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("should close the parent on a second Escape once the nested editor has closed", () => {
|
|
196
|
+
const outer = renderHook(() => useEditorDialog(() => false));
|
|
197
|
+
act(() => outer.result.current.setOpen(true));
|
|
198
|
+
|
|
199
|
+
const inner = renderHook(() => useEditorDialog(() => false));
|
|
200
|
+
act(() => inner.result.current.setOpen(true));
|
|
201
|
+
|
|
202
|
+
pressEscape();
|
|
203
|
+
pressEscape();
|
|
204
|
+
|
|
205
|
+
expect(outer.result.current.open).toBe(false);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("should hand Escape back to the parent when the nested editor unmounts while open", () => {
|
|
209
|
+
const outer = renderHook(() => useEditorDialog(() => false));
|
|
210
|
+
act(() => outer.result.current.setOpen(true));
|
|
211
|
+
|
|
212
|
+
const inner = renderHook(() => useEditorDialog(() => false));
|
|
213
|
+
act(() => inner.result.current.setOpen(true));
|
|
214
|
+
|
|
215
|
+
// The nested editor is torn down without closing first (parent stopped
|
|
216
|
+
// rendering it). Its stack entry must not strand Escape forever.
|
|
217
|
+
inner.unmount();
|
|
218
|
+
pressEscape();
|
|
219
|
+
|
|
220
|
+
expect(outer.result.current.open).toBe(false);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("should still show the discard prompt for the topmost editor when it is dirty", () => {
|
|
224
|
+
const outer = renderHook(() => useEditorDialog(() => false));
|
|
225
|
+
act(() => outer.result.current.setOpen(true));
|
|
226
|
+
|
|
227
|
+
const inner = renderHook(() => useEditorDialog(() => true));
|
|
228
|
+
act(() => inner.result.current.setOpen(true));
|
|
229
|
+
|
|
230
|
+
pressEscape();
|
|
231
|
+
|
|
232
|
+
expect(inner.result.current.discardDialogProps.open).toBe(true);
|
|
233
|
+
expect(inner.result.current.open).toBe(true);
|
|
234
|
+
expect(outer.result.current.open).toBe(true);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
171
237
|
});
|
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
4
|
|
|
5
|
+
// Every OPEN editor dialog, in the order it opened — the last entry is the
|
|
6
|
+
// topmost. Escape has to consult this because each open dialog attaches its own
|
|
7
|
+
// `keydown` listener to `document`: listeners on the SAME node all run
|
|
8
|
+
// regardless of stopPropagation() (only stopImmediatePropagation() would stop
|
|
9
|
+
// them, and that would close the wrong one — the parent registers first). So
|
|
10
|
+
// without this stack a single Escape closes a nested create dialog AND the
|
|
11
|
+
// editor that opened it.
|
|
12
|
+
const openEditorStack: object[] = [];
|
|
13
|
+
|
|
5
14
|
type UseEditorDialogOptions = {
|
|
6
15
|
dialogOpen?: boolean;
|
|
7
16
|
onDialogOpenChange?: (open: boolean) => void;
|
|
@@ -87,10 +96,26 @@ export function useEditorDialog(isFormDirty: () => boolean, options?: UseEditorD
|
|
|
87
96
|
[isFormDirty],
|
|
88
97
|
);
|
|
89
98
|
|
|
99
|
+
// Identity of this dialog within `openEditorStack`.
|
|
100
|
+
const dialogId = useRef<object>({});
|
|
101
|
+
|
|
102
|
+
// Join the stack while open. The cleanup also covers unmount-while-open, so a
|
|
103
|
+
// dialog whose parent stops rendering it cannot strand Escape at the top.
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (!open) return;
|
|
106
|
+
const id = dialogId.current;
|
|
107
|
+
openEditorStack.push(id);
|
|
108
|
+
return () => {
|
|
109
|
+
const index = openEditorStack.lastIndexOf(id);
|
|
110
|
+
if (index !== -1) openEditorStack.splice(index, 1);
|
|
111
|
+
};
|
|
112
|
+
}, [open]);
|
|
113
|
+
|
|
90
114
|
// Escape key handler
|
|
91
115
|
useEffect(() => {
|
|
92
116
|
const handleKeyDown = (event: KeyboardEvent) => {
|
|
93
117
|
if (event.key === "Escape" && open) {
|
|
118
|
+
if (openEditorStack[openEditorStack.length - 1] !== dialogId.current) return;
|
|
94
119
|
event.preventDefault();
|
|
95
120
|
event.stopPropagation();
|
|
96
121
|
handleOpenChange(false);
|
|
@@ -12,6 +12,7 @@ const SharedContext = createContext<{
|
|
|
12
12
|
titleActions?: ReactNode;
|
|
13
13
|
prioritizeFunctions?: boolean;
|
|
14
14
|
icon?: ReactNode;
|
|
15
|
+
actionBar?: ReactNode;
|
|
15
16
|
};
|
|
16
17
|
} | null>(null);
|
|
17
18
|
|
|
@@ -26,6 +27,7 @@ interface SharedProviderProps {
|
|
|
26
27
|
titleActions?: ReactNode;
|
|
27
28
|
prioritizeFunctions?: boolean;
|
|
28
29
|
icon?: ReactNode;
|
|
30
|
+
actionBar?: ReactNode;
|
|
29
31
|
};
|
|
30
32
|
};
|
|
31
33
|
}
|
|
@@ -48,7 +48,13 @@ function SheetContent({
|
|
|
48
48
|
}) {
|
|
49
49
|
return (
|
|
50
50
|
<SheetPortal>
|
|
51
|
-
|
|
51
|
+
{/* forceRender: Base UI skips the backdrop entirely for a NESTED dialog
|
|
52
|
+
(`enabled: forceRender || !nested`). Without it a sheet opened from
|
|
53
|
+
inside another sheet has no backdrop, so nothing covers the parent
|
|
54
|
+
popup — which Base UI has marked inert. Clicks there are swallowed by
|
|
55
|
+
`inert` and never reach the dismiss logic, so the nested sheet cannot
|
|
56
|
+
be closed by clicking outside it. Non-nested sheets are unaffected. */}
|
|
57
|
+
<SheetOverlay forceRender />
|
|
52
58
|
<SheetPrimitive.Popup
|
|
53
59
|
data-slot="sheet-content"
|
|
54
60
|
data-side={side}
|