@bsuite/ui 1.2.2 → 1.3.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.
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useOnClickOutside
|
|
3
|
+
*
|
|
4
|
+
* Dismisses an open dropdown/menu/popover when the user clicks (or taps)
|
|
5
|
+
* outside of its container, or presses Escape.
|
|
6
|
+
*
|
|
7
|
+
* Why not a full-screen backdrop `<div>`? A `fixed inset-0` backdrop only
|
|
8
|
+
* reliably covers the whole viewport when none of its ancestors establish a
|
|
9
|
+
* new containing block for fixed-position descendants. `backdrop-filter`,
|
|
10
|
+
* `filter`, `transform`, and `will-change: transform` on an ancestor all do
|
|
11
|
+
* exactly that — and BSuite's glass-panel header chrome
|
|
12
|
+
* (`backdropFilter: blur(...)`) is one of them. A backdrop nested inside such
|
|
13
|
+
* an ancestor silently shrinks to that ancestor's box instead of the full
|
|
14
|
+
* page, so clicks on the rest of the page never reach it and the menu never
|
|
15
|
+
* closes. This is what broke click-outside dismissal for crm7's header
|
|
16
|
+
* tenant switchers (operator report, 2026-07-17) — see crm7's
|
|
17
|
+
* `TenantSwitcher.tsx` / `UserTenantSwitcher.tsx` / `CRM7Header.tsx`, the
|
|
18
|
+
* hook this one is promoted from.
|
|
19
|
+
*
|
|
20
|
+
* A `document`-level pointerdown listener sidesteps the problem entirely:
|
|
21
|
+
* JS event listeners are not affected by CSS stacking contexts or
|
|
22
|
+
* containing-block quirks, so this works regardless of what ancestors do.
|
|
23
|
+
*
|
|
24
|
+
* This is the canonical, shared version — promoted here so every consumer
|
|
25
|
+
* app (and every `@bsuite/*` package that depends on `@bsuite/ui`) gets one
|
|
26
|
+
* implementation instead of a fresh hand-rolled copy per app. crm7 carries
|
|
27
|
+
* its own pre-existing copy at `src/hooks/useClickOutside.ts`; that one is
|
|
28
|
+
* already correct and untouched by this promotion — a future consolidation
|
|
29
|
+
* onto this shared copy is a reasonable low-risk follow-up, not a bug fix.
|
|
30
|
+
*
|
|
31
|
+
* Usage:
|
|
32
|
+
* const containerRef = useRef<HTMLDivElement>(null);
|
|
33
|
+
* useOnClickOutside(containerRef, () => setIsOpen(false), isOpen);
|
|
34
|
+
*
|
|
35
|
+
* return (
|
|
36
|
+
* <div ref={containerRef} className="relative">
|
|
37
|
+
* <button onClick={() => setIsOpen((o) => !o)}>...</button>
|
|
38
|
+
* {isOpen && <div className="absolute ...">...menu...</div>}
|
|
39
|
+
* </div>
|
|
40
|
+
* );
|
|
41
|
+
*/
|
|
42
|
+
import { type RefObject } from 'react';
|
|
43
|
+
export declare function useOnClickOutside<T extends HTMLElement>(ref: RefObject<T | null>, handler: () => void, enabled?: boolean): void;
|
|
44
|
+
//# sourceMappingURL=useOnClickOutside.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useOnClickOutside.d.ts","sourceRoot":"","sources":["../../src/hooks/useOnClickOutside.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,OAAO,EAAa,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAEjD,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,WAAW,EACrD,GAAG,EAAE,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,EACxB,OAAO,EAAE,MAAM,IAAI,EACnB,OAAO,GAAE,OAAc,GACtB,IAAI,CAiCN"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useOnClickOutside
|
|
3
|
+
*
|
|
4
|
+
* Dismisses an open dropdown/menu/popover when the user clicks (or taps)
|
|
5
|
+
* outside of its container, or presses Escape.
|
|
6
|
+
*
|
|
7
|
+
* Why not a full-screen backdrop `<div>`? A `fixed inset-0` backdrop only
|
|
8
|
+
* reliably covers the whole viewport when none of its ancestors establish a
|
|
9
|
+
* new containing block for fixed-position descendants. `backdrop-filter`,
|
|
10
|
+
* `filter`, `transform`, and `will-change: transform` on an ancestor all do
|
|
11
|
+
* exactly that — and BSuite's glass-panel header chrome
|
|
12
|
+
* (`backdropFilter: blur(...)`) is one of them. A backdrop nested inside such
|
|
13
|
+
* an ancestor silently shrinks to that ancestor's box instead of the full
|
|
14
|
+
* page, so clicks on the rest of the page never reach it and the menu never
|
|
15
|
+
* closes. This is what broke click-outside dismissal for crm7's header
|
|
16
|
+
* tenant switchers (operator report, 2026-07-17) — see crm7's
|
|
17
|
+
* `TenantSwitcher.tsx` / `UserTenantSwitcher.tsx` / `CRM7Header.tsx`, the
|
|
18
|
+
* hook this one is promoted from.
|
|
19
|
+
*
|
|
20
|
+
* A `document`-level pointerdown listener sidesteps the problem entirely:
|
|
21
|
+
* JS event listeners are not affected by CSS stacking contexts or
|
|
22
|
+
* containing-block quirks, so this works regardless of what ancestors do.
|
|
23
|
+
*
|
|
24
|
+
* This is the canonical, shared version — promoted here so every consumer
|
|
25
|
+
* app (and every `@bsuite/*` package that depends on `@bsuite/ui`) gets one
|
|
26
|
+
* implementation instead of a fresh hand-rolled copy per app. crm7 carries
|
|
27
|
+
* its own pre-existing copy at `src/hooks/useClickOutside.ts`; that one is
|
|
28
|
+
* already correct and untouched by this promotion — a future consolidation
|
|
29
|
+
* onto this shared copy is a reasonable low-risk follow-up, not a bug fix.
|
|
30
|
+
*
|
|
31
|
+
* Usage:
|
|
32
|
+
* const containerRef = useRef<HTMLDivElement>(null);
|
|
33
|
+
* useOnClickOutside(containerRef, () => setIsOpen(false), isOpen);
|
|
34
|
+
*
|
|
35
|
+
* return (
|
|
36
|
+
* <div ref={containerRef} className="relative">
|
|
37
|
+
* <button onClick={() => setIsOpen((o) => !o)}>...</button>
|
|
38
|
+
* {isOpen && <div className="absolute ...">...menu...</div>}
|
|
39
|
+
* </div>
|
|
40
|
+
* );
|
|
41
|
+
*/
|
|
42
|
+
import { useEffect } from 'react';
|
|
43
|
+
export function useOnClickOutside(ref, handler, enabled = true) {
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
// Only attach listeners while the menu is actually open — avoids
|
|
46
|
+
// unnecessary global listeners on every page.
|
|
47
|
+
if (!enabled)
|
|
48
|
+
return;
|
|
49
|
+
const handlePointerDown = (event) => {
|
|
50
|
+
const container = ref.current;
|
|
51
|
+
if (!container)
|
|
52
|
+
return;
|
|
53
|
+
if (event.target instanceof Node && container.contains(event.target)) {
|
|
54
|
+
// Click landed inside the container (trigger button or menu body) —
|
|
55
|
+
// let the component's own click handlers deal with it.
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
handler();
|
|
59
|
+
};
|
|
60
|
+
const handleKeyDown = (event) => {
|
|
61
|
+
if (event.key === 'Escape')
|
|
62
|
+
handler();
|
|
63
|
+
};
|
|
64
|
+
// `pointerdown` fires before `click`, so the menu is already closed by
|
|
65
|
+
// the time a click on an element behind it fires — clicking "through"
|
|
66
|
+
// to another control outside the menu both closes the menu AND performs
|
|
67
|
+
// that control's own action, matching standard menu/popover UX.
|
|
68
|
+
document.addEventListener('pointerdown', handlePointerDown);
|
|
69
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
70
|
+
return () => {
|
|
71
|
+
document.removeEventListener('pointerdown', handlePointerDown);
|
|
72
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
73
|
+
};
|
|
74
|
+
}, [ref, handler, enabled]);
|
|
75
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export { LoadingSpinner, type LoadingSpinnerProps } from './loading-spinner.js';
|
|
|
13
13
|
export { StatusBadge, type StatusBadgeProps, type StatusBadgeTone } from './status-badge.js';
|
|
14
14
|
export { Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, } from './command.js';
|
|
15
15
|
export { Popover, PopoverTrigger, PopoverContent } from './popover.js';
|
|
16
|
+
export { useOnClickOutside } from './hooks/useOnClickOutside.js';
|
|
16
17
|
export { EntitySelector, type EntitySelectorProps, type EntitySelectorQuery, type EntitySelectorLogger, type EntitySelectorSupabaseClient, } from './entity-selector.js';
|
|
17
18
|
export { BrandingCard, ColorEditorSheet, OklchColorPicker, type BrandingCardProps, type ColorEditorSheetProps, type OklchColorPickerProps, } from './branding/index.js';
|
|
18
19
|
export { sanitizeCustomCss } from './branding/sanitizeCustomCss.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACnE,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAC7D,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAC7D,OAAO,EAAE,EAAE,EAAE,MAAM,YAAY,CAAA;AAC/B,OAAO,EACL,IAAI,EACJ,cAAc,EACd,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,OAAO,GACb,MAAM,WAAW,CAAA;AAClB,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAC5E,OAAO,EACL,MAAM,EACN,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,aAAa,GACnB,MAAM,aAAa,CAAA;AACpB,OAAO,EACL,MAAM,EACN,WAAW,EACX,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,WAAW,EACX,aAAa,EACb,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACnE,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,SAAS,GACf,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAC5E,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAC/E,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAC5F,OAAO,EACL,OAAO,EACP,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,eAAe,GAChB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AACtE,OAAO,EACL,cAAc,EACd,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,4BAA4B,GAClC,MAAM,sBAAsB,CAAA;AAG7B,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACnE,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAC7D,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAC7D,OAAO,EAAE,EAAE,EAAE,MAAM,YAAY,CAAA;AAC/B,OAAO,EACL,IAAI,EACJ,cAAc,EACd,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,OAAO,GACb,MAAM,WAAW,CAAA;AAClB,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAC5E,OAAO,EACL,MAAM,EACN,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,aAAa,GACnB,MAAM,aAAa,CAAA;AACpB,OAAO,EACL,MAAM,EACN,WAAW,EACX,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,WAAW,EACX,aAAa,EACb,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACnE,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,SAAS,GACf,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAC5E,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAC/E,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAC5F,OAAO,EACL,OAAO,EACP,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,eAAe,GAChB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AACtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAA;AAChE,OAAO,EACL,cAAc,EACd,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,4BAA4B,GAClC,MAAM,sBAAsB,CAAA;AAG7B,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ export { LoadingSpinner } from './loading-spinner.js';
|
|
|
13
13
|
export { StatusBadge } from './status-badge.js';
|
|
14
14
|
export { Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, } from './command.js';
|
|
15
15
|
export { Popover, PopoverTrigger, PopoverContent } from './popover.js';
|
|
16
|
+
export { useOnClickOutside } from './hooks/useOnClickOutside.js';
|
|
16
17
|
export { EntitySelector, } from './entity-selector.js';
|
|
17
18
|
// Branding components (v0.3.0+)
|
|
18
19
|
export { BrandingCard, ColorEditorSheet, OklchColorPicker, } from './branding/index.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bsuite/ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"publishConfig": {
|
|
@@ -79,6 +79,10 @@
|
|
|
79
79
|
"import": "./dist/entity-selector.js",
|
|
80
80
|
"types": "./dist/entity-selector.d.ts"
|
|
81
81
|
},
|
|
82
|
+
"./use-on-click-outside": {
|
|
83
|
+
"import": "./dist/hooks/useOnClickOutside.js",
|
|
84
|
+
"types": "./dist/hooks/useOnClickOutside.d.ts"
|
|
85
|
+
},
|
|
82
86
|
"./assets/d2c-default-logo.svg": "./src/assets/d2c-default-logo.svg"
|
|
83
87
|
},
|
|
84
88
|
"scripts": {
|