@crisp-ui-kit/crisp 0.39.0 → 0.40.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.
- package/dist/cjs/components/dialog/dialog.js +12 -0
- package/dist/cjs/components/drawer/drawer.js +7 -0
- package/dist/cjs/core/floating-stack.js +21 -0
- package/dist/cjs/core/index.js +2 -1
- package/dist/cjs/product/recordpanel/recordpanel.js +13 -2
- package/dist/esm/components/dialog/dialog.js +13 -1
- package/dist/esm/components/drawer/drawer.js +8 -1
- package/dist/esm/core/floating-stack.js +20 -0
- package/dist/esm/core/index.js +1 -1
- package/dist/esm/product/recordpanel/recordpanel.js +13 -2
- package/dist/types/core/floating-stack.d.ts +18 -0
- package/dist/types/core/index.d.ts +1 -1
- package/package.json +1 -1
|
@@ -72,6 +72,18 @@ function Dialog({ open, onOpenChange, title, icon, width = 640, "aria-label": ar
|
|
|
72
72
|
// search), rather than the close button; fall back to the machine default.
|
|
73
73
|
initialFocusEl: () => document.querySelector(`[data-crisp-dialog="${id}"] .crisp-dialog-body :where(input, textarea, select, [contenteditable]):not([disabled])`),
|
|
74
74
|
"aria-label": typeof title === "string" ? title : ariaLabel,
|
|
75
|
+
// Defer to a nested Menu/Select/Popover/ContextMenu's own Escape
|
|
76
|
+
// handling (#361): none of those five are Zag machines, so this
|
|
77
|
+
// machine's own dismissable-layer stack has no visibility into them --
|
|
78
|
+
// pressing Escape to close one opened from inside this Dialog closed
|
|
79
|
+
// the Dialog too, since both listen for the same keypress independently.
|
|
80
|
+
// Zag only calls its own dismiss when the event isn't already
|
|
81
|
+
// prevented (`@zag-js/dismissable`), so this cleanly defers instead of
|
|
82
|
+
// racing it.
|
|
83
|
+
onEscapeKeyDown(event) {
|
|
84
|
+
if ((0, core_1.hasOpenFloatingLayer)())
|
|
85
|
+
event.preventDefault();
|
|
86
|
+
},
|
|
75
87
|
});
|
|
76
88
|
const api = dialog.connect(service, react_1.normalizeProps);
|
|
77
89
|
if (!api.open)
|
|
@@ -66,6 +66,13 @@ function Drawer({ open, onOpenChange, side = "right", title, icon, "aria-label":
|
|
|
66
66
|
onOpenChange(details.open);
|
|
67
67
|
},
|
|
68
68
|
"aria-label": typeof title === "string" ? title : ariaLabel,
|
|
69
|
+
// Defer to a nested Menu/Select/Popover/ContextMenu's own Escape
|
|
70
|
+
// handling (#361) -- see Dialog's identical comment; this composes the
|
|
71
|
+
// same Zag machine.
|
|
72
|
+
onEscapeKeyDown(event) {
|
|
73
|
+
if ((0, core_1.hasOpenFloatingLayer)())
|
|
74
|
+
event.preventDefault();
|
|
75
|
+
},
|
|
69
76
|
});
|
|
70
77
|
const api = dialog.connect(service, react_1.normalizeProps);
|
|
71
78
|
if (!api.open)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.registerFloatingLayer = registerFloatingLayer;
|
|
4
4
|
exports.isInsideLaterFloatingLayer = isInsideLaterFloatingLayer;
|
|
5
|
+
exports.hasOpenFloatingLayer = hasOpenFloatingLayer;
|
|
5
6
|
/**
|
|
6
7
|
* Tracks every currently-open `FloatingPortal` layer, in open order, so a
|
|
7
8
|
* layer's outside-click check can tell "outside me" from "inside a layer I
|
|
@@ -52,3 +53,23 @@ function isInsideLaterFloatingLayer(target, self) {
|
|
|
52
53
|
}
|
|
53
54
|
return false;
|
|
54
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* True when at least one `Select`/`MultiSelect`/`Menu`/`Popover`/
|
|
58
|
+
* `ContextMenu` is currently open, anywhere (#361).
|
|
59
|
+
*
|
|
60
|
+
* A dismissable container that closes on Escape via its OWN raw keydown
|
|
61
|
+
* listener or a library's dismissable-layer machine (`RecordPanel`,
|
|
62
|
+
* `Dialog`, `Drawer`) has no visibility into these five: none of them are
|
|
63
|
+
* built on the same machine, so a Menu opened from inside a `Dialog`
|
|
64
|
+
* registers here, but the Dialog's own Escape handling doesn't know to
|
|
65
|
+
* check for it. Both listeners fire on the same Escape keydown, so both
|
|
66
|
+
* close — the outer container closing was never the point of that
|
|
67
|
+
* keypress, which was meant for the nested layer alone. Call this before
|
|
68
|
+
* closing on Escape (or in a dismissable-layer's `onEscapeKeyDown`, calling
|
|
69
|
+
* `event.preventDefault()` when it's true) to defer instead: the nested
|
|
70
|
+
* layer's own listener still runs and closes it, and the outer container
|
|
71
|
+
* gets its own, uncontested Escape on the next press.
|
|
72
|
+
*/
|
|
73
|
+
function hasOpenFloatingLayer() {
|
|
74
|
+
return stack.length > 0;
|
|
75
|
+
}
|
package/dist/cjs/core/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.usePrefersReducedMotion = exports.useLabelAssociation = exports.useFormReset = exports.useFocusAfterRemoval = exports.specToRecipe = exports.defineSpec = exports.resolveRecipe = exports.Primitive = exports.placeFloating = exports.formValueStyle = exports.registerFloatingLayer = exports.isInsideLaterFloatingLayer = exports.FloatingPortal = exports.isRtlElement = exports.withDescribedBy = exports.defineRecipe = exports.cx = exports.parseCellDate = exports.formatCellDate = void 0;
|
|
17
|
+
exports.usePrefersReducedMotion = exports.useLabelAssociation = exports.useFormReset = exports.useFocusAfterRemoval = exports.specToRecipe = exports.defineSpec = exports.resolveRecipe = exports.Primitive = exports.placeFloating = exports.formValueStyle = exports.registerFloatingLayer = exports.isInsideLaterFloatingLayer = exports.hasOpenFloatingLayer = exports.FloatingPortal = exports.isRtlElement = exports.withDescribedBy = exports.defineRecipe = exports.cx = exports.parseCellDate = exports.formatCellDate = void 0;
|
|
18
18
|
var cell_date_1 = require("./cell-date");
|
|
19
19
|
Object.defineProperty(exports, "formatCellDate", { enumerable: true, get: function () { return cell_date_1.formatCellDate; } });
|
|
20
20
|
Object.defineProperty(exports, "parseCellDate", { enumerable: true, get: function () { return cell_date_1.parseCellDate; } });
|
|
@@ -29,6 +29,7 @@ Object.defineProperty(exports, "isRtlElement", { enumerable: true, get: function
|
|
|
29
29
|
var floating_portal_1 = require("./floating-portal");
|
|
30
30
|
Object.defineProperty(exports, "FloatingPortal", { enumerable: true, get: function () { return floating_portal_1.FloatingPortal; } });
|
|
31
31
|
var floating_stack_1 = require("./floating-stack");
|
|
32
|
+
Object.defineProperty(exports, "hasOpenFloatingLayer", { enumerable: true, get: function () { return floating_stack_1.hasOpenFloatingLayer; } });
|
|
32
33
|
Object.defineProperty(exports, "isInsideLaterFloatingLayer", { enumerable: true, get: function () { return floating_stack_1.isInsideLaterFloatingLayer; } });
|
|
33
34
|
Object.defineProperty(exports, "registerFloatingLayer", { enumerable: true, get: function () { return floating_stack_1.registerFloatingLayer; } });
|
|
34
35
|
var form_value_1 = require("./form-value");
|
|
@@ -38,6 +38,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
38
38
|
const lucide_react_1 = require("lucide-react");
|
|
39
39
|
const React = __importStar(require("react"));
|
|
40
40
|
const avatar_1 = require("../../components/avatar");
|
|
41
|
+
const core_1 = require("../../core");
|
|
41
42
|
const recordstable_1 = require("../recordstable");
|
|
42
43
|
const recordpanel_recipe_1 = require("./recordpanel.recipe");
|
|
43
44
|
function FieldValue({ col, row, emptyLabel, }) {
|
|
@@ -64,11 +65,21 @@ function FieldValue({ col, row, emptyLabel, }) {
|
|
|
64
65
|
*/
|
|
65
66
|
function RecordPanel({ record, columns, onClose, className, closeLabel = "Close panel", title, detailsLabel = "Details", emptyLabel = "Empty", footer, ...rest }) {
|
|
66
67
|
const titleId = React.useId();
|
|
67
|
-
// close on Escape while open
|
|
68
|
+
// close on Escape while open -- but not when a Menu/Select/Popover/
|
|
69
|
+
// ContextMenu opened from inside the panel (e.g. a footer "More actions"
|
|
70
|
+
// menu) is itself open (#361): that Escape belongs to the nested layer,
|
|
71
|
+
// which has its own listener and will close itself. Without this check
|
|
72
|
+
// both close on the same keypress, since neither knows about the other --
|
|
73
|
+
// none of the five are built on this listener or registered anywhere it
|
|
74
|
+
// can see, only on the shared floating-stack this checks instead.
|
|
68
75
|
React.useEffect(() => {
|
|
69
76
|
if (!record)
|
|
70
77
|
return;
|
|
71
|
-
const onKey = (e) =>
|
|
78
|
+
const onKey = (e) => {
|
|
79
|
+
if (e.key !== "Escape" || (0, core_1.hasOpenFloatingLayer)())
|
|
80
|
+
return;
|
|
81
|
+
onClose();
|
|
82
|
+
};
|
|
72
83
|
document.addEventListener("keydown", onKey);
|
|
73
84
|
return () => document.removeEventListener("keydown", onKey);
|
|
74
85
|
}, [record, onClose]);
|
|
@@ -3,7 +3,7 @@ import * as dialog from "@zag-js/dialog";
|
|
|
3
3
|
import { normalizeProps, Portal, useMachine } from "@zag-js/react";
|
|
4
4
|
import { X } from "lucide-react";
|
|
5
5
|
import * as React from "react";
|
|
6
|
-
import { cx, withDescribedBy } from "../../core/index.js";
|
|
6
|
+
import { cx, hasOpenFloatingLayer, withDescribedBy } from "../../core/index.js";
|
|
7
7
|
import { dialogRecipe } from "./dialog.recipe.js";
|
|
8
8
|
// A globally unique id per mounted instance (see command.tsx for the rationale —
|
|
9
9
|
// the machine defers its dismissal listeners a frame, so tree-relative useId
|
|
@@ -34,6 +34,18 @@ export function Dialog({ open, onOpenChange, title, icon, width = 640, "aria-lab
|
|
|
34
34
|
// search), rather than the close button; fall back to the machine default.
|
|
35
35
|
initialFocusEl: () => document.querySelector(`[data-crisp-dialog="${id}"] .crisp-dialog-body :where(input, textarea, select, [contenteditable]):not([disabled])`),
|
|
36
36
|
"aria-label": typeof title === "string" ? title : ariaLabel,
|
|
37
|
+
// Defer to a nested Menu/Select/Popover/ContextMenu's own Escape
|
|
38
|
+
// handling (#361): none of those five are Zag machines, so this
|
|
39
|
+
// machine's own dismissable-layer stack has no visibility into them --
|
|
40
|
+
// pressing Escape to close one opened from inside this Dialog closed
|
|
41
|
+
// the Dialog too, since both listen for the same keypress independently.
|
|
42
|
+
// Zag only calls its own dismiss when the event isn't already
|
|
43
|
+
// prevented (`@zag-js/dismissable`), so this cleanly defers instead of
|
|
44
|
+
// racing it.
|
|
45
|
+
onEscapeKeyDown(event) {
|
|
46
|
+
if (hasOpenFloatingLayer())
|
|
47
|
+
event.preventDefault();
|
|
48
|
+
},
|
|
37
49
|
});
|
|
38
50
|
const api = dialog.connect(service, normalizeProps);
|
|
39
51
|
if (!api.open)
|
|
@@ -3,7 +3,7 @@ import * as dialog from "@zag-js/dialog";
|
|
|
3
3
|
import { normalizeProps, Portal, useMachine } from "@zag-js/react";
|
|
4
4
|
import { X } from "lucide-react";
|
|
5
5
|
import * as React from "react";
|
|
6
|
-
import { cx, withDescribedBy } from "../../core/index.js";
|
|
6
|
+
import { cx, hasOpenFloatingLayer, withDescribedBy } from "../../core/index.js";
|
|
7
7
|
import { drawerRecipe } from "./drawer.recipe.js";
|
|
8
8
|
let instanceCounter = 0;
|
|
9
9
|
function useDrawerInstanceId() {
|
|
@@ -28,6 +28,13 @@ export function Drawer({ open, onOpenChange, side = "right", title, icon, "aria-
|
|
|
28
28
|
onOpenChange(details.open);
|
|
29
29
|
},
|
|
30
30
|
"aria-label": typeof title === "string" ? title : ariaLabel,
|
|
31
|
+
// Defer to a nested Menu/Select/Popover/ContextMenu's own Escape
|
|
32
|
+
// handling (#361) -- see Dialog's identical comment; this composes the
|
|
33
|
+
// same Zag machine.
|
|
34
|
+
onEscapeKeyDown(event) {
|
|
35
|
+
if (hasOpenFloatingLayer())
|
|
36
|
+
event.preventDefault();
|
|
37
|
+
},
|
|
31
38
|
});
|
|
32
39
|
const api = dialog.connect(service, normalizeProps);
|
|
33
40
|
if (!api.open)
|
|
@@ -48,3 +48,23 @@ export function isInsideLaterFloatingLayer(target, self) {
|
|
|
48
48
|
}
|
|
49
49
|
return false;
|
|
50
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* True when at least one `Select`/`MultiSelect`/`Menu`/`Popover`/
|
|
53
|
+
* `ContextMenu` is currently open, anywhere (#361).
|
|
54
|
+
*
|
|
55
|
+
* A dismissable container that closes on Escape via its OWN raw keydown
|
|
56
|
+
* listener or a library's dismissable-layer machine (`RecordPanel`,
|
|
57
|
+
* `Dialog`, `Drawer`) has no visibility into these five: none of them are
|
|
58
|
+
* built on the same machine, so a Menu opened from inside a `Dialog`
|
|
59
|
+
* registers here, but the Dialog's own Escape handling doesn't know to
|
|
60
|
+
* check for it. Both listeners fire on the same Escape keydown, so both
|
|
61
|
+
* close — the outer container closing was never the point of that
|
|
62
|
+
* keypress, which was meant for the nested layer alone. Call this before
|
|
63
|
+
* closing on Escape (or in a dismissable-layer's `onEscapeKeyDown`, calling
|
|
64
|
+
* `event.preventDefault()` when it's true) to defer instead: the nested
|
|
65
|
+
* layer's own listener still runs and closes it, and the outer container
|
|
66
|
+
* gets its own, uncontested Escape on the next press.
|
|
67
|
+
*/
|
|
68
|
+
export function hasOpenFloatingLayer() {
|
|
69
|
+
return stack.length > 0;
|
|
70
|
+
}
|
package/dist/esm/core/index.js
CHANGED
|
@@ -4,7 +4,7 @@ export { defineRecipe } from "./define.js";
|
|
|
4
4
|
export { withDescribedBy } from "./dialog-description.js";
|
|
5
5
|
export { isRtlElement } from "./direction.js";
|
|
6
6
|
export { FloatingPortal } from "./floating-portal.js";
|
|
7
|
-
export { isInsideLaterFloatingLayer, registerFloatingLayer, } from "./floating-stack.js";
|
|
7
|
+
export { hasOpenFloatingLayer, isInsideLaterFloatingLayer, registerFloatingLayer, } from "./floating-stack.js";
|
|
8
8
|
export { formValueStyle } from "./form-value.js";
|
|
9
9
|
export { placeFloating, } from "./place-floating.js";
|
|
10
10
|
export { Primitive } from "./primitive.js";
|
|
@@ -2,6 +2,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { X } from "lucide-react";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { Avatar } from "../../components/avatar/index.js";
|
|
5
|
+
import { hasOpenFloatingLayer } from "../../core/index.js";
|
|
5
6
|
import { tagTone, } from "../recordstable/index.js";
|
|
6
7
|
import { recordpanelRecipe } from "./recordpanel.recipe.js";
|
|
7
8
|
function FieldValue({ col, row, emptyLabel, }) {
|
|
@@ -28,11 +29,21 @@ function FieldValue({ col, row, emptyLabel, }) {
|
|
|
28
29
|
*/
|
|
29
30
|
export function RecordPanel({ record, columns, onClose, className, closeLabel = "Close panel", title, detailsLabel = "Details", emptyLabel = "Empty", footer, ...rest }) {
|
|
30
31
|
const titleId = React.useId();
|
|
31
|
-
// close on Escape while open
|
|
32
|
+
// close on Escape while open -- but not when a Menu/Select/Popover/
|
|
33
|
+
// ContextMenu opened from inside the panel (e.g. a footer "More actions"
|
|
34
|
+
// menu) is itself open (#361): that Escape belongs to the nested layer,
|
|
35
|
+
// which has its own listener and will close itself. Without this check
|
|
36
|
+
// both close on the same keypress, since neither knows about the other --
|
|
37
|
+
// none of the five are built on this listener or registered anywhere it
|
|
38
|
+
// can see, only on the shared floating-stack this checks instead.
|
|
32
39
|
React.useEffect(() => {
|
|
33
40
|
if (!record)
|
|
34
41
|
return;
|
|
35
|
-
const onKey = (e) =>
|
|
42
|
+
const onKey = (e) => {
|
|
43
|
+
if (e.key !== "Escape" || hasOpenFloatingLayer())
|
|
44
|
+
return;
|
|
45
|
+
onClose();
|
|
46
|
+
};
|
|
36
47
|
document.addEventListener("keydown", onKey);
|
|
37
48
|
return () => document.removeEventListener("keydown", onKey);
|
|
38
49
|
}, [record, onClose]);
|
|
@@ -11,3 +11,21 @@ export declare function registerFloatingLayer(el: HTMLElement): () => void;
|
|
|
11
11
|
* right query for "was this click inside some floating child I opened".
|
|
12
12
|
*/
|
|
13
13
|
export declare function isInsideLaterFloatingLayer(target: Node, self: HTMLElement | null): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* True when at least one `Select`/`MultiSelect`/`Menu`/`Popover`/
|
|
16
|
+
* `ContextMenu` is currently open, anywhere (#361).
|
|
17
|
+
*
|
|
18
|
+
* A dismissable container that closes on Escape via its OWN raw keydown
|
|
19
|
+
* listener or a library's dismissable-layer machine (`RecordPanel`,
|
|
20
|
+
* `Dialog`, `Drawer`) has no visibility into these five: none of them are
|
|
21
|
+
* built on the same machine, so a Menu opened from inside a `Dialog`
|
|
22
|
+
* registers here, but the Dialog's own Escape handling doesn't know to
|
|
23
|
+
* check for it. Both listeners fire on the same Escape keydown, so both
|
|
24
|
+
* close — the outer container closing was never the point of that
|
|
25
|
+
* keypress, which was meant for the nested layer alone. Call this before
|
|
26
|
+
* closing on Escape (or in a dismissable-layer's `onEscapeKeyDown`, calling
|
|
27
|
+
* `event.preventDefault()` when it's true) to defer instead: the nested
|
|
28
|
+
* layer's own listener still runs and closes it, and the outer container
|
|
29
|
+
* gets its own, uncontested Escape on the next press.
|
|
30
|
+
*/
|
|
31
|
+
export declare function hasOpenFloatingLayer(): boolean;
|
|
@@ -4,7 +4,7 @@ export { defineRecipe } from "./define";
|
|
|
4
4
|
export { withDescribedBy } from "./dialog-description";
|
|
5
5
|
export { isRtlElement } from "./direction";
|
|
6
6
|
export { FloatingPortal } from "./floating-portal";
|
|
7
|
-
export { isInsideLaterFloatingLayer, registerFloatingLayer, } from "./floating-stack";
|
|
7
|
+
export { hasOpenFloatingLayer, isInsideLaterFloatingLayer, registerFloatingLayer, } from "./floating-stack";
|
|
8
8
|
export { formValueStyle } from "./form-value";
|
|
9
9
|
export { type FloatingAlign, type PlaceFloatingOptions, placeFloating, } from "./place-floating";
|
|
10
10
|
export { Primitive } from "./primitive";
|