@tensor_1/react 2.26.1 → 2.26.3
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/components/EmojiPicker.d.ts +3 -0
- package/dist/components/EmojiPicker.js +109 -54
- package/dist/components/Popover.d.ts +15 -5
- package/dist/components/Popover.js +66 -6
- package/dist/{emoji-image-WQLh8ZQP.d.cts → emoji-image-6mPZ15Zy.d.cts} +4 -1
- package/dist/emoji.cjs +324 -74
- package/dist/emoji.d.cts +3 -3
- package/dist/hooks/useAnchoredPosition.d.ts +13 -5
- package/dist/hooks/useAnchoredPosition.js +146 -15
- package/dist/hooks/useDialog.js +1 -0
- package/dist/index.cjs +225 -29
- package/dist/index.d.cts +26 -11
- package/dist/{overlay-position-CEGSz6Z2.d.cts → overlay-position-Cjv5E7mn.d.cts} +7 -0
- package/dist/overlay-position.d.ts +7 -0
- package/dist/overlay-position.js +7 -2
- package/dist/rich-text.cjs +324 -74
- package/dist/rich-text.d.cts +3 -3
- package/package.json +1 -1
|
@@ -2,43 +2,174 @@
|
|
|
2
2
|
import { useCallback, useEffect, useLayoutEffect, useRef } from "react";
|
|
3
3
|
import { computePosition } from "../overlay-position.js";
|
|
4
4
|
const useIsomorphicLayoutEffect = typeof document !== "undefined" ? useLayoutEffect : useEffect;
|
|
5
|
-
const
|
|
5
|
+
const resolveAnchor = (a) => a && typeof a === "object" && "current" in a ? a.current : a ?? null;
|
|
6
|
+
let anchoringSupported;
|
|
7
|
+
function supportsAnchoring() {
|
|
8
|
+
if (anchoringSupported === void 0) {
|
|
9
|
+
try {
|
|
10
|
+
const css = typeof CSS !== "undefined" ? CSS : void 0;
|
|
11
|
+
anchoringSupported = !!css && typeof css.supports === "function" && css.supports("anchor-name", "--uix-a") && css.supports("position-anchor", "--uix-a") && css.supports("top", "round(anchor(bottom) + 1px, 1px)");
|
|
12
|
+
} catch {
|
|
13
|
+
anchoringSupported = false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return anchoringSupported;
|
|
17
|
+
}
|
|
18
|
+
let anchorCount = 0;
|
|
19
|
+
const namesOf = (value) => value.split(",").map((n) => n.trim()).filter((n) => n.startsWith("--"));
|
|
20
|
+
function addAnchorName(el, name) {
|
|
21
|
+
const names = namesOf(getComputedStyle(el).getPropertyValue("anchor-name"));
|
|
22
|
+
if (!names.includes(name)) names.push(name);
|
|
23
|
+
el.style.setProperty("anchor-name", names.join(", "));
|
|
24
|
+
}
|
|
25
|
+
function removeAnchorName(el, name) {
|
|
26
|
+
const names = namesOf(el.style.getPropertyValue("anchor-name")).filter((n) => n !== name);
|
|
27
|
+
if (names.length) el.style.setProperty("anchor-name", names.join(", "));
|
|
28
|
+
else el.style.removeProperty("anchor-name");
|
|
29
|
+
}
|
|
30
|
+
function translationOf(el) {
|
|
31
|
+
const t = getComputedStyle(el).transform;
|
|
32
|
+
if (!t || t === "none" || typeof DOMMatrixReadOnly === "undefined") return { x: 0, y: 0 };
|
|
33
|
+
try {
|
|
34
|
+
const m = new DOMMatrixReadOnly(t);
|
|
35
|
+
return { x: m.m41, y: m.m42 };
|
|
36
|
+
} catch {
|
|
37
|
+
return { x: 0, y: 0 };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const signed = (n) => `${n < 0 ? "-" : "+"} ${Math.round(Math.abs(n) * 100) / 100}px`;
|
|
41
|
+
const prepared = /* @__PURE__ */ new WeakSet();
|
|
6
42
|
function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-start", offset = 6, padding = 8 }) {
|
|
7
43
|
const opts = useRef({ placement, offset, padding });
|
|
8
44
|
opts.current = { placement, offset, padding };
|
|
45
|
+
const session = useRef(false);
|
|
46
|
+
const side = useRef(null);
|
|
47
|
+
const written = useRef("");
|
|
48
|
+
const anchoring = useRef(null);
|
|
49
|
+
const anchoringFailed = useRef(false);
|
|
50
|
+
const anchorName = useRef("");
|
|
51
|
+
const release = useCallback((keepPlace) => {
|
|
52
|
+
const current = anchoring.current;
|
|
53
|
+
if (!current) return;
|
|
54
|
+
anchoring.current = null;
|
|
55
|
+
written.current = "";
|
|
56
|
+
const floating = floatingRef.current;
|
|
57
|
+
if (floating) {
|
|
58
|
+
const s = floating.style;
|
|
59
|
+
if (keepPlace && current.anchor.isConnected) {
|
|
60
|
+
const a = current.anchor.getBoundingClientRect();
|
|
61
|
+
s.top = `${Math.round((current.yEdge === "bottom" ? a.bottom : a.top) + current.dy)}px`;
|
|
62
|
+
s.left = `${Math.round((current.xEdge === "right" ? a.right : a.left) + current.dx)}px`;
|
|
63
|
+
}
|
|
64
|
+
s.removeProperty("position-anchor");
|
|
65
|
+
}
|
|
66
|
+
removeAnchorName(current.anchor, current.name);
|
|
67
|
+
}, [floatingRef]);
|
|
9
68
|
const reposition = useCallback(() => {
|
|
10
|
-
const anchorEl =
|
|
69
|
+
const anchorEl = resolveAnchor(anchor);
|
|
11
70
|
const floating = floatingRef.current;
|
|
12
71
|
if (!anchorEl || !floating) return;
|
|
13
72
|
const s = floating.style;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
73
|
+
if (!prepared.has(floating)) {
|
|
74
|
+
s.position = "fixed";
|
|
75
|
+
s.inset = "auto";
|
|
76
|
+
s.margin = "0";
|
|
77
|
+
prepared.add(floating);
|
|
78
|
+
written.current = "";
|
|
79
|
+
}
|
|
17
80
|
const a = anchorEl.getBoundingClientRect();
|
|
81
|
+
const size = { width: floating.offsetWidth, height: floating.offsetHeight };
|
|
18
82
|
const { placement: p, offset: o, padding: pad } = opts.current;
|
|
19
83
|
const pos = computePosition(
|
|
20
84
|
{ x: a.x, y: a.y, width: a.width, height: a.height },
|
|
21
|
-
|
|
85
|
+
size,
|
|
22
86
|
{ width: window.innerWidth, height: window.innerHeight },
|
|
23
|
-
{ placement: p, offset: o, padding: pad }
|
|
87
|
+
{ placement: p, offset: o, padding: pad, stickySide: side.current ?? void 0 }
|
|
24
88
|
);
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
floating.dataset.placement = pos.placement;
|
|
28
|
-
|
|
89
|
+
const measured = size.width > 0 || size.height > 0;
|
|
90
|
+
if (measured && session.current) side.current = pos.side;
|
|
91
|
+
if (floating.dataset.placement !== pos.placement) floating.dataset.placement = pos.placement;
|
|
92
|
+
const writeFixed = (at) => {
|
|
93
|
+
const left2 = `${Math.round(at.x)}px`;
|
|
94
|
+
const top2 = `${Math.round(at.y)}px`;
|
|
95
|
+
const key2 = `fixed ${left2} ${top2}`;
|
|
96
|
+
if (key2 === written.current) return;
|
|
97
|
+
s.left = left2;
|
|
98
|
+
s.top = top2;
|
|
99
|
+
written.current = key2;
|
|
100
|
+
};
|
|
101
|
+
if (anchoringFailed.current || !supportsAnchoring()) {
|
|
102
|
+
writeFixed(pos);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (anchoring.current && anchoring.current.anchor !== anchorEl) release(false);
|
|
106
|
+
if (!anchoring.current) {
|
|
107
|
+
anchorName.current || (anchorName.current = `--uix-anchor-${++anchorCount}`);
|
|
108
|
+
addAnchorName(anchorEl, anchorName.current);
|
|
109
|
+
s.setProperty("position-anchor", anchorName.current);
|
|
110
|
+
anchoring.current = { anchor: anchorEl, name: anchorName.current, yEdge: "bottom", xEdge: "left", dy: 0, dx: 0, verified: false };
|
|
111
|
+
written.current = "";
|
|
112
|
+
}
|
|
113
|
+
const current = anchoring.current;
|
|
114
|
+
current.yEdge = pos.side === "bottom" ? "bottom" : "top";
|
|
115
|
+
current.xEdge = pos.side === "right" ? "right" : "left";
|
|
116
|
+
current.dy = pos.y - (current.yEdge === "bottom" ? a.bottom : a.top);
|
|
117
|
+
current.dx = pos.x - (current.xEdge === "right" ? a.right : a.left);
|
|
118
|
+
const top = `round(anchor(${current.yEdge}) ${signed(current.dy)}, 1px)`;
|
|
119
|
+
const left = `round(anchor(${current.xEdge}) ${signed(current.dx)}, 1px)`;
|
|
120
|
+
const key = `anchored ${top} ${left}`;
|
|
121
|
+
if (key !== written.current) {
|
|
122
|
+
s.top = top;
|
|
123
|
+
s.left = left;
|
|
124
|
+
written.current = key;
|
|
125
|
+
}
|
|
126
|
+
if (!current.verified && measured) {
|
|
127
|
+
current.verified = true;
|
|
128
|
+
const box = floating.getBoundingClientRect();
|
|
129
|
+
const t = translationOf(floating);
|
|
130
|
+
const dxc = box.left + box.width / 2 - t.x - (pos.x + size.width / 2);
|
|
131
|
+
const dyc = box.top + box.height / 2 - t.y - (pos.y + size.height / 2);
|
|
132
|
+
if (Math.abs(dxc) > 2 || Math.abs(dyc) > 2) {
|
|
133
|
+
anchoringFailed.current = true;
|
|
134
|
+
release(false);
|
|
135
|
+
writeFixed(pos);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}, [anchor, floatingRef, release]);
|
|
29
139
|
useIsomorphicLayoutEffect(() => {
|
|
30
140
|
if (!open) return;
|
|
141
|
+
session.current = true;
|
|
31
142
|
reposition();
|
|
32
|
-
const
|
|
143
|
+
const anchorEl = resolveAnchor(anchor);
|
|
144
|
+
const floating = floatingRef.current;
|
|
145
|
+
const onScroll = (event) => {
|
|
146
|
+
const target = event.target;
|
|
147
|
+
if (target && typeof target.contains === "function") {
|
|
148
|
+
if (floating?.contains(target)) return;
|
|
149
|
+
if (anchorEl && !target.contains(anchorEl)) return;
|
|
150
|
+
}
|
|
151
|
+
reposition();
|
|
152
|
+
};
|
|
153
|
+
const onResize = () => reposition();
|
|
33
154
|
window.addEventListener("scroll", onScroll, { passive: true, capture: true });
|
|
34
|
-
window.addEventListener("resize",
|
|
155
|
+
window.addEventListener("resize", onResize);
|
|
156
|
+
const observer = typeof ResizeObserver === "function" ? new ResizeObserver(() => reposition()) : null;
|
|
157
|
+
if (floating) observer?.observe(floating);
|
|
158
|
+
if (anchorEl) observer?.observe(anchorEl);
|
|
35
159
|
return () => {
|
|
36
160
|
window.removeEventListener("scroll", onScroll, true);
|
|
37
|
-
window.removeEventListener("resize",
|
|
161
|
+
window.removeEventListener("resize", onResize);
|
|
162
|
+
observer?.disconnect();
|
|
163
|
+
session.current = false;
|
|
164
|
+
side.current = null;
|
|
165
|
+
anchoringFailed.current = false;
|
|
166
|
+
release(true);
|
|
38
167
|
};
|
|
39
|
-
}, [open, reposition]);
|
|
168
|
+
}, [open, reposition, anchor, floatingRef, release]);
|
|
169
|
+
useEffect(() => () => release(false), [release]);
|
|
40
170
|
return reposition;
|
|
41
171
|
}
|
|
42
172
|
export {
|
|
173
|
+
resolveAnchor,
|
|
43
174
|
useAnchoredPosition
|
|
44
175
|
};
|
package/dist/hooks/useDialog.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -294,6 +294,7 @@ function useDialog(open, onClose) {
|
|
|
294
294
|
lockBodyScroll();
|
|
295
295
|
let locked = true;
|
|
296
296
|
const handleClose = () => {
|
|
297
|
+
if (el.open) return;
|
|
297
298
|
if (locked) {
|
|
298
299
|
locked = false;
|
|
299
300
|
unlockBodyScroll();
|
|
@@ -703,10 +704,15 @@ function fitsOn(side, a, f, vp, offset, padding) {
|
|
|
703
704
|
return spaceOn(side, a, vp, offset) >= need + padding;
|
|
704
705
|
}
|
|
705
706
|
function computePosition(anchor, floating, viewport, options = {}) {
|
|
706
|
-
const { placement = "bottom-start", offset = 6, padding = 8, flip = true, shift = true } = options;
|
|
707
|
+
const { placement = "bottom-start", offset = 6, padding = 8, flip = true, shift = true, stickySide } = options;
|
|
707
708
|
const { side: preferred, align } = parsePlacement(placement);
|
|
708
709
|
let side = preferred;
|
|
709
|
-
|
|
710
|
+
const sticky = stickySide === preferred || stickySide === OPPOSITE[preferred] ? stickySide : void 0;
|
|
711
|
+
if (flip && sticky) {
|
|
712
|
+
side = sticky;
|
|
713
|
+
const opp = OPPOSITE[side];
|
|
714
|
+
if (!fitsOn(side, anchor, floating, viewport, offset, padding) && fitsOn(opp, anchor, floating, viewport, offset, padding)) side = opp;
|
|
715
|
+
} else if (flip && !fitsOn(side, anchor, floating, viewport, offset, padding)) {
|
|
710
716
|
const opp = OPPOSITE[side];
|
|
711
717
|
if (fitsOn(opp, anchor, floating, viewport, offset, padding) || spaceOn(opp, anchor, viewport, offset) > spaceOn(side, anchor, viewport, offset)) {
|
|
712
718
|
side = opp;
|
|
@@ -742,41 +748,171 @@ function computePosition(anchor, floating, viewport, options = {}) {
|
|
|
742
748
|
|
|
743
749
|
// src/hooks/useAnchoredPosition.ts
|
|
744
750
|
var useIsomorphicLayoutEffect = typeof document !== "undefined" ? import_react4.useLayoutEffect : import_react4.useEffect;
|
|
745
|
-
var
|
|
751
|
+
var resolveAnchor = (a) => a && typeof a === "object" && "current" in a ? a.current : a ?? null;
|
|
752
|
+
var anchoringSupported;
|
|
753
|
+
function supportsAnchoring() {
|
|
754
|
+
if (anchoringSupported === void 0) {
|
|
755
|
+
try {
|
|
756
|
+
const css = typeof CSS !== "undefined" ? CSS : void 0;
|
|
757
|
+
anchoringSupported = !!css && typeof css.supports === "function" && css.supports("anchor-name", "--uix-a") && css.supports("position-anchor", "--uix-a") && css.supports("top", "round(anchor(bottom) + 1px, 1px)");
|
|
758
|
+
} catch {
|
|
759
|
+
anchoringSupported = false;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
return anchoringSupported;
|
|
763
|
+
}
|
|
764
|
+
var anchorCount = 0;
|
|
765
|
+
var namesOf = (value) => value.split(",").map((n) => n.trim()).filter((n) => n.startsWith("--"));
|
|
766
|
+
function addAnchorName(el, name) {
|
|
767
|
+
const names = namesOf(getComputedStyle(el).getPropertyValue("anchor-name"));
|
|
768
|
+
if (!names.includes(name)) names.push(name);
|
|
769
|
+
el.style.setProperty("anchor-name", names.join(", "));
|
|
770
|
+
}
|
|
771
|
+
function removeAnchorName(el, name) {
|
|
772
|
+
const names = namesOf(el.style.getPropertyValue("anchor-name")).filter((n) => n !== name);
|
|
773
|
+
if (names.length) el.style.setProperty("anchor-name", names.join(", "));
|
|
774
|
+
else el.style.removeProperty("anchor-name");
|
|
775
|
+
}
|
|
776
|
+
function translationOf(el) {
|
|
777
|
+
const t = getComputedStyle(el).transform;
|
|
778
|
+
if (!t || t === "none" || typeof DOMMatrixReadOnly === "undefined") return { x: 0, y: 0 };
|
|
779
|
+
try {
|
|
780
|
+
const m = new DOMMatrixReadOnly(t);
|
|
781
|
+
return { x: m.m41, y: m.m42 };
|
|
782
|
+
} catch {
|
|
783
|
+
return { x: 0, y: 0 };
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
var signed = (n) => `${n < 0 ? "-" : "+"} ${Math.round(Math.abs(n) * 100) / 100}px`;
|
|
787
|
+
var prepared = /* @__PURE__ */ new WeakSet();
|
|
746
788
|
function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-start", offset = 6, padding = 8 }) {
|
|
747
789
|
const opts = (0, import_react4.useRef)({ placement, offset, padding });
|
|
748
790
|
opts.current = { placement, offset, padding };
|
|
791
|
+
const session = (0, import_react4.useRef)(false);
|
|
792
|
+
const side = (0, import_react4.useRef)(null);
|
|
793
|
+
const written = (0, import_react4.useRef)("");
|
|
794
|
+
const anchoring = (0, import_react4.useRef)(null);
|
|
795
|
+
const anchoringFailed = (0, import_react4.useRef)(false);
|
|
796
|
+
const anchorName = (0, import_react4.useRef)("");
|
|
797
|
+
const release = (0, import_react4.useCallback)((keepPlace) => {
|
|
798
|
+
const current = anchoring.current;
|
|
799
|
+
if (!current) return;
|
|
800
|
+
anchoring.current = null;
|
|
801
|
+
written.current = "";
|
|
802
|
+
const floating = floatingRef.current;
|
|
803
|
+
if (floating) {
|
|
804
|
+
const s = floating.style;
|
|
805
|
+
if (keepPlace && current.anchor.isConnected) {
|
|
806
|
+
const a = current.anchor.getBoundingClientRect();
|
|
807
|
+
s.top = `${Math.round((current.yEdge === "bottom" ? a.bottom : a.top) + current.dy)}px`;
|
|
808
|
+
s.left = `${Math.round((current.xEdge === "right" ? a.right : a.left) + current.dx)}px`;
|
|
809
|
+
}
|
|
810
|
+
s.removeProperty("position-anchor");
|
|
811
|
+
}
|
|
812
|
+
removeAnchorName(current.anchor, current.name);
|
|
813
|
+
}, [floatingRef]);
|
|
749
814
|
const reposition = (0, import_react4.useCallback)(() => {
|
|
750
|
-
const anchorEl =
|
|
815
|
+
const anchorEl = resolveAnchor(anchor);
|
|
751
816
|
const floating = floatingRef.current;
|
|
752
817
|
if (!anchorEl || !floating) return;
|
|
753
818
|
const s = floating.style;
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
819
|
+
if (!prepared.has(floating)) {
|
|
820
|
+
s.position = "fixed";
|
|
821
|
+
s.inset = "auto";
|
|
822
|
+
s.margin = "0";
|
|
823
|
+
prepared.add(floating);
|
|
824
|
+
written.current = "";
|
|
825
|
+
}
|
|
757
826
|
const a = anchorEl.getBoundingClientRect();
|
|
827
|
+
const size = { width: floating.offsetWidth, height: floating.offsetHeight };
|
|
758
828
|
const { placement: p, offset: o, padding: pad } = opts.current;
|
|
759
829
|
const pos = computePosition(
|
|
760
830
|
{ x: a.x, y: a.y, width: a.width, height: a.height },
|
|
761
|
-
|
|
831
|
+
size,
|
|
762
832
|
{ width: window.innerWidth, height: window.innerHeight },
|
|
763
|
-
{ placement: p, offset: o, padding: pad }
|
|
833
|
+
{ placement: p, offset: o, padding: pad, stickySide: side.current ?? void 0 }
|
|
764
834
|
);
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
floating.dataset.placement = pos.placement;
|
|
768
|
-
|
|
835
|
+
const measured = size.width > 0 || size.height > 0;
|
|
836
|
+
if (measured && session.current) side.current = pos.side;
|
|
837
|
+
if (floating.dataset.placement !== pos.placement) floating.dataset.placement = pos.placement;
|
|
838
|
+
const writeFixed = (at) => {
|
|
839
|
+
const left2 = `${Math.round(at.x)}px`;
|
|
840
|
+
const top2 = `${Math.round(at.y)}px`;
|
|
841
|
+
const key2 = `fixed ${left2} ${top2}`;
|
|
842
|
+
if (key2 === written.current) return;
|
|
843
|
+
s.left = left2;
|
|
844
|
+
s.top = top2;
|
|
845
|
+
written.current = key2;
|
|
846
|
+
};
|
|
847
|
+
if (anchoringFailed.current || !supportsAnchoring()) {
|
|
848
|
+
writeFixed(pos);
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
if (anchoring.current && anchoring.current.anchor !== anchorEl) release(false);
|
|
852
|
+
if (!anchoring.current) {
|
|
853
|
+
anchorName.current || (anchorName.current = `--uix-anchor-${++anchorCount}`);
|
|
854
|
+
addAnchorName(anchorEl, anchorName.current);
|
|
855
|
+
s.setProperty("position-anchor", anchorName.current);
|
|
856
|
+
anchoring.current = { anchor: anchorEl, name: anchorName.current, yEdge: "bottom", xEdge: "left", dy: 0, dx: 0, verified: false };
|
|
857
|
+
written.current = "";
|
|
858
|
+
}
|
|
859
|
+
const current = anchoring.current;
|
|
860
|
+
current.yEdge = pos.side === "bottom" ? "bottom" : "top";
|
|
861
|
+
current.xEdge = pos.side === "right" ? "right" : "left";
|
|
862
|
+
current.dy = pos.y - (current.yEdge === "bottom" ? a.bottom : a.top);
|
|
863
|
+
current.dx = pos.x - (current.xEdge === "right" ? a.right : a.left);
|
|
864
|
+
const top = `round(anchor(${current.yEdge}) ${signed(current.dy)}, 1px)`;
|
|
865
|
+
const left = `round(anchor(${current.xEdge}) ${signed(current.dx)}, 1px)`;
|
|
866
|
+
const key = `anchored ${top} ${left}`;
|
|
867
|
+
if (key !== written.current) {
|
|
868
|
+
s.top = top;
|
|
869
|
+
s.left = left;
|
|
870
|
+
written.current = key;
|
|
871
|
+
}
|
|
872
|
+
if (!current.verified && measured) {
|
|
873
|
+
current.verified = true;
|
|
874
|
+
const box = floating.getBoundingClientRect();
|
|
875
|
+
const t = translationOf(floating);
|
|
876
|
+
const dxc = box.left + box.width / 2 - t.x - (pos.x + size.width / 2);
|
|
877
|
+
const dyc = box.top + box.height / 2 - t.y - (pos.y + size.height / 2);
|
|
878
|
+
if (Math.abs(dxc) > 2 || Math.abs(dyc) > 2) {
|
|
879
|
+
anchoringFailed.current = true;
|
|
880
|
+
release(false);
|
|
881
|
+
writeFixed(pos);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
}, [anchor, floatingRef, release]);
|
|
769
885
|
useIsomorphicLayoutEffect(() => {
|
|
770
886
|
if (!open) return;
|
|
887
|
+
session.current = true;
|
|
771
888
|
reposition();
|
|
772
|
-
const
|
|
889
|
+
const anchorEl = resolveAnchor(anchor);
|
|
890
|
+
const floating = floatingRef.current;
|
|
891
|
+
const onScroll = (event) => {
|
|
892
|
+
const target = event.target;
|
|
893
|
+
if (target && typeof target.contains === "function") {
|
|
894
|
+
if (floating?.contains(target)) return;
|
|
895
|
+
if (anchorEl && !target.contains(anchorEl)) return;
|
|
896
|
+
}
|
|
897
|
+
reposition();
|
|
898
|
+
};
|
|
899
|
+
const onResize = () => reposition();
|
|
773
900
|
window.addEventListener("scroll", onScroll, { passive: true, capture: true });
|
|
774
|
-
window.addEventListener("resize",
|
|
901
|
+
window.addEventListener("resize", onResize);
|
|
902
|
+
const observer = typeof ResizeObserver === "function" ? new ResizeObserver(() => reposition()) : null;
|
|
903
|
+
if (floating) observer?.observe(floating);
|
|
904
|
+
if (anchorEl) observer?.observe(anchorEl);
|
|
775
905
|
return () => {
|
|
776
906
|
window.removeEventListener("scroll", onScroll, true);
|
|
777
|
-
window.removeEventListener("resize",
|
|
907
|
+
window.removeEventListener("resize", onResize);
|
|
908
|
+
observer?.disconnect();
|
|
909
|
+
session.current = false;
|
|
910
|
+
side.current = null;
|
|
911
|
+
anchoringFailed.current = false;
|
|
912
|
+
release(true);
|
|
778
913
|
};
|
|
779
|
-
}, [open, reposition]);
|
|
914
|
+
}, [open, reposition, anchor, floatingRef, release]);
|
|
915
|
+
(0, import_react4.useEffect)(() => () => release(false), [release]);
|
|
780
916
|
return reposition;
|
|
781
917
|
}
|
|
782
918
|
|
|
@@ -1489,17 +1625,36 @@ var helpLabelFor = (thing) => typeof thing === "string" || typeof thing === "num
|
|
|
1489
1625
|
// src/components/Popover.tsx
|
|
1490
1626
|
var import_react14 = require("react");
|
|
1491
1627
|
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
1628
|
+
var useIsomorphicLayoutEffect3 = typeof document !== "undefined" ? import_react14.useLayoutEffect : import_react14.useEffect;
|
|
1629
|
+
var PLACING = "data-uix-placing";
|
|
1630
|
+
var isPopoverOpen = (el) => {
|
|
1631
|
+
try {
|
|
1632
|
+
return el.matches(":popover-open");
|
|
1633
|
+
} catch {
|
|
1634
|
+
return false;
|
|
1635
|
+
}
|
|
1636
|
+
};
|
|
1637
|
+
var settle = (el) => {
|
|
1638
|
+
if (!el.hasAttribute(PLACING)) return;
|
|
1639
|
+
getComputedStyle(el).getPropertyValue("transform");
|
|
1640
|
+
el.removeAttribute(PLACING);
|
|
1641
|
+
};
|
|
1492
1642
|
function Popover({
|
|
1493
1643
|
popover = "auto",
|
|
1494
1644
|
anchor,
|
|
1495
1645
|
placement = "bottom-start",
|
|
1496
1646
|
offset = 6,
|
|
1647
|
+
closeWhenAnchorHidden = false,
|
|
1648
|
+
onAnchorHidden,
|
|
1497
1649
|
className,
|
|
1498
1650
|
children,
|
|
1499
1651
|
...props
|
|
1500
1652
|
}) {
|
|
1501
1653
|
const ref = (0, import_react14.useRef)(null);
|
|
1502
1654
|
const [open, setOpen] = (0, import_react14.useState)(false);
|
|
1655
|
+
const openRef = (0, import_react14.useRef)(false);
|
|
1656
|
+
const onAnchorHiddenRef = (0, import_react14.useRef)(onAnchorHidden);
|
|
1657
|
+
onAnchorHiddenRef.current = onAnchorHidden;
|
|
1503
1658
|
const popoverAttr = { popover };
|
|
1504
1659
|
const reposition = useAnchoredPosition(anchor ?? null, ref, {
|
|
1505
1660
|
open: open && !!anchor,
|
|
@@ -1509,14 +1664,55 @@ function Popover({
|
|
|
1509
1664
|
(0, import_react14.useEffect)(() => {
|
|
1510
1665
|
const el = ref.current;
|
|
1511
1666
|
if (!el || !anchor) return;
|
|
1667
|
+
const hold = (event) => {
|
|
1668
|
+
if (event.newState !== "open") return;
|
|
1669
|
+
el.setAttribute(PLACING, "");
|
|
1670
|
+
window.setTimeout(() => settle(el), 150);
|
|
1671
|
+
};
|
|
1512
1672
|
const sync = () => {
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1673
|
+
if (!isPopoverOpen(el)) {
|
|
1674
|
+
el.removeAttribute(PLACING);
|
|
1675
|
+
setOpen(false);
|
|
1676
|
+
return;
|
|
1677
|
+
}
|
|
1678
|
+
reposition();
|
|
1679
|
+
window.setTimeout(() => {
|
|
1680
|
+
if (!isPopoverOpen(el)) return;
|
|
1681
|
+
if (!openRef.current) {
|
|
1682
|
+
setOpen(true);
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1685
|
+
reposition();
|
|
1686
|
+
settle(el);
|
|
1687
|
+
}, 0);
|
|
1516
1688
|
};
|
|
1689
|
+
el.addEventListener("beforetoggle", hold);
|
|
1517
1690
|
el.addEventListener("toggle", sync);
|
|
1518
|
-
return () =>
|
|
1691
|
+
return () => {
|
|
1692
|
+
el.removeEventListener("beforetoggle", hold);
|
|
1693
|
+
el.removeEventListener("toggle", sync);
|
|
1694
|
+
el.removeAttribute(PLACING);
|
|
1695
|
+
};
|
|
1519
1696
|
}, [anchor, reposition]);
|
|
1697
|
+
useIsomorphicLayoutEffect3(() => {
|
|
1698
|
+
openRef.current = open;
|
|
1699
|
+
if (open && ref.current) settle(ref.current);
|
|
1700
|
+
}, [open]);
|
|
1701
|
+
(0, import_react14.useEffect)(() => {
|
|
1702
|
+
const el = ref.current;
|
|
1703
|
+
const anchorEl = resolveAnchor(anchor);
|
|
1704
|
+
if (!closeWhenAnchorHidden || !open || !el || !anchorEl || typeof IntersectionObserver !== "function") return;
|
|
1705
|
+
const observer = new IntersectionObserver((entries) => {
|
|
1706
|
+
const latest = entries[entries.length - 1];
|
|
1707
|
+
if (!latest || latest.isIntersecting || !isPopoverOpen(el)) return;
|
|
1708
|
+
onAnchorHiddenRef.current?.();
|
|
1709
|
+
const active = el.ownerDocument.activeElement;
|
|
1710
|
+
if (active && el.contains(active)) active.blur();
|
|
1711
|
+
el.hidePopover();
|
|
1712
|
+
});
|
|
1713
|
+
observer.observe(anchorEl);
|
|
1714
|
+
return () => observer.disconnect();
|
|
1715
|
+
}, [closeWhenAnchorHidden, open, anchor]);
|
|
1520
1716
|
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { ref, className: cx("uix-popover", className), ...popoverAttr, ...props, children });
|
|
1521
1717
|
}
|
|
1522
1718
|
|
|
@@ -3614,7 +3810,7 @@ var VIEW_MENU_COLUMN_LABELS = {
|
|
|
3614
3810
|
};
|
|
3615
3811
|
var fill = (template, values) => template.replace(/\{(\w+)\}/g, (match, name) => name in values ? String(values[name]) : match);
|
|
3616
3812
|
var columnText = (column) => column.textLabel ?? (typeof column.label === "string" ? column.label : column.id);
|
|
3617
|
-
var
|
|
3813
|
+
var isPopoverOpen2 = (el) => {
|
|
3618
3814
|
try {
|
|
3619
3815
|
return !!el?.matches(":popover-open");
|
|
3620
3816
|
} catch {
|
|
@@ -3630,14 +3826,14 @@ function ColumnRowMenu({ label, actions, triggerRef }) {
|
|
|
3630
3826
|
const enabled = () => items.current.filter((el) => !!el && !el.disabled);
|
|
3631
3827
|
const close = (returnFocus) => {
|
|
3632
3828
|
const el = document.getElementById(menuId);
|
|
3633
|
-
if (
|
|
3829
|
+
if (isPopoverOpen2(el)) el.hidePopover();
|
|
3634
3830
|
setOpen(false);
|
|
3635
3831
|
if (returnFocus) button.current?.focus();
|
|
3636
3832
|
};
|
|
3637
3833
|
(0, import_react36.useEffect)(() => {
|
|
3638
3834
|
if (!open) return;
|
|
3639
3835
|
const el = document.getElementById(menuId);
|
|
3640
|
-
if (el && typeof el.showPopover === "function" && !
|
|
3836
|
+
if (el && typeof el.showPopover === "function" && !isPopoverOpen2(el)) {
|
|
3641
3837
|
try {
|
|
3642
3838
|
el.showPopover();
|
|
3643
3839
|
} catch {
|
|
@@ -3688,7 +3884,7 @@ function ColumnRowMenu({ label, actions, triggerRef }) {
|
|
|
3688
3884
|
"aria-expanded": open,
|
|
3689
3885
|
"aria-controls": open ? menuId : void 0,
|
|
3690
3886
|
onPointerDown: () => {
|
|
3691
|
-
wasOpenAtPress.current =
|
|
3887
|
+
wasOpenAtPress.current = isPopoverOpen2(document.getElementById(menuId));
|
|
3692
3888
|
},
|
|
3693
3889
|
onClick: () => {
|
|
3694
3890
|
const wasOpen = wasOpenAtPress.current;
|
|
@@ -7660,7 +7856,7 @@ function DiffViewer({
|
|
|
7660
7856
|
const [internalResolutions, setInternalResolutions] = (0, import_react51.useState)({});
|
|
7661
7857
|
const resolutions = controlledResolutions ?? internalResolutions;
|
|
7662
7858
|
const summary = summarizeDiff(entries, resolutions);
|
|
7663
|
-
const
|
|
7859
|
+
const resolve = (path, resolution) => {
|
|
7664
7860
|
if (controlledResolutions === void 0) setInternalResolutions((currentState) => ({ ...currentState, [path]: resolution }));
|
|
7665
7861
|
onResolutionChange?.(path, resolution);
|
|
7666
7862
|
};
|
|
@@ -7707,9 +7903,9 @@ function DiffViewer({
|
|
|
7707
7903
|
/* @__PURE__ */ (0, import_jsx_runtime75.jsx)("pre", { children: renderValue(entry[version], labels.notPresent) })
|
|
7708
7904
|
] }, version)) }),
|
|
7709
7905
|
/* @__PURE__ */ (0, import_jsx_runtime75.jsxs)("div", { className: "uix-diff-viewer__actions", role: "group", "aria-label": fillLabel(labels.resolve, { path: entry.path }), children: [
|
|
7710
|
-
/* @__PURE__ */ (0, import_jsx_runtime75.jsx)("button", { type: "button", className: cx("uix-btn uix-btn--primary", buttonSize), "aria-label": fillLabel(labels.acceptIncomingFor, { path: entry.path }), "aria-pressed": resolution === "accept", onClick: () =>
|
|
7711
|
-
/* @__PURE__ */ (0, import_jsx_runtime75.jsx)("button", { type: "button", className: cx("uix-btn uix-btn--secondary", buttonSize), "aria-label": fillLabel(labels.keepCurrentFor, { path: entry.path }), "aria-pressed": resolution === "skip", onClick: () =>
|
|
7712
|
-
/* @__PURE__ */ (0, import_jsx_runtime75.jsx)("button", { type: "button", className: cx("uix-btn uix-btn--ghost", buttonSize), "aria-label": fillLabel(labels.markPendingFor, { path: entry.path }), "aria-pressed": resolution === "pending", onClick: () =>
|
|
7906
|
+
/* @__PURE__ */ (0, import_jsx_runtime75.jsx)("button", { type: "button", className: cx("uix-btn uix-btn--primary", buttonSize), "aria-label": fillLabel(labels.acceptIncomingFor, { path: entry.path }), "aria-pressed": resolution === "accept", onClick: () => resolve(entry.path, "accept"), children: labels.acceptIncoming }),
|
|
7907
|
+
/* @__PURE__ */ (0, import_jsx_runtime75.jsx)("button", { type: "button", className: cx("uix-btn uix-btn--secondary", buttonSize), "aria-label": fillLabel(labels.keepCurrentFor, { path: entry.path }), "aria-pressed": resolution === "skip", onClick: () => resolve(entry.path, "skip"), children: labels.keepCurrent }),
|
|
7908
|
+
/* @__PURE__ */ (0, import_jsx_runtime75.jsx)("button", { type: "button", className: cx("uix-btn uix-btn--ghost", buttonSize), "aria-label": fillLabel(labels.markPendingFor, { path: entry.path }), "aria-pressed": resolution === "pending", onClick: () => resolve(entry.path, "pending"), children: labels.markPending })
|
|
7713
7909
|
] })
|
|
7714
7910
|
] }, entry.path);
|
|
7715
7911
|
})
|
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { RefObject, ButtonHTMLAttributes, ReactNode, InputHTMLAttributes, AriaAttributes, HTMLAttributes, Ref, TextareaHTMLAttributes, SelectHTMLAttributes, AnchorHTMLAttributes, MouseEvent, DetailsHTMLAttributes, TdHTMLAttributes, TableHTMLAttributes, ThHTMLAttributes } from 'react';
|
|
3
|
-
import { P as Placement } from './overlay-position-
|
|
4
|
-
export { A as Align, a as PositionOptions, b as PositionResult, R as Rect, S as Side, c as Size, d as computePosition } from './overlay-position-
|
|
3
|
+
import { P as Placement } from './overlay-position-Cjv5E7mn.cjs';
|
|
4
|
+
export { A as Align, a as PositionOptions, b as PositionResult, R as Rect, S as Side, c as Size, d as computePosition } from './overlay-position-Cjv5E7mn.cjs';
|
|
5
5
|
|
|
6
6
|
declare function cx(...args: (string | false | null | undefined | 0)[]): string;
|
|
7
7
|
|
|
@@ -182,10 +182,15 @@ interface UseAnchoredPositionOptions {
|
|
|
182
182
|
* Position a floating element (a native-popover overlay) against an anchor with
|
|
183
183
|
* cross-browser flip/shift — the DOM half of the overlay-position engine (UIX-FIX-02).
|
|
184
184
|
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
185
|
+
* The engine decides the side, alignment and viewport clamp from getBoundingClientRect.
|
|
186
|
+
* Where the browser supports CSS anchor positioning, that result is written as an offset
|
|
187
|
+
* from the anchor (`position-anchor` + `anchor()`), so the browser keeps the overlay glued
|
|
188
|
+
* to its anchor while the page scrolls, with no frame of lag; elsewhere it is written as
|
|
189
|
+
* `position: fixed` + left/top and follows scroll/resize from JS. While `open`, the side
|
|
190
|
+
* chosen at open is kept until it no longer fits and the other side does, scrolls inside
|
|
191
|
+
* the overlay itself are ignored, and a size change of either element re-places it.
|
|
192
|
+
* Returns a `reposition` fn for callers that want to nudge it manually (e.g. right when a
|
|
193
|
+
* native popover's `toggle` fires).
|
|
189
194
|
*/
|
|
190
195
|
declare function useAnchoredPosition(anchor: RefObject<HTMLElement | null> | HTMLElement | null | undefined, floatingRef: RefObject<HTMLElement | null>, { open, placement, offset, padding }: UseAnchoredPositionOptions): () => void;
|
|
191
196
|
|
|
@@ -1643,16 +1648,26 @@ interface PopoverProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
1643
1648
|
popover?: 'auto' | 'manual';
|
|
1644
1649
|
/**
|
|
1645
1650
|
* Element (or ref) to anchor against. When set, the popover is placed with
|
|
1646
|
-
* cross-browser JS positioning — flip when it won't fit, shift to stay on-screen
|
|
1647
|
-
*
|
|
1648
|
-
*
|
|
1649
|
-
* the top layer (so it
|
|
1651
|
+
* cross-browser JS positioning — flip when it won't fit, shift to stay on-screen.
|
|
1652
|
+
* Where the browser supports CSS anchor positioning, the placement is written as an
|
|
1653
|
+
* offset from the anchor, so it stays attached while the page scrolls; elsewhere it
|
|
1654
|
+
* follows scroll from JS. The native Popover API still provides the top layer (so it
|
|
1655
|
+
* escapes `overflow` clipping) and light-dismiss.
|
|
1650
1656
|
*/
|
|
1651
1657
|
anchor?: RefObject<HTMLElement | null> | HTMLElement | null;
|
|
1652
1658
|
/** Preferred placement when `anchor` is set. Default `'bottom-start'`. */
|
|
1653
1659
|
placement?: Placement;
|
|
1654
1660
|
/** Gap between the trigger and the popover, in px. Default 6. */
|
|
1655
1661
|
offset?: number;
|
|
1662
|
+
/**
|
|
1663
|
+
* Close the popover once its `anchor` has scrolled completely out of view (outside the
|
|
1664
|
+
* viewport or a clipping ancestor), e.g. a picker opened from an editor that the user
|
|
1665
|
+
* scrolls away from. Focus inside the popover is let go rather than sent back to the
|
|
1666
|
+
* hidden anchor. Needs `anchor`. Default `false`.
|
|
1667
|
+
*/
|
|
1668
|
+
closeWhenAnchorHidden?: boolean;
|
|
1669
|
+
/** Called when `closeWhenAnchorHidden` is about to close the popover. */
|
|
1670
|
+
onAnchorHidden?: () => void;
|
|
1656
1671
|
children?: ReactNode;
|
|
1657
1672
|
}
|
|
1658
1673
|
/**
|
|
@@ -1660,7 +1675,7 @@ interface PopoverProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
1660
1675
|
* things like a filter popover, menu, or rich select by putting controls inside.
|
|
1661
1676
|
* Pass `anchor` to get collision-aware, cross-browser placement (UIX-FIX-02).
|
|
1662
1677
|
*/
|
|
1663
|
-
declare function Popover({ popover, anchor, placement, offset, className, children, ...props }: PopoverProps): react.JSX.Element;
|
|
1678
|
+
declare function Popover({ popover, anchor, placement, offset, closeWhenAnchorHidden, onAnchorHidden, className, children, ...props }: PopoverProps): react.JSX.Element;
|
|
1664
1679
|
|
|
1665
1680
|
interface CommandPaletteProps extends HTMLAttributes<HTMLDivElement> {
|
|
1666
1681
|
/** TENSOR RX-125 (UIX-04): translatable; English default. */
|
|
@@ -39,6 +39,13 @@ interface PositionOptions {
|
|
|
39
39
|
flip?: boolean;
|
|
40
40
|
/** Slide along the cross axis to stay on-screen. Default true. */
|
|
41
41
|
shift?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* The side the element is already on, while it stays open. It is kept unless it no longer
|
|
44
|
+
* fits AND the opposite side does, so a size change or a small scroll never flips an open
|
|
45
|
+
* overlay back and forth. Ignored when it is not on `placement`'s axis. Unset = decide from
|
|
46
|
+
* `placement` alone (an overlay that is opening).
|
|
47
|
+
*/
|
|
48
|
+
stickySide?: Side;
|
|
42
49
|
}
|
|
43
50
|
interface PositionResult {
|
|
44
51
|
/** Viewport-space left, for `position: fixed`. */
|