@tamagui/dismissable 2.0.0-rc.8 → 2.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.
- package/dist/cjs/Dismissable.cjs +331 -158
- package/dist/cjs/Dismissable.native.js +46 -28
- package/dist/cjs/Dismissable.native.js.map +1 -1
- package/dist/cjs/DismissableProps.cjs +7 -5
- package/dist/cjs/DismissableProps.native.js +7 -5
- package/dist/cjs/DismissableProps.native.js.map +1 -1
- package/dist/cjs/index.cjs +7 -5
- package/dist/cjs/index.native.js +7 -5
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/esm/Dismissable.mjs +295 -129
- package/dist/esm/Dismissable.mjs.map +1 -1
- package/dist/esm/Dismissable.native.js +18 -6
- package/dist/esm/Dismissable.native.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -6
- package/dist/jsx/Dismissable.mjs +295 -129
- package/dist/jsx/Dismissable.mjs.map +1 -1
- package/dist/jsx/Dismissable.native.js +46 -28
- package/dist/jsx/Dismissable.native.js.map +1 -1
- package/dist/jsx/DismissableProps.native.js +7 -5
- package/dist/jsx/index.js +1 -1
- package/dist/jsx/index.js.map +1 -6
- package/dist/jsx/index.native.js +7 -5
- package/package.json +10 -13
- package/src/Dismissable.native.tsx +21 -1
- package/src/Dismissable.tsx +187 -33
- package/src/DismissableProps.tsx +10 -0
- package/types/Dismissable.d.ts +28 -2
- package/types/Dismissable.d.ts.map +1 -1
- package/types/Dismissable.native.d.ts +4 -0
- package/types/Dismissable.native.d.ts.map +1 -1
- package/types/DismissableProps.d.ts +10 -0
- package/types/DismissableProps.d.ts.map +1 -1
- package/dist/cjs/Dismissable.js +0 -179
- package/dist/cjs/Dismissable.js.map +0 -6
- package/dist/cjs/DismissableProps.js +0 -14
- package/dist/cjs/DismissableProps.js.map +0 -6
- package/dist/cjs/index.js +0 -15
- package/dist/cjs/index.js.map +0 -6
- package/dist/esm/Dismissable.js +0 -161
- package/dist/esm/Dismissable.js.map +0 -6
- package/dist/esm/DismissableProps.js +0 -1
- package/dist/esm/DismissableProps.js.map +0 -6
- package/dist/jsx/Dismissable.js +0 -161
- package/dist/jsx/Dismissable.js.map +0 -6
- package/dist/jsx/DismissableProps.js +0 -1
- package/dist/jsx/DismissableProps.js.map +0 -6
|
@@ -4,46 +4,64 @@ var __create = Object.create;
|
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
5
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
-
var __getProtoOf = Object.getPrototypeOf
|
|
8
|
-
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
9
|
var __export = (target, all) => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
for (var name in all) __defProp(target, name, {
|
|
11
|
+
get: all[name],
|
|
12
|
+
enumerable: true
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
+
for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
17
18
|
get: () => from[key],
|
|
18
19
|
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
19
20
|
});
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
}
|
|
22
|
+
return to;
|
|
23
|
+
};
|
|
22
24
|
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
26
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
27
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
28
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
29
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
30
|
+
value: mod,
|
|
31
|
+
enumerable: true
|
|
32
|
+
}) : target, mod));
|
|
33
|
+
var __toCommonJS = mod => __copyProps(__defProp({}, "__esModule", {
|
|
34
|
+
value: true
|
|
35
|
+
}), mod);
|
|
34
36
|
var Dismissable_native_exports = {};
|
|
35
37
|
__export(Dismissable_native_exports, {
|
|
36
38
|
Dismissable: () => Dismissable,
|
|
37
39
|
DismissableBranch: () => DismissableBranch,
|
|
38
|
-
dispatchDiscreteCustomEvent: () => dispatchDiscreteCustomEvent
|
|
40
|
+
dispatchDiscreteCustomEvent: () => dispatchDiscreteCustomEvent,
|
|
41
|
+
getDismissableLayerCount: () => getDismissableLayerCount,
|
|
42
|
+
useDismissableLayersAbove: () => useDismissableLayersAbove,
|
|
43
|
+
useHasDismissableLayers: () => useHasDismissableLayers,
|
|
44
|
+
useIsInsideDismissable: () => useIsInsideDismissable
|
|
39
45
|
});
|
|
40
46
|
module.exports = __toCommonJS(Dismissable_native_exports);
|
|
41
47
|
var import_react = __toESM(require("react"), 1);
|
|
42
48
|
function dispatchDiscreteCustomEvent(_target, _event) {}
|
|
49
|
+
function getDismissableLayerCount() {
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
function useHasDismissableLayers() {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
function useIsInsideDismissable(_ref) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
function useDismissableLayersAbove(_ref) {
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
43
61
|
var Dismissable = /* @__PURE__ */import_react.default.forwardRef(function (props, _ref) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
62
|
+
return props.children;
|
|
63
|
+
});
|
|
64
|
+
var DismissableBranch = /* @__PURE__ */import_react.default.forwardRef(function (props, _ref) {
|
|
65
|
+
return props.children;
|
|
66
|
+
});
|
|
49
67
|
//# sourceMappingURL=Dismissable.native.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["Dismissable_native_exports","__export","Dismissable","DismissableBranch","dispatchDiscreteCustomEvent","module","exports","__toCommonJS","import_react","__toESM","require","_target","_event","default","forwardRef","props","
|
|
1
|
+
{"version":3,"names":["Dismissable_native_exports","__export","Dismissable","DismissableBranch","dispatchDiscreteCustomEvent","getDismissableLayerCount","useDismissableLayersAbove","useHasDismissableLayers","useIsInsideDismissable","module","exports","__toCommonJS","import_react","__toESM","require","_target","_event","_ref","default","forwardRef","props","children"],"sources":["../../src/Dismissable.native.tsx"],"sourcesContent":[null],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,IAAAA,0BAAA;AAAAC,QAAA,CAAAD,0BAAA;EAAAE,WAAA,EAAAA,CAAA,KAAAA,WAAA;EAAAC,iBAAA,EAAAA,CAAA,KAAAA,iBAAA;EAAAC,2BAAA,EAAAA,CAAA,KAAAA,2BAAA;EAAAC,wBAAA,EAAAA,CAAA,KAAAA,wBAAA;EAAAC,yBAAA,EAAAA,CAAA,KAAAA,yBAAA;EAAAC,uBAAA,EAAAA,CAAA,KAAAA,uBAAA;EAAAC,sBAAA,EAAAA,CAAA,KAAAA;AAAA;AAAAC,MAAA,CAAAC,OAAA,GAAAC,YAAA,CAAAX,0BAAA;AAAA,IAAAY,YAAA,GAAkBC,OAAA,CAAAC,OAAA;AAEX,SAASV,4BAA4BW,OAAA,EAASC,MAAA,EAAQ,CAAC;AACvD,SAASX,yBAAA,EAA2B;EACvC,OAAO;AACX;AACO,SAASE,wBAAA,EAA0B;EACtC,OAAO;AACX;AACO,SAASC,uBAAuBS,IAAA,EAAM;EACzC,OAAO;AACX;AACO,SAASX,0BAA0BW,IAAA,EAAM;EAC5C,OAAO;AACX;AACO,IAAIf,WAAA,GAA4B,eAAAU,YAAA,CAAAM,OAAA,CAAMC,UAAA,CAAW,UAASC,KAAA,EAAOH,IAAA,EAAM;EAC1E,OAAOG,KAAA,CAAMC,QAAA;AACjB,CAAC;AACM,IAAIlB,iBAAA,GAAkC,eAAAS,YAAA,CAAAM,OAAA,CAAMC,UAAA,CAAW,UAASC,KAAA,EAAOH,IAAA,EAAM;EAChF,OAAOG,KAAA,CAAMC,QAAA;AACjB,CAAC","ignoreList":[]}
|
|
@@ -5,14 +5,16 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
7
|
var __copyProps = (to, from, except, desc) => {
|
|
8
|
-
if (from && typeof from
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
9
|
+
for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
10
|
+
get: () => from[key],
|
|
11
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
12
|
+
});
|
|
13
|
+
}
|
|
12
14
|
return to;
|
|
13
15
|
};
|
|
14
16
|
var __toCommonJS = mod => __copyProps(__defProp({}, "__esModule", {
|
|
15
|
-
value:
|
|
17
|
+
value: true
|
|
16
18
|
}), mod);
|
|
17
19
|
var DismissableProps_exports = {};
|
|
18
20
|
module.exports = __toCommonJS(DismissableProps_exports);
|
package/dist/jsx/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from "./Dismissable";
|
|
1
|
+
export * from "./Dismissable.mjs";
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/jsx/index.js.map
CHANGED
package/dist/jsx/index.native.js
CHANGED
|
@@ -5,15 +5,17 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
7
|
var __copyProps = (to, from, except, desc) => {
|
|
8
|
-
|
|
8
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
9
|
+
for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
9
10
|
get: () => from[key],
|
|
10
11
|
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
11
12
|
});
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
}
|
|
14
|
+
return to;
|
|
15
|
+
};
|
|
16
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
15
17
|
var __toCommonJS = mod => __copyProps(__defProp({}, "__esModule", {
|
|
16
|
-
value:
|
|
18
|
+
value: true
|
|
17
19
|
}), mod);
|
|
18
20
|
var index_exports = {};
|
|
19
21
|
module.exports = __toCommonJS(index_exports);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tamagui/dismissable",
|
|
3
|
-
"version": "2.0.0
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"source": "src/index.ts",
|
|
5
5
|
"files": [
|
|
6
6
|
"src",
|
|
@@ -16,15 +16,12 @@
|
|
|
16
16
|
"./package.json": "./package.json",
|
|
17
17
|
".": {
|
|
18
18
|
"types": "./types/index.d.ts",
|
|
19
|
-
"react-native":
|
|
20
|
-
|
|
21
|
-
"import": "./dist/esm/index.native.js",
|
|
22
|
-
"require": "./dist/cjs/index.native.js"
|
|
23
|
-
},
|
|
19
|
+
"react-native": "./dist/esm/index.native.js",
|
|
20
|
+
"browser": "./dist/esm/index.mjs",
|
|
24
21
|
"module": "./dist/esm/index.mjs",
|
|
25
22
|
"import": "./dist/esm/index.mjs",
|
|
26
23
|
"require": "./dist/cjs/index.cjs",
|
|
27
|
-
"default": "./dist/
|
|
24
|
+
"default": "./dist/esm/index.mjs"
|
|
28
25
|
}
|
|
29
26
|
},
|
|
30
27
|
"publishConfig": {
|
|
@@ -37,14 +34,14 @@
|
|
|
37
34
|
"clean:build": "tamagui-build clean:build"
|
|
38
35
|
},
|
|
39
36
|
"dependencies": {
|
|
40
|
-
"@tamagui/compose-refs": "2.0.0
|
|
41
|
-
"@tamagui/core": "2.0.0
|
|
42
|
-
"@tamagui/helpers": "2.0.0
|
|
43
|
-
"@tamagui/use-escape-keydown": "2.0.0
|
|
44
|
-
"@tamagui/use-event": "2.0.0
|
|
37
|
+
"@tamagui/compose-refs": "2.0.0",
|
|
38
|
+
"@tamagui/core": "2.0.0",
|
|
39
|
+
"@tamagui/helpers": "2.0.0",
|
|
40
|
+
"@tamagui/use-escape-keydown": "2.0.0",
|
|
41
|
+
"@tamagui/use-event": "2.0.0"
|
|
45
42
|
},
|
|
46
43
|
"devDependencies": {
|
|
47
|
-
"@tamagui/build": "2.0.0
|
|
44
|
+
"@tamagui/build": "2.0.0",
|
|
48
45
|
"react": ">=19",
|
|
49
46
|
"react-dom": "*"
|
|
50
47
|
},
|
|
@@ -2,12 +2,32 @@ import React from 'react'
|
|
|
2
2
|
|
|
3
3
|
import type { DismissableBranchProps, DismissableProps } from './DismissableProps'
|
|
4
4
|
|
|
5
|
-
//
|
|
5
|
+
// stubs for native - dismissable is a web-only concept
|
|
6
6
|
export function dispatchDiscreteCustomEvent<E extends CustomEvent>(
|
|
7
7
|
_target: E['target'],
|
|
8
8
|
_event: E
|
|
9
9
|
) {}
|
|
10
10
|
|
|
11
|
+
export function getDismissableLayerCount(): number {
|
|
12
|
+
return 0
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function useHasDismissableLayers(): boolean {
|
|
16
|
+
return false
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function useIsInsideDismissable(
|
|
20
|
+
_ref: React.RefObject<HTMLElement | null>
|
|
21
|
+
): boolean {
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function useDismissableLayersAbove(
|
|
26
|
+
_ref: React.RefObject<HTMLElement | null>
|
|
27
|
+
): number {
|
|
28
|
+
return 0
|
|
29
|
+
}
|
|
30
|
+
|
|
11
31
|
export const Dismissable = React.forwardRef((props: DismissableProps, _ref) => {
|
|
12
32
|
return props.children as any
|
|
13
33
|
})
|
package/src/Dismissable.tsx
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// https://github.com/radix-ui/primitives/blob/cfd8dcba5fa6a0e751486af418d05a7b88a7f541/packages/react/dismissable-layer/src/DismissableLayer.tsx#L324
|
|
3
3
|
|
|
4
4
|
import { useComposedRefs } from '@tamagui/compose-refs'
|
|
5
|
-
import { Slot, View, composeEventHandlers } from '@tamagui/core'
|
|
5
|
+
import { Slot, TamaguiElement, View, composeEventHandlers } from '@tamagui/core'
|
|
6
6
|
import { useEscapeKeydown } from '@tamagui/use-escape-keydown'
|
|
7
7
|
import { useEvent } from '@tamagui/use-event'
|
|
8
8
|
import * as React from 'react'
|
|
@@ -28,14 +28,128 @@ const FOCUS_OUTSIDE = 'dismissable.focusOutside'
|
|
|
28
28
|
|
|
29
29
|
let originalBodyPointerEvents: string
|
|
30
30
|
|
|
31
|
+
// global layer tracking
|
|
32
|
+
const globalLayers = new Set<HTMLElement>()
|
|
33
|
+
const layerChangeListeners = new Set<() => void>()
|
|
34
|
+
|
|
35
|
+
// track if any layer has disableOutsidePointerEvents - only then do we need position updates
|
|
36
|
+
let layersWithPointerEventsDisabledCount = 0
|
|
37
|
+
|
|
38
|
+
function notifyLayerChange() {
|
|
39
|
+
for (const listener of layerChangeListeners) {
|
|
40
|
+
listener()
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* returns the number of active dismissable layers
|
|
46
|
+
* useful for non-React contexts (e.g. escape key handlers)
|
|
47
|
+
*/
|
|
48
|
+
export function getDismissableLayerCount(): number {
|
|
49
|
+
return globalLayers.size
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* debug helper - logs what elements are registered as dismissable layers
|
|
54
|
+
*/
|
|
55
|
+
export function debugDismissableLayers(): HTMLElement[] {
|
|
56
|
+
const layers = Array.from(globalLayers)
|
|
57
|
+
console.log('[Dismissable] Active layers:', layers.length, layers)
|
|
58
|
+
return layers
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* hook that returns true when any dismissable layer is active
|
|
63
|
+
* re-renders when the state changes
|
|
64
|
+
* uses module-level globals, not React context, so works anywhere in tree
|
|
65
|
+
*/
|
|
66
|
+
export function useHasDismissableLayers(): boolean {
|
|
67
|
+
const [count, setCount] = React.useState(() => globalLayers.size)
|
|
68
|
+
|
|
69
|
+
React.useEffect(() => {
|
|
70
|
+
setCount(globalLayers.size)
|
|
71
|
+
const update = () => setCount(globalLayers.size)
|
|
72
|
+
layerChangeListeners.add(update)
|
|
73
|
+
return () => {
|
|
74
|
+
layerChangeListeners.delete(update)
|
|
75
|
+
}
|
|
76
|
+
}, [])
|
|
77
|
+
|
|
78
|
+
return count > 0
|
|
79
|
+
}
|
|
80
|
+
|
|
31
81
|
const DismissableContext = React.createContext({
|
|
32
|
-
layers: new Set<
|
|
33
|
-
layersWithOutsidePointerEventsDisabled: new Set<
|
|
34
|
-
branches: new Set<
|
|
82
|
+
layers: new Set<HTMLElement>(),
|
|
83
|
+
layersWithOutsidePointerEventsDisabled: new Set<HTMLElement>(),
|
|
84
|
+
branches: new Set<HTMLElement>(),
|
|
35
85
|
})
|
|
36
86
|
|
|
87
|
+
/**
|
|
88
|
+
* hook to check if a DOM element is inside an active dismissable layer
|
|
89
|
+
* useful for custom escape handling - if inside a dismissable, you may want to defer
|
|
90
|
+
*/
|
|
91
|
+
export function useIsInsideDismissable(ref: React.RefObject<HTMLElement | null>) {
|
|
92
|
+
const context = React.useContext(DismissableContext)
|
|
93
|
+
const [isInside, setIsInside] = React.useState(false)
|
|
94
|
+
|
|
95
|
+
React.useEffect(() => {
|
|
96
|
+
const check = () => {
|
|
97
|
+
const el = ref.current
|
|
98
|
+
if (!el) {
|
|
99
|
+
setIsInside(false)
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
for (const layer of context.layers) {
|
|
103
|
+
if (layer.contains(el)) {
|
|
104
|
+
setIsInside(true)
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
setIsInside(false)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
check()
|
|
112
|
+
document.addEventListener(CONTEXT_UPDATE, check)
|
|
113
|
+
return () => document.removeEventListener(CONTEXT_UPDATE, check)
|
|
114
|
+
}, [context.layers, ref])
|
|
115
|
+
|
|
116
|
+
return isInside
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* hook to check if there are dismissable layers above a given element
|
|
121
|
+
* returns the count of layers that are ancestors of the element
|
|
122
|
+
*/
|
|
123
|
+
export function useDismissableLayersAbove(ref: React.RefObject<HTMLElement | null>) {
|
|
124
|
+
const context = React.useContext(DismissableContext)
|
|
125
|
+
const [count, setCount] = React.useState(0)
|
|
126
|
+
|
|
127
|
+
React.useEffect(() => {
|
|
128
|
+
const check = () => {
|
|
129
|
+
const el = ref.current
|
|
130
|
+
if (!el) {
|
|
131
|
+
setCount(0)
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
let above = 0
|
|
135
|
+
for (const layer of context.layers) {
|
|
136
|
+
if (layer.contains(el)) {
|
|
137
|
+
above++
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
setCount(above)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
check()
|
|
144
|
+
document.addEventListener(CONTEXT_UPDATE, check)
|
|
145
|
+
return () => document.removeEventListener(CONTEXT_UPDATE, check)
|
|
146
|
+
}, [context.layers, ref])
|
|
147
|
+
|
|
148
|
+
return count
|
|
149
|
+
}
|
|
150
|
+
|
|
37
151
|
const Dismissable = React.forwardRef<
|
|
38
|
-
|
|
152
|
+
HTMLElement,
|
|
39
153
|
DismissableProps & { asChild?: boolean }
|
|
40
154
|
>((props, forwardedRef) => {
|
|
41
155
|
const {
|
|
@@ -48,13 +162,16 @@ const Dismissable = React.forwardRef<
|
|
|
48
162
|
onDismiss,
|
|
49
163
|
asChild,
|
|
50
164
|
children,
|
|
165
|
+
branches: branchesProp,
|
|
51
166
|
...layerProps
|
|
52
167
|
} = props
|
|
53
|
-
const Comp =
|
|
168
|
+
const Comp = asChild ? Slot : View
|
|
54
169
|
const context = React.useContext(DismissableContext)
|
|
55
|
-
const [node, setNode] = React.useState<
|
|
170
|
+
const [node, setNode] = React.useState<HTMLElement | null>(null)
|
|
56
171
|
const [, force] = React.useState({})
|
|
57
|
-
const composedRefs = useComposedRefs(forwardedRef, (node) =>
|
|
172
|
+
const composedRefs = useComposedRefs(forwardedRef, (node: HTMLElement | null) =>
|
|
173
|
+
setNode(node)
|
|
174
|
+
)
|
|
58
175
|
const layers = Array.from(context.layers)
|
|
59
176
|
|
|
60
177
|
const [highestLayerWithOutsidePointerEventsDisabled] = [
|
|
@@ -71,10 +188,10 @@ const Dismissable = React.forwardRef<
|
|
|
71
188
|
index >= highestLayerWithOutsidePointerEventsDisabledIndex
|
|
72
189
|
|
|
73
190
|
const pointerDownOutside = usePointerDownOutside((event) => {
|
|
74
|
-
const target = event.target as
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
)
|
|
191
|
+
const target = event.target as HTMLElement
|
|
192
|
+
// check prop-based branches first (scoped to this dismissable), then global branches
|
|
193
|
+
const branches = branchesProp || context.branches
|
|
194
|
+
const isPointerDownOnBranch = [...branches].some((branch) => branch.contains(target))
|
|
78
195
|
if (!isPointerEventsEnabled || isPointerDownOnBranch) return
|
|
79
196
|
onPointerDownOutside?.(event)
|
|
80
197
|
onInteractOutside?.(event)
|
|
@@ -82,17 +199,25 @@ const Dismissable = React.forwardRef<
|
|
|
82
199
|
})
|
|
83
200
|
|
|
84
201
|
const focusOutside = useFocusOutside((event) => {
|
|
85
|
-
const target = event.target as
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
)
|
|
202
|
+
const target = event.target as HTMLElement
|
|
203
|
+
// check prop-based branches first (scoped to this dismissable), then global branches
|
|
204
|
+
const branches = branchesProp || context.branches
|
|
205
|
+
const isFocusInBranch = [...branches].some((branch) => branch.contains(target))
|
|
89
206
|
if (isFocusInBranch) return
|
|
90
207
|
onFocusOutside?.(event)
|
|
91
208
|
onInteractOutside?.(event)
|
|
92
209
|
if (!event.defaultPrevented) onDismiss?.()
|
|
93
210
|
})
|
|
94
211
|
|
|
212
|
+
// track forceUnmount in a ref so escape handler can check it
|
|
213
|
+
const forceUnmountRef = React.useRef(forceUnmount)
|
|
214
|
+
React.useEffect(() => {
|
|
215
|
+
forceUnmountRef.current = forceUnmount
|
|
216
|
+
}, [forceUnmount])
|
|
217
|
+
|
|
95
218
|
useEscapeKeydown((event) => {
|
|
219
|
+
// skip if this layer is force-unmounted (e.g. dialog closed but still mounted)
|
|
220
|
+
if (forceUnmountRef.current) return
|
|
96
221
|
// Check layers at callback time, not render time, to avoid stale closures
|
|
97
222
|
const currentLayers = Array.from(context.layers)
|
|
98
223
|
const currentIndex = node ? currentLayers.indexOf(node) : -1
|
|
@@ -107,24 +232,32 @@ const Dismissable = React.forwardRef<
|
|
|
107
232
|
|
|
108
233
|
React.useEffect(() => {
|
|
109
234
|
if (!node) return
|
|
235
|
+
// don't add to layers when force-unmounted (dialog closed but still mounted)
|
|
236
|
+
if (forceUnmount) return
|
|
110
237
|
if (disableOutsidePointerEvents) {
|
|
111
238
|
if (context.layersWithOutsidePointerEventsDisabled.size === 0) {
|
|
112
239
|
originalBodyPointerEvents = document.body.style.pointerEvents
|
|
113
240
|
document.body.style.pointerEvents = 'none'
|
|
114
241
|
}
|
|
115
242
|
context.layersWithOutsidePointerEventsDisabled.add(node)
|
|
243
|
+
layersWithPointerEventsDisabledCount++
|
|
116
244
|
}
|
|
117
245
|
context.layers.add(node)
|
|
118
|
-
|
|
246
|
+
globalLayers.add(node)
|
|
247
|
+
// only dispatch position update when pointer-events tracking is active
|
|
248
|
+
if (disableOutsidePointerEvents || layersWithPointerEventsDisabledCount > 0) {
|
|
249
|
+
dispatchUpdate()
|
|
250
|
+
}
|
|
251
|
+
notifyLayerChange()
|
|
119
252
|
return () => {
|
|
120
|
-
if (
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
253
|
+
if (disableOutsidePointerEvents) {
|
|
254
|
+
if (context.layersWithOutsidePointerEventsDisabled.size === 1) {
|
|
255
|
+
document.body.style.pointerEvents = originalBodyPointerEvents
|
|
256
|
+
}
|
|
257
|
+
// decrement AFTER dispatch so other layers still re-render
|
|
125
258
|
}
|
|
126
259
|
}
|
|
127
|
-
}, [node, disableOutsidePointerEvents, context])
|
|
260
|
+
}, [node, disableOutsidePointerEvents, forceUnmount, context])
|
|
128
261
|
|
|
129
262
|
/**
|
|
130
263
|
* We purposefully prevent combining this effect with the `disableOutsidePointerEvents` effect
|
|
@@ -136,15 +269,30 @@ const Dismissable = React.forwardRef<
|
|
|
136
269
|
if (forceUnmount) return
|
|
137
270
|
return () => {
|
|
138
271
|
if (!node) return
|
|
272
|
+
const hadPointerEventsDisabled =
|
|
273
|
+
context.layersWithOutsidePointerEventsDisabled.has(node)
|
|
139
274
|
context.layers.delete(node)
|
|
140
275
|
context.layersWithOutsidePointerEventsDisabled.delete(node)
|
|
141
|
-
|
|
276
|
+
globalLayers.delete(node)
|
|
277
|
+
// only dispatch position update when pointer-events tracking is active
|
|
278
|
+
if (layersWithPointerEventsDisabledCount > 0) {
|
|
279
|
+
dispatchUpdate()
|
|
280
|
+
}
|
|
281
|
+
notifyLayerChange()
|
|
282
|
+
// decrement count AFTER dispatch so other layers see count > 0 and re-render
|
|
283
|
+
if (hadPointerEventsDisabled) {
|
|
284
|
+
layersWithPointerEventsDisabledCount--
|
|
285
|
+
}
|
|
142
286
|
}
|
|
143
287
|
}, [node, context, forceUnmount])
|
|
144
288
|
|
|
145
289
|
React.useEffect(() => {
|
|
146
290
|
const handleUpdate = () => {
|
|
147
|
-
force
|
|
291
|
+
// only force re-render if we need to track layer positions for pointer-events
|
|
292
|
+
// this avoids N^2 re-renders when multiple dismissables mount/unmount
|
|
293
|
+
if (layersWithPointerEventsDisabledCount > 0) {
|
|
294
|
+
force({})
|
|
295
|
+
}
|
|
148
296
|
}
|
|
149
297
|
document.addEventListener(CONTEXT_UPDATE, handleUpdate)
|
|
150
298
|
return () => document.removeEventListener(CONTEXT_UPDATE, handleUpdate)
|
|
@@ -155,7 +303,9 @@ const Dismissable = React.forwardRef<
|
|
|
155
303
|
{...layerProps}
|
|
156
304
|
// @ts-ignore
|
|
157
305
|
ref={composedRefs}
|
|
158
|
-
|
|
306
|
+
{...(!asChild && {
|
|
307
|
+
display: 'contents',
|
|
308
|
+
})}
|
|
159
309
|
pointerEvents={
|
|
160
310
|
isBodyPointerEventsDisabled
|
|
161
311
|
? isPointerEventsEnabled
|
|
@@ -189,23 +339,27 @@ Dismissable.displayName = DISMISSABLE_LAYER_NAME
|
|
|
189
339
|
|
|
190
340
|
const BRANCH_NAME = 'DismissableBranch'
|
|
191
341
|
|
|
192
|
-
const DismissableBranch = React.forwardRef<
|
|
342
|
+
const DismissableBranch = React.forwardRef<TamaguiElement, DismissableBranchProps>(
|
|
193
343
|
(props, forwardedRef) => {
|
|
344
|
+
const { branches: branchesProp, ...rest } = props
|
|
194
345
|
const context = React.useContext(DismissableContext)
|
|
195
|
-
const ref = React.useRef<
|
|
346
|
+
const ref = React.useRef<TamaguiElement>(null)
|
|
196
347
|
const composedRefs = useComposedRefs(forwardedRef, ref)
|
|
197
348
|
|
|
198
349
|
React.useEffect(() => {
|
|
199
350
|
const node = ref.current
|
|
200
|
-
if (node)
|
|
201
|
-
|
|
351
|
+
if (!(node instanceof HTMLElement)) return
|
|
352
|
+
// use prop-based branches if provided, otherwise fall back to global context
|
|
353
|
+
const branches = branchesProp || context.branches
|
|
354
|
+
if (node && branches) {
|
|
355
|
+
branches.add(node)
|
|
202
356
|
return () => {
|
|
203
|
-
|
|
357
|
+
branches.delete(node)
|
|
204
358
|
}
|
|
205
359
|
}
|
|
206
|
-
}, [context.branches])
|
|
360
|
+
}, [branchesProp, context.branches])
|
|
207
361
|
|
|
208
|
-
return <
|
|
362
|
+
return <View asChild="except-style" {...rest} ref={composedRefs} />
|
|
209
363
|
}
|
|
210
364
|
)
|
|
211
365
|
|
package/src/DismissableProps.tsx
CHANGED
|
@@ -9,6 +9,11 @@ export interface DismissableProps {
|
|
|
9
9
|
* interact with them: once to close the `Dismissable`, and again to trigger the element.
|
|
10
10
|
*/
|
|
11
11
|
disableOutsidePointerEvents?: boolean
|
|
12
|
+
/**
|
|
13
|
+
* Optional Set of branch elements that should not trigger dismissal.
|
|
14
|
+
* Pass the same Set to DismissableBranch components to scope them to this Dismissable.
|
|
15
|
+
*/
|
|
16
|
+
branches?: Set<HTMLElement>
|
|
12
17
|
/**
|
|
13
18
|
* Event handler called when the escape key is down.
|
|
14
19
|
* Can be prevented.
|
|
@@ -50,4 +55,9 @@ export interface DismissableProps {
|
|
|
50
55
|
|
|
51
56
|
export interface DismissableBranchProps {
|
|
52
57
|
children?: React.ReactNode
|
|
58
|
+
/**
|
|
59
|
+
* Optional Set to register this branch with.
|
|
60
|
+
* Pass the same Set to the Dismissable to scope this branch to that specific layer.
|
|
61
|
+
*/
|
|
62
|
+
branches?: Set<HTMLElement>
|
|
53
63
|
}
|
package/types/Dismissable.d.ts
CHANGED
|
@@ -1,10 +1,36 @@
|
|
|
1
|
+
import { TamaguiElement } from '@tamagui/core';
|
|
1
2
|
import * as React from 'react';
|
|
2
3
|
import type { DismissableBranchProps, DismissableProps } from './DismissableProps';
|
|
3
4
|
export declare function dispatchDiscreteCustomEvent<E extends CustomEvent>(target: E['target'], event: E): void;
|
|
5
|
+
/**
|
|
6
|
+
* returns the number of active dismissable layers
|
|
7
|
+
* useful for non-React contexts (e.g. escape key handlers)
|
|
8
|
+
*/
|
|
9
|
+
export declare function getDismissableLayerCount(): number;
|
|
10
|
+
/**
|
|
11
|
+
* debug helper - logs what elements are registered as dismissable layers
|
|
12
|
+
*/
|
|
13
|
+
export declare function debugDismissableLayers(): HTMLElement[];
|
|
14
|
+
/**
|
|
15
|
+
* hook that returns true when any dismissable layer is active
|
|
16
|
+
* re-renders when the state changes
|
|
17
|
+
* uses module-level globals, not React context, so works anywhere in tree
|
|
18
|
+
*/
|
|
19
|
+
export declare function useHasDismissableLayers(): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* hook to check if a DOM element is inside an active dismissable layer
|
|
22
|
+
* useful for custom escape handling - if inside a dismissable, you may want to defer
|
|
23
|
+
*/
|
|
24
|
+
export declare function useIsInsideDismissable(ref: React.RefObject<HTMLElement | null>): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* hook to check if there are dismissable layers above a given element
|
|
27
|
+
* returns the count of layers that are ancestors of the element
|
|
28
|
+
*/
|
|
29
|
+
export declare function useDismissableLayersAbove(ref: React.RefObject<HTMLElement | null>): number;
|
|
4
30
|
declare const Dismissable: React.ForwardRefExoticComponent<DismissableProps & {
|
|
5
31
|
asChild?: boolean;
|
|
6
|
-
} & React.RefAttributes<
|
|
7
|
-
declare const DismissableBranch: React.ForwardRefExoticComponent<DismissableBranchProps & React.RefAttributes<
|
|
32
|
+
} & React.RefAttributes<HTMLElement>>;
|
|
33
|
+
declare const DismissableBranch: React.ForwardRefExoticComponent<DismissableBranchProps & React.RefAttributes<TamaguiElement>>;
|
|
8
34
|
export type PointerDownOutsideEvent = CustomEvent<{
|
|
9
35
|
originalEvent: PointerEvent;
|
|
10
36
|
}>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dismissable.d.ts","sourceRoot":"","sources":["../src/Dismissable.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Dismissable.d.ts","sourceRoot":"","sources":["../src/Dismissable.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAQ,cAAc,EAA8B,MAAM,eAAe,CAAA;AAGhF,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,OAAO,KAAK,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAElF,wBAAgB,2BAA2B,CAAC,CAAC,SAAS,WAAW,EAC/D,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,EACnB,KAAK,EAAE,CAAC,QAGT;AA0BD;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CAEjD;AAED;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,WAAW,EAAE,CAItD;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,IAAI,OAAO,CAajD;AAQD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,WA0B9E;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,UA0BjF;AAED,QAAA,MAAM,WAAW;cAEgB,OAAO;qCAmLtC,CAAA;AAUF,QAAA,MAAM,iBAAiB,+FAsBtB,CAAA;AAMD,MAAM,MAAM,uBAAuB,GAAG,WAAW,CAAC;IAAE,aAAa,EAAE,YAAY,CAAA;CAAE,CAAC,CAAA;AAClF,MAAM,MAAM,iBAAiB,GAAG,WAAW,CAAC;IAAE,aAAa,EAAE,UAAU,CAAA;CAAE,CAAC,CAAA;AAsI1E,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,CAAA;AAEzC,YAAY,EAAE,gBAAgB,EAAE,CAAA"}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { DismissableBranchProps, DismissableProps } from './DismissableProps';
|
|
3
3
|
export declare function dispatchDiscreteCustomEvent<E extends CustomEvent>(_target: E['target'], _event: E): void;
|
|
4
|
+
export declare function getDismissableLayerCount(): number;
|
|
5
|
+
export declare function useHasDismissableLayers(): boolean;
|
|
6
|
+
export declare function useIsInsideDismissable(_ref: React.RefObject<HTMLElement | null>): boolean;
|
|
7
|
+
export declare function useDismissableLayersAbove(_ref: React.RefObject<HTMLElement | null>): number;
|
|
4
8
|
export declare const Dismissable: React.ForwardRefExoticComponent<DismissableProps & React.RefAttributes<unknown>>;
|
|
5
9
|
export declare const DismissableBranch: React.ForwardRefExoticComponent<DismissableBranchProps & React.RefAttributes<unknown>>;
|
|
6
10
|
//# sourceMappingURL=Dismissable.native.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dismissable.native.d.ts","sourceRoot":"","sources":["../src/Dismissable.native.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAGlF,wBAAgB,2BAA2B,CAAC,CAAC,SAAS,WAAW,EAC/D,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,EACpB,MAAM,EAAE,CAAC,QACP;AAEJ,eAAO,MAAM,WAAW,kFAEtB,CAAA;AAEF,eAAO,MAAM,iBAAiB,wFAI7B,CAAA"}
|
|
1
|
+
{"version":3,"file":"Dismissable.native.d.ts","sourceRoot":"","sources":["../src/Dismissable.native.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAGlF,wBAAgB,2BAA2B,CAAC,CAAC,SAAS,WAAW,EAC/D,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,EACpB,MAAM,EAAE,CAAC,QACP;AAEJ,wBAAgB,wBAAwB,IAAI,MAAM,CAEjD;AAED,wBAAgB,uBAAuB,IAAI,OAAO,CAEjD;AAED,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,GACxC,OAAO,CAET;AAED,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,GACxC,MAAM,CAER;AAED,eAAO,MAAM,WAAW,kFAEtB,CAAA;AAEF,eAAO,MAAM,iBAAiB,wFAI7B,CAAA"}
|