@tensor_1/react 2.26.2 → 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/index.cjs +224 -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/index.cjs
CHANGED
|
@@ -704,10 +704,15 @@ function fitsOn(side, a, f, vp, offset, padding) {
|
|
|
704
704
|
return spaceOn(side, a, vp, offset) >= need + padding;
|
|
705
705
|
}
|
|
706
706
|
function computePosition(anchor, floating, viewport, options = {}) {
|
|
707
|
-
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;
|
|
708
708
|
const { side: preferred, align } = parsePlacement(placement);
|
|
709
709
|
let side = preferred;
|
|
710
|
-
|
|
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)) {
|
|
711
716
|
const opp = OPPOSITE[side];
|
|
712
717
|
if (fitsOn(opp, anchor, floating, viewport, offset, padding) || spaceOn(opp, anchor, viewport, offset) > spaceOn(side, anchor, viewport, offset)) {
|
|
713
718
|
side = opp;
|
|
@@ -743,41 +748,171 @@ function computePosition(anchor, floating, viewport, options = {}) {
|
|
|
743
748
|
|
|
744
749
|
// src/hooks/useAnchoredPosition.ts
|
|
745
750
|
var useIsomorphicLayoutEffect = typeof document !== "undefined" ? import_react4.useLayoutEffect : import_react4.useEffect;
|
|
746
|
-
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();
|
|
747
788
|
function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-start", offset = 6, padding = 8 }) {
|
|
748
789
|
const opts = (0, import_react4.useRef)({ placement, offset, padding });
|
|
749
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]);
|
|
750
814
|
const reposition = (0, import_react4.useCallback)(() => {
|
|
751
|
-
const anchorEl =
|
|
815
|
+
const anchorEl = resolveAnchor(anchor);
|
|
752
816
|
const floating = floatingRef.current;
|
|
753
817
|
if (!anchorEl || !floating) return;
|
|
754
818
|
const s = floating.style;
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
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
|
+
}
|
|
758
826
|
const a = anchorEl.getBoundingClientRect();
|
|
827
|
+
const size = { width: floating.offsetWidth, height: floating.offsetHeight };
|
|
759
828
|
const { placement: p, offset: o, padding: pad } = opts.current;
|
|
760
829
|
const pos = computePosition(
|
|
761
830
|
{ x: a.x, y: a.y, width: a.width, height: a.height },
|
|
762
|
-
|
|
831
|
+
size,
|
|
763
832
|
{ width: window.innerWidth, height: window.innerHeight },
|
|
764
|
-
{ placement: p, offset: o, padding: pad }
|
|
833
|
+
{ placement: p, offset: o, padding: pad, stickySide: side.current ?? void 0 }
|
|
765
834
|
);
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
floating.dataset.placement = pos.placement;
|
|
769
|
-
|
|
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]);
|
|
770
885
|
useIsomorphicLayoutEffect(() => {
|
|
771
886
|
if (!open) return;
|
|
887
|
+
session.current = true;
|
|
772
888
|
reposition();
|
|
773
|
-
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();
|
|
774
900
|
window.addEventListener("scroll", onScroll, { passive: true, capture: true });
|
|
775
|
-
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);
|
|
776
905
|
return () => {
|
|
777
906
|
window.removeEventListener("scroll", onScroll, true);
|
|
778
|
-
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);
|
|
779
913
|
};
|
|
780
|
-
}, [open, reposition]);
|
|
914
|
+
}, [open, reposition, anchor, floatingRef, release]);
|
|
915
|
+
(0, import_react4.useEffect)(() => () => release(false), [release]);
|
|
781
916
|
return reposition;
|
|
782
917
|
}
|
|
783
918
|
|
|
@@ -1490,17 +1625,36 @@ var helpLabelFor = (thing) => typeof thing === "string" || typeof thing === "num
|
|
|
1490
1625
|
// src/components/Popover.tsx
|
|
1491
1626
|
var import_react14 = require("react");
|
|
1492
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
|
+
};
|
|
1493
1642
|
function Popover({
|
|
1494
1643
|
popover = "auto",
|
|
1495
1644
|
anchor,
|
|
1496
1645
|
placement = "bottom-start",
|
|
1497
1646
|
offset = 6,
|
|
1647
|
+
closeWhenAnchorHidden = false,
|
|
1648
|
+
onAnchorHidden,
|
|
1498
1649
|
className,
|
|
1499
1650
|
children,
|
|
1500
1651
|
...props
|
|
1501
1652
|
}) {
|
|
1502
1653
|
const ref = (0, import_react14.useRef)(null);
|
|
1503
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;
|
|
1504
1658
|
const popoverAttr = { popover };
|
|
1505
1659
|
const reposition = useAnchoredPosition(anchor ?? null, ref, {
|
|
1506
1660
|
open: open && !!anchor,
|
|
@@ -1510,14 +1664,55 @@ function Popover({
|
|
|
1510
1664
|
(0, import_react14.useEffect)(() => {
|
|
1511
1665
|
const el = ref.current;
|
|
1512
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
|
+
};
|
|
1513
1672
|
const sync = () => {
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
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);
|
|
1517
1688
|
};
|
|
1689
|
+
el.addEventListener("beforetoggle", hold);
|
|
1518
1690
|
el.addEventListener("toggle", sync);
|
|
1519
|
-
return () =>
|
|
1691
|
+
return () => {
|
|
1692
|
+
el.removeEventListener("beforetoggle", hold);
|
|
1693
|
+
el.removeEventListener("toggle", sync);
|
|
1694
|
+
el.removeAttribute(PLACING);
|
|
1695
|
+
};
|
|
1520
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]);
|
|
1521
1716
|
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { ref, className: cx("uix-popover", className), ...popoverAttr, ...props, children });
|
|
1522
1717
|
}
|
|
1523
1718
|
|
|
@@ -3615,7 +3810,7 @@ var VIEW_MENU_COLUMN_LABELS = {
|
|
|
3615
3810
|
};
|
|
3616
3811
|
var fill = (template, values) => template.replace(/\{(\w+)\}/g, (match, name) => name in values ? String(values[name]) : match);
|
|
3617
3812
|
var columnText = (column) => column.textLabel ?? (typeof column.label === "string" ? column.label : column.id);
|
|
3618
|
-
var
|
|
3813
|
+
var isPopoverOpen2 = (el) => {
|
|
3619
3814
|
try {
|
|
3620
3815
|
return !!el?.matches(":popover-open");
|
|
3621
3816
|
} catch {
|
|
@@ -3631,14 +3826,14 @@ function ColumnRowMenu({ label, actions, triggerRef }) {
|
|
|
3631
3826
|
const enabled = () => items.current.filter((el) => !!el && !el.disabled);
|
|
3632
3827
|
const close = (returnFocus) => {
|
|
3633
3828
|
const el = document.getElementById(menuId);
|
|
3634
|
-
if (
|
|
3829
|
+
if (isPopoverOpen2(el)) el.hidePopover();
|
|
3635
3830
|
setOpen(false);
|
|
3636
3831
|
if (returnFocus) button.current?.focus();
|
|
3637
3832
|
};
|
|
3638
3833
|
(0, import_react36.useEffect)(() => {
|
|
3639
3834
|
if (!open) return;
|
|
3640
3835
|
const el = document.getElementById(menuId);
|
|
3641
|
-
if (el && typeof el.showPopover === "function" && !
|
|
3836
|
+
if (el && typeof el.showPopover === "function" && !isPopoverOpen2(el)) {
|
|
3642
3837
|
try {
|
|
3643
3838
|
el.showPopover();
|
|
3644
3839
|
} catch {
|
|
@@ -3689,7 +3884,7 @@ function ColumnRowMenu({ label, actions, triggerRef }) {
|
|
|
3689
3884
|
"aria-expanded": open,
|
|
3690
3885
|
"aria-controls": open ? menuId : void 0,
|
|
3691
3886
|
onPointerDown: () => {
|
|
3692
|
-
wasOpenAtPress.current =
|
|
3887
|
+
wasOpenAtPress.current = isPopoverOpen2(document.getElementById(menuId));
|
|
3693
3888
|
},
|
|
3694
3889
|
onClick: () => {
|
|
3695
3890
|
const wasOpen = wasOpenAtPress.current;
|
|
@@ -7661,7 +7856,7 @@ function DiffViewer({
|
|
|
7661
7856
|
const [internalResolutions, setInternalResolutions] = (0, import_react51.useState)({});
|
|
7662
7857
|
const resolutions = controlledResolutions ?? internalResolutions;
|
|
7663
7858
|
const summary = summarizeDiff(entries, resolutions);
|
|
7664
|
-
const
|
|
7859
|
+
const resolve = (path, resolution) => {
|
|
7665
7860
|
if (controlledResolutions === void 0) setInternalResolutions((currentState) => ({ ...currentState, [path]: resolution }));
|
|
7666
7861
|
onResolutionChange?.(path, resolution);
|
|
7667
7862
|
};
|
|
@@ -7708,9 +7903,9 @@ function DiffViewer({
|
|
|
7708
7903
|
/* @__PURE__ */ (0, import_jsx_runtime75.jsx)("pre", { children: renderValue(entry[version], labels.notPresent) })
|
|
7709
7904
|
] }, version)) }),
|
|
7710
7905
|
/* @__PURE__ */ (0, import_jsx_runtime75.jsxs)("div", { className: "uix-diff-viewer__actions", role: "group", "aria-label": fillLabel(labels.resolve, { path: entry.path }), children: [
|
|
7711
|
-
/* @__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: () =>
|
|
7712
|
-
/* @__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: () =>
|
|
7713
|
-
/* @__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 })
|
|
7714
7909
|
] })
|
|
7715
7910
|
] }, entry.path);
|
|
7716
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`. */
|
|
@@ -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`. */
|