@astryxdesign/core 0.4.1-canary.9e8a72a → 0.4.1-canary.fb0b7a9
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/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +9 -7
- package/dist/HoverCard/HoverCard.d.ts +1 -1
- package/dist/HoverCard/HoverCard.d.ts.map +1 -1
- package/dist/HoverCard/HoverCard.js +5 -13
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +6 -5
- package/dist/InputGroup/groupStyles.d.ts.map +1 -1
- package/dist/InputGroup/groupStyles.js +7 -2
- package/dist/Layer/layerHost.d.ts +24 -0
- package/dist/Layer/layerHost.d.ts.map +1 -0
- package/dist/Layer/layerHost.js +79 -0
- package/dist/Layer/useLayer.d.ts +14 -6
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +155 -29
- package/dist/astryx.css +3 -7
- package/dist/astryx.umd.js +44 -44
- package/dist/astryx.umd.js.map +4 -4
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +16 -2
- package/package.json +2 -2
- package/src/Button/Button.tsx +5 -3
- package/src/ButtonGroup/ButtonGroup.test.tsx +7 -4
- package/src/HoverCard/HoverCard.doc.mjs +3 -3
- package/src/HoverCard/HoverCard.test.tsx +283 -55
- package/src/HoverCard/HoverCard.tsx +5 -13
- package/src/HoverCard/useHoverCard.tsx +8 -11
- package/src/InputGroup/groupStyles.ts +7 -8
- package/src/Layer/layerHost.test.ts +99 -0
- package/src/Layer/layerHost.ts +141 -0
- package/src/Layer/useLayer.doc.mjs +14 -4
- package/src/Layer/useLayer.test.tsx +318 -7
- package/src/Layer/useLayer.tsx +229 -35
- package/src/Timestamp/Timestamp.test.tsx +40 -22
- package/src/Toolbar/Toolbar.test.tsx +14 -14
- package/src/hooks/index.ts +5 -1
- package/src/hooks/useFocusTrap.test.tsx +16 -0
- package/src/hooks/useFocusTrap.ts +20 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Button.d.ts","sourceRoot":"","sources":["../../src/Button/Button.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAwB,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAC5D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAC,SAAS,EAAE,SAAS,EAAC,MAAM,gBAAgB,CAAC;AACzD,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAuB3C,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,eAAe,CAAC;AAIrD,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,SAAS,CAAC;AAuG9C,QAAA,MAAM,UAAU;;;;;;;;;;EAUd,CAAC;AAkFH;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,gBAAgB,CAAC;AAEnD;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,OAAO,UAAU,CAAC;AAEjD,MAAM,WAAW,WAAY,SAAQ,SAAS,CAAC,iBAAiB,CAAC;IAC/D,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACnC,oDAAoD;IACpD,IAAI,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;IACrC,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IAChD,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB;;;OAGG;IACH,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB;;;;OAIG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;;;OAQG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;OAGG;IACH,OAAO,CAAC,EAAE,KAAK,CAAC,iBAAiB,CAAC,iBAAiB,CAAC,CAAC;IACrD;;OAEG;IACH,WAAW,CAAC,EAAE,CACZ,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,iBAAiB,CAAC,KACnC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B;;OAEG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;;OAGG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,EAAE,CAAC,EAAE,iBAAiB,CAAC;IACvB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;
|
|
1
|
+
{"version":3,"file":"Button.d.ts","sourceRoot":"","sources":["../../src/Button/Button.tsx"],"names":[],"mappings":"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"}
|
package/dist/Button/Button.js
CHANGED
|
@@ -253,8 +253,10 @@ const loadingStyles = {
|
|
|
253
253
|
*
|
|
254
254
|
* Layers always carry the native `popover` attribute (useLayer.tsx), and a
|
|
255
255
|
* popover is never an in-flow member — it is `display: none` until shown, then
|
|
256
|
-
* promoted to the top layer.
|
|
257
|
-
*
|
|
256
|
+
* promoted to the top layer. Context layers also retain an inert `<template>`
|
|
257
|
+
* marker so they can re-resolve their JSX position. Neither element is a group
|
|
258
|
+
* member, so "last member" is: no following element sibling besides those two
|
|
259
|
+
* pieces of layer infrastructure.
|
|
258
260
|
*
|
|
259
261
|
* Reading it the other way round — marking the *buttons* and testing for a
|
|
260
262
|
* marked sibling — is the trap: it silently reclassifies anything it doesn't
|
|
@@ -270,13 +272,13 @@ const loadingStyles = {
|
|
|
270
272
|
* The leading edge still uses `:first-child` — a member's button always precedes
|
|
271
273
|
* its own layer, so the first button is genuinely `:first-child`.
|
|
272
274
|
*/
|
|
273
|
-
const IS_LAST_ITEM = ':not(:has(~ *:not([popover])))';
|
|
275
|
+
const IS_LAST_ITEM = ':not(:has(~ *:not([popover]):not(template)))';
|
|
274
276
|
const groupStyles = {
|
|
275
277
|
horizontal: {
|
|
276
278
|
krdFHd: "x15mokao x8eehn2",
|
|
277
279
|
kVL7Gh: "xbiv7yw x1xrp5p4",
|
|
278
|
-
kfmiAY: "x1ga7v0g
|
|
279
|
-
kT0f0o: "x16uus16
|
|
280
|
+
kfmiAY: "x1ga7v0g x1tzxhge",
|
|
281
|
+
kT0f0o: "x16uus16 x1rmb4wm",
|
|
280
282
|
k2ei4v: "xgbv0en x1pjv70x",
|
|
281
283
|
kVhnKS: "x1t7ytsu xyf0ibl",
|
|
282
284
|
kGJrpR: "x1j92z86",
|
|
@@ -285,8 +287,8 @@ const groupStyles = {
|
|
|
285
287
|
vertical: {
|
|
286
288
|
krdFHd: "x15mokao x8eehn2",
|
|
287
289
|
kfmiAY: "x1ga7v0g x2qxyot",
|
|
288
|
-
kVL7Gh: "xbiv7yw
|
|
289
|
-
kT0f0o: "x16uus16
|
|
290
|
+
kVL7Gh: "xbiv7yw xturttb",
|
|
291
|
+
kT0f0o: "x16uus16 x1rmb4wm",
|
|
290
292
|
kEafiO: "x11xkdxz x1g31smg",
|
|
291
293
|
kPef9Z: "x13fuv20 x1d9v4yf",
|
|
292
294
|
kLZC3w: "x1pc3f07",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @file HoverCard.tsx
|
|
3
3
|
* @input Uses React, useHoverCard hook
|
|
4
4
|
* @output Exports HoverCard component for hover/focus triggered layers
|
|
5
|
-
* @position Layer component;
|
|
5
|
+
* @position Layer component; inline-safe trigger wrapper, floating layer hosted by useLayer
|
|
6
6
|
*
|
|
7
7
|
* SYNC: When modified, update these files to stay in sync:
|
|
8
8
|
* - /packages/core/src/HoverCard/HoverCard.test.tsx
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HoverCard.d.ts","sourceRoot":"","sources":["../../src/HoverCard/HoverCard.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAsB,KAAK,YAAY,EAAE,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAG7E,OAAO,EAAe,KAAK,qBAAqB,EAAC,MAAM,gBAAgB,CAAC;AACxE,OAAO,KAAK,EAAC,cAAc,EAAE,cAAc,EAAC,MAAM,mBAAmB,CAAC;AACtE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAG5C,YAAY,EAAC,qBAAqB,EAAC,MAAM,gBAAgB,CAAC;AAiB1D,MAAM,WAAW,cAAe,SAAQ,IAAI,CAC1C,SAAS,EACT,QAAQ,GAAG,WAAW,GAAG,OAAO,CACjC;IACC;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IAEpB;;OAEG;IACH,OAAO,EAAE,SAAS,CAAC;IAEnB;;;OAGG;IACH,SAAS,CAAC,EAAE,cAAc,CAAC;IAE3B;;;OAGG;IACH,SAAS,CAAC,EAAE,cAAc,CAAC;IAE3B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,qBAAqB,CAAC;IAErC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAEzC;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAEtC;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAiBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,SAAS,CAAC,EACxB,QAAQ,EACR,OAAO,EACP,SAAmB,EACnB,SAAoB,EACpB,KAAW,EACX,SAAe,EACf,YAAqB,EACrB,SAAgB,EAChB,KAAK,EACL,YAAY,EACZ,kBAA2B,EAC3B,MAAM,EACN,aAAa,EACb,MAAM,EACN,SAAS,EACT,KAAK,GACN,EAAE,cAAc,GAAG,YAAY,
|
|
1
|
+
{"version":3,"file":"HoverCard.d.ts","sourceRoot":"","sources":["../../src/HoverCard/HoverCard.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAsB,KAAK,YAAY,EAAE,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAG7E,OAAO,EAAe,KAAK,qBAAqB,EAAC,MAAM,gBAAgB,CAAC;AACxE,OAAO,KAAK,EAAC,cAAc,EAAE,cAAc,EAAC,MAAM,mBAAmB,CAAC;AACtE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAG5C,YAAY,EAAC,qBAAqB,EAAC,MAAM,gBAAgB,CAAC;AAiB1D,MAAM,WAAW,cAAe,SAAQ,IAAI,CAC1C,SAAS,EACT,QAAQ,GAAG,WAAW,GAAG,OAAO,CACjC;IACC;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IAEpB;;OAEG;IACH,OAAO,EAAE,SAAS,CAAC;IAEnB;;;OAGG;IACH,SAAS,CAAC,EAAE,cAAc,CAAC;IAE3B;;;OAGG;IACH,SAAS,CAAC,EAAE,cAAc,CAAC;IAE3B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,qBAAqB,CAAC;IAErC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAEzC;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAEtC;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAiBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,SAAS,CAAC,EACxB,QAAQ,EACR,OAAO,EACP,SAAmB,EACnB,SAAoB,EACpB,KAAW,EACX,SAAe,EACf,YAAqB,EACrB,SAAgB,EAChB,KAAK,EACL,YAAY,EACZ,kBAA2B,EAC3B,MAAM,EACN,aAAa,EACb,MAAM,EACN,SAAS,EACT,KAAK,GACN,EAAE,cAAc,GAAG,YAAY,CAyG/B;yBA1He,SAAS"}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* @file HoverCard.tsx
|
|
7
7
|
* @input Uses React, useHoverCard hook
|
|
8
8
|
* @output Exports HoverCard component for hover/focus triggered layers
|
|
9
|
-
* @position Layer component;
|
|
9
|
+
* @position Layer component; inline-safe trigger wrapper, floating layer hosted by useLayer
|
|
10
10
|
*
|
|
11
11
|
* SYNC: When modified, update these files to stay in sync:
|
|
12
12
|
* - /packages/core/src/HoverCard/HoverCard.test.tsx
|
|
@@ -127,18 +127,10 @@ export function HoverCard({
|
|
|
127
127
|
};
|
|
128
128
|
}, [textOnly, hoverCard.ref, hoverCard.describedBy]);
|
|
129
129
|
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
// anchor positioning resolves the trigger reference regardless of where the
|
|
135
|
-
// element sits in the DOM, so no portal is needed to "escape" layout.
|
|
136
|
-
//
|
|
137
|
-
// The layer renders as inline-safe phrasing markup (a `<span>`, see
|
|
138
|
-
// useHoverCard), which stays put inside a `<p>` instead of being reparented
|
|
139
|
-
// by the HTML parser. That keeps the server markup and the first client
|
|
140
|
-
// render identical, so there is no hydration mismatch — and it preserves the
|
|
141
|
-
// inline-safety guarantee (no block elements injected into a paragraph).
|
|
130
|
+
// While closed, useLayer leaves only an inert <template> marker at this JSX
|
|
131
|
+
// position. When the card needs to open, it uses that marker to keep the
|
|
132
|
+
// final layer inline when the parent is safe or portal it outside a <p>,
|
|
133
|
+
// link, or other ancestor that cannot contain it safely.
|
|
142
134
|
const renderedHoverCard = hoverCard.renderHoverCard(content, {
|
|
143
135
|
xstyle,
|
|
144
136
|
className,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useHoverCard.d.ts","sourceRoot":"","sources":["../../src/HoverCard/useHoverCard.tsx"],"names":[],"mappings":"AAIA;;;;;;;;GAQG;AAEH,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,WAAW,EACjB,MAAM,OAAO,CAAC;AAEf,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,cAAc,EACpB,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"useHoverCard.d.ts","sourceRoot":"","sources":["../../src/HoverCard/useHoverCard.tsx"],"names":[],"mappings":"AAIA;;;;;;;;GAQG;AAEH,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,WAAW,EACjB,MAAM,OAAO,CAAC;AAEf,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,cAAc,EACpB,MAAM,mBAAmB,CAAC;AA4B3B;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEhE,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,SAAS,CAAC,EAAE,cAAc,CAAC;IAE3B;;;OAGG;IACH,SAAS,CAAC,EAAE,cAAc,CAAC;IAE3B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,qBAAqB,CAAC;IAErC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IAEpB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,GAAG,EAAE,WAAW,CAAC,WAAW,CAAC,CAAC;IAE9B;;;OAGG;IACH,WAAW,EAAE,WAAW,CAAC,WAAW,CAAC,CAAC;IAEtC;;;;OAIG;IACH,cAAc,EAAE,WAAW,CAAC,WAAW,CAAC,CAAC;IAEzC;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;;;;;;OAOG;IACH,eAAe,EAAE,CACf,QAAQ,EAAE,SAAS,EACnB,KAAK,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,aAAa,CAAC,KAC5C,SAAS,CAAC;IAEf;;OAEG;IACH,IAAI,EAAE,MAAM,IAAI,CAAC;IAEjB;;OAEG;IACH,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB;AAyBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,gBAAqB,GAAG,eAAe,CAkT5E"}
|
|
@@ -88,6 +88,7 @@ export function useHoverCard(options = {}) {
|
|
|
88
88
|
} = options;
|
|
89
89
|
const layer = useLayer({
|
|
90
90
|
mode: 'context',
|
|
91
|
+
lazyMount: true,
|
|
91
92
|
onShow,
|
|
92
93
|
onHide
|
|
93
94
|
});
|
|
@@ -254,15 +255,15 @@ export function useHoverCard(options = {}) {
|
|
|
254
255
|
// Consumer surface style props land on the layer container — the
|
|
255
256
|
// themed surface (bg/radius/shadow) where the theme class lives — so
|
|
256
257
|
// customizing the card targets the same element as the theme. The inner
|
|
257
|
-
//
|
|
258
|
+
// div keeps `styles.content` for padding.
|
|
258
259
|
xstyle: [popoverXstyle, layerAnimations[renderPlacement], props?.xstyle],
|
|
259
260
|
className: props?.className ? `${themeClassName} ${props.className}` : themeClassName,
|
|
260
261
|
style: props?.style,
|
|
261
|
-
//
|
|
262
|
-
//
|
|
263
|
-
as: '
|
|
262
|
+
// useLayer mounts only after it has verified or corrected the parent,
|
|
263
|
+
// so rich HoverCard content can use block-safe markup.
|
|
264
|
+
as: 'div'
|
|
264
265
|
};
|
|
265
|
-
return layer.render(/*#__PURE__*/_jsx("
|
|
266
|
+
return layer.render(/*#__PURE__*/_jsx("div", {
|
|
266
267
|
...{
|
|
267
268
|
className: "x1lliihq x1vlblms xvmdzux x126nfab x1t818jl"
|
|
268
269
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"groupStyles.d.ts","sourceRoot":"","sources":["../../src/InputGroup/groupStyles.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"groupStyles.d.ts","sourceRoot":"","sources":["../../src/InputGroup/groupStyles.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAQ3C,eAAO,MAAM,WAAW;;;;;;;;;;;;;;EA6BtB,CAAC"}
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
import * as stylex from '@stylexjs/stylex';
|
|
11
11
|
import "../theme/tokens.stylex.js";
|
|
12
12
|
import { radiusVars, borderVars } from "../theme/tokens.stylex.js";
|
|
13
|
+
|
|
14
|
+
// A grouped control may be followed by context-layer infrastructure rather
|
|
15
|
+
// than another control. Neither the inert marker nor the popover is a visual
|
|
16
|
+
// group member, so skip both when finding the trailing edge.
|
|
17
|
+
const IS_LAST_ITEM = ':not(:has(~ *:not([popover]):not(template)))';
|
|
13
18
|
export const groupStyles = {
|
|
14
19
|
inGroup: {
|
|
15
20
|
kUk6DE: "x98rzlu",
|
|
@@ -18,8 +23,8 @@ export const groupStyles = {
|
|
|
18
23
|
keTefX: "xd10s4z x1pwwqoy",
|
|
19
24
|
krdFHd: "x15mokao x8eehn2",
|
|
20
25
|
kVL7Gh: "xbiv7yw x1xrp5p4",
|
|
21
|
-
kfmiAY: "x1ga7v0g
|
|
22
|
-
kT0f0o: "x16uus16
|
|
26
|
+
kfmiAY: "x1ga7v0g x1tzxhge",
|
|
27
|
+
kT0f0o: "x16uus16 x1rmb4wm",
|
|
23
28
|
kXnDq0: "x1rm9qnc",
|
|
24
29
|
$$css: true
|
|
25
30
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Return the corrective portal target for a layer's intended inline parent.
|
|
3
|
+
*
|
|
4
|
+
* A null result means the inline parent and all of its ancestors are safe, so
|
|
5
|
+
* the layer should stay at its JSX position. Otherwise the result is the
|
|
6
|
+
* nearest element outside every unsafe ancestor around that position.
|
|
7
|
+
*
|
|
8
|
+
* Walking up from the actual render position (rather than the trigger, or
|
|
9
|
+
* portaling to `document.body`) keeps the two things a layer inherits there:
|
|
10
|
+
*
|
|
11
|
+
* - **Theme.** Theme scopes live on DOM ancestors. Staying as close as
|
|
12
|
+
* possible to the intended render position preserves their component rules
|
|
13
|
+
* and custom properties; `document.body` may sit outside a nested scope.
|
|
14
|
+
* - **Tab order.** Sequential focus follows DOM order, so a host near the
|
|
15
|
+
* trigger keeps the layer's focusables next to it. (`show()` also passes
|
|
16
|
+
* the trigger as the popover's invoker `source`, which pins focus order to
|
|
17
|
+
* the invoker in browsers that support it.)
|
|
18
|
+
*
|
|
19
|
+
* The outermost unsafe ancestor matters. A safe div may itself sit inside an
|
|
20
|
+
* anchor; stopping at that div would still put the layer's buttons inside the
|
|
21
|
+
* link. Walking the whole chain ensures the target is outside both.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveLayerPortalTarget(inlineParent: HTMLElement | null): HTMLElement | null;
|
|
24
|
+
//# sourceMappingURL=layerHost.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layerHost.d.ts","sourceRoot":"","sources":["../../src/Layer/layerHost.ts"],"names":[],"mappings":"AAqGA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,wBAAwB,CACtC,YAAY,EAAE,WAAW,GAAG,IAAI,GAC/B,WAAW,GAAG,IAAI,CAepB"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file layerHost.ts
|
|
5
|
+
* @input Uses the layer's intended inline parent element
|
|
6
|
+
* @output Exports resolveLayerPortalTarget, the corrective portal target
|
|
7
|
+
* @position Layer utility; used by useLayer to place the popover in the DOM
|
|
8
|
+
*
|
|
9
|
+
* SYNC: When modified, update:
|
|
10
|
+
* - /packages/core/src/Layer/layerHost.test.ts
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Ancestors a layer must not be hosted inside.
|
|
15
|
+
*
|
|
16
|
+
* Two overlapping reasons, both verified in Chrome:
|
|
17
|
+
*
|
|
18
|
+
* 1. The HTML parser owns server markup. `<p>` and the heading-ish elements
|
|
19
|
+
* take phrasing content only, so a block element inside one makes the
|
|
20
|
+
* parser close the paragraph and reparent the rest — the layer's content
|
|
21
|
+
* ends up in the page instead of the popover. A nested `<a>` triggers the
|
|
22
|
+
* same tearing through the adoption agency algorithm.
|
|
23
|
+
* 2. Interactive ancestors capture the layer's own interactions. A card
|
|
24
|
+
* hosted inside an `<a>` or `<button>` puts its links and buttons inside
|
|
25
|
+
* that control: clicking one navigates the wrapping link, and every
|
|
26
|
+
* focusable in the card joins the control's own tab stop.
|
|
27
|
+
*
|
|
28
|
+
* Inline formatting elements are on the list for a third, milder reason: a
|
|
29
|
+
* layer hosted in one inherits its typography (font-size, text-align), so a
|
|
30
|
+
* card in a 13px centered paragraph renders 13px and centered.
|
|
31
|
+
*/
|
|
32
|
+
const UNSAFE_HOSTS = new Set([
|
|
33
|
+
// Phrasing-content-only containers
|
|
34
|
+
'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'dt', 'pre', 'legend', 'data', 'dfn', 'meter', 'output', 'progress', 'option', 'optgroup',
|
|
35
|
+
// Structural containers whose direct children are restricted. An inert
|
|
36
|
+
// <template> marker is valid script-supporting content in these positions,
|
|
37
|
+
// but the eventual div/span layer is not.
|
|
38
|
+
'table', 'thead', 'tbody', 'tfoot', 'tr', 'colgroup', 'ul', 'ol', 'menu', 'dl', 'select', 'datalist', 'picture', 'hgroup', 'ruby', 'rt', 'rp',
|
|
39
|
+
// Interactive containers
|
|
40
|
+
'a', 'button', 'label', 'summary',
|
|
41
|
+
// Inline formatting context
|
|
42
|
+
'span', 'em', 'strong', 'b', 'i', 'u', 's', 'small', 'mark', 'code', 'kbd', 'samp', 'var', 'sub', 'sup', 'abbr', 'cite', 'q', 'time', 'bdi', 'bdo', 'ins', 'del']);
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Return the corrective portal target for a layer's intended inline parent.
|
|
46
|
+
*
|
|
47
|
+
* A null result means the inline parent and all of its ancestors are safe, so
|
|
48
|
+
* the layer should stay at its JSX position. Otherwise the result is the
|
|
49
|
+
* nearest element outside every unsafe ancestor around that position.
|
|
50
|
+
*
|
|
51
|
+
* Walking up from the actual render position (rather than the trigger, or
|
|
52
|
+
* portaling to `document.body`) keeps the two things a layer inherits there:
|
|
53
|
+
*
|
|
54
|
+
* - **Theme.** Theme scopes live on DOM ancestors. Staying as close as
|
|
55
|
+
* possible to the intended render position preserves their component rules
|
|
56
|
+
* and custom properties; `document.body` may sit outside a nested scope.
|
|
57
|
+
* - **Tab order.** Sequential focus follows DOM order, so a host near the
|
|
58
|
+
* trigger keeps the layer's focusables next to it. (`show()` also passes
|
|
59
|
+
* the trigger as the popover's invoker `source`, which pins focus order to
|
|
60
|
+
* the invoker in browsers that support it.)
|
|
61
|
+
*
|
|
62
|
+
* The outermost unsafe ancestor matters. A safe div may itself sit inside an
|
|
63
|
+
* anchor; stopping at that div would still put the layer's buttons inside the
|
|
64
|
+
* link. Walking the whole chain ensures the target is outside both.
|
|
65
|
+
*/
|
|
66
|
+
export function resolveLayerPortalTarget(inlineParent) {
|
|
67
|
+
if (!inlineParent) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
let outermostUnsafe = null;
|
|
71
|
+
let node = inlineParent;
|
|
72
|
+
while (node) {
|
|
73
|
+
if (UNSAFE_HOSTS.has(node.tagName.toLowerCase())) {
|
|
74
|
+
outermostUnsafe = node;
|
|
75
|
+
}
|
|
76
|
+
node = node.parentElement;
|
|
77
|
+
}
|
|
78
|
+
return outermostUnsafe?.parentElement ?? null;
|
|
79
|
+
}
|
package/dist/Layer/useLayer.d.ts
CHANGED
|
@@ -94,12 +94,11 @@ export interface ContextRenderProps {
|
|
|
94
94
|
/**
|
|
95
95
|
* HTML tag to render the popover container as.
|
|
96
96
|
*
|
|
97
|
-
* Defaults to `'div'`.
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
* the same on either tag.
|
|
97
|
+
* Defaults to `'div'`. Context layers render an inert `<template>` marker at
|
|
98
|
+
* the JSX position. The marker's parent is checked before the requested
|
|
99
|
+
* container mounts there or portals outside ancestors that cannot safely
|
|
100
|
+
* contain it. The marker remains available to detect a new parent if the
|
|
101
|
+
* render call moves. With `lazyMount`, the first check waits until `show()`.
|
|
103
102
|
*
|
|
104
103
|
* @default 'div'
|
|
105
104
|
*/
|
|
@@ -162,6 +161,15 @@ interface BaseLayerOptions {
|
|
|
162
161
|
*/
|
|
163
162
|
export interface ContextLayerOptions extends BaseLayerOptions {
|
|
164
163
|
mode: 'context';
|
|
164
|
+
/**
|
|
165
|
+
* Defer mounting the final layer and resolving its inline/portal position
|
|
166
|
+
* until `show()` is requested. Hiding unmounts it while the inert marker
|
|
167
|
+
* remains at the JSX position. Use this when rich content must never enter
|
|
168
|
+
* an unsafe ancestor, even briefly, and does not need to exist while closed.
|
|
169
|
+
*
|
|
170
|
+
* @default false
|
|
171
|
+
*/
|
|
172
|
+
lazyMount?: boolean;
|
|
165
173
|
}
|
|
166
174
|
/**
|
|
167
175
|
* Options for fixed mode (manual positioning)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useLayer.d.ts","sourceRoot":"","sources":["../../src/Layer/useLayer.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,EAMZ,KAAK,SAAS,EACd,KAAK,WAAW,EACjB,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"useLayer.d.ts","sourceRoot":"","sources":["../../src/Layer/useLayer.tsx"],"names":[],"mappings":"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"}
|
package/dist/Layer/useLayer.js
CHANGED
|
@@ -15,10 +15,12 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import React, { useCallback, useEffect, useId, useRef, useState } from 'react';
|
|
17
17
|
import * as stylex from '@stylexjs/stylex';
|
|
18
|
+
import { createPortal } from 'react-dom';
|
|
18
19
|
import { addAnchorName, removeAnchorName } from "./anchorName.js";
|
|
20
|
+
import { resolveLayerPortalTarget } from "./layerHost.js";
|
|
19
21
|
import "../theme/tokens.stylex.js";
|
|
20
22
|
import { typographyVars } from "../theme/tokens.stylex.js";
|
|
21
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
23
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
22
24
|
const _temp = {
|
|
23
25
|
keoZOQ: "x1vhfslr",
|
|
24
26
|
k1K539: "xlm3tn6",
|
|
@@ -100,17 +102,40 @@ const styles = {
|
|
|
100
102
|
function toCssLength(value) {
|
|
101
103
|
return typeof value === 'number' ? `${value}px` : value;
|
|
102
104
|
}
|
|
105
|
+
function readPortalWritingContext(element, portalTarget) {
|
|
106
|
+
const view = element.ownerDocument.defaultView;
|
|
107
|
+
if (!view) {
|
|
108
|
+
return {};
|
|
109
|
+
}
|
|
110
|
+
const sourceStyle = view.getComputedStyle(element);
|
|
111
|
+
const targetStyle = view.getComputedStyle(portalTarget);
|
|
112
|
+
|
|
113
|
+
// Do not snapshot custom properties here. The portal target is the closest
|
|
114
|
+
// safe ancestor, so theme variables continue to inherit and update there.
|
|
115
|
+
// These two properties can be set on the unsafe chain itself and directly
|
|
116
|
+
// affect the logical anchor-positioning keywords used by the layer. Only
|
|
117
|
+
// override values the portal would actually lose; matching values should
|
|
118
|
+
// keep inheriting from the target so later direction changes remain live.
|
|
119
|
+
return {
|
|
120
|
+
...(sourceStyle.direction !== targetStyle.direction && {
|
|
121
|
+
direction: sourceStyle.direction
|
|
122
|
+
}),
|
|
123
|
+
...(sourceStyle.writingMode !== targetStyle.writingMode && {
|
|
124
|
+
writingMode: sourceStyle.writingMode
|
|
125
|
+
})
|
|
126
|
+
};
|
|
127
|
+
}
|
|
103
128
|
|
|
104
129
|
/**
|
|
105
130
|
* Map logical placement/alignment to a CSS position-area value.
|
|
106
131
|
*
|
|
107
132
|
* Uses the self-* logical keyword family: the inline axis resolves against
|
|
108
|
-
* the popover's own
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
133
|
+
* the popover's own direction (inherited inline or preserved when portaled),
|
|
134
|
+
* so it mirrors in RTL without placement-specific JS. The block axis is
|
|
135
|
+
* direction-neutral but must come from the same keyword family — mixing
|
|
136
|
+
* physical `top` with `self-inline-*` produces an invalid position-area
|
|
137
|
+
* (computes to `none`, which pins the popover to the viewport corner because
|
|
138
|
+
* styles.base zeroes the UA margins).
|
|
114
139
|
*
|
|
115
140
|
* Note the plain logical family (`inline-start`, no `self-`) is NOT a
|
|
116
141
|
* substitute: it resolves against the containing block — the page root for
|
|
@@ -182,36 +207,101 @@ export function useLayer(options) {
|
|
|
182
207
|
onHide,
|
|
183
208
|
lightDismiss = false
|
|
184
209
|
} = options;
|
|
210
|
+
const lazyMount = mode === 'context' ? options.lazyMount ?? false : false;
|
|
185
211
|
const id = useId();
|
|
186
212
|
const anchorId = `--astryx-layer-${id.replace(/:/g, '')}`;
|
|
187
213
|
const [isOpen, setIsOpen] = useState(false);
|
|
188
214
|
const popoverRef = useRef(null);
|
|
215
|
+
// The DOM element on which the current logical open state was applied.
|
|
216
|
+
// A portal target change replaces the popover element; retaining the old
|
|
217
|
+
// reference lets the ref callback recognize and reopen its replacement.
|
|
218
|
+
const openedPopoverRef = useRef(null);
|
|
189
219
|
const triggerRef = useRef(null);
|
|
220
|
+
// Context layers place a persistent inert marker at their real JSX position.
|
|
221
|
+
// Its parent tells us whether the final layer can stay inline or needs a
|
|
222
|
+
// corrective portal, including after the render call moves.
|
|
223
|
+
const sentinelRef = useRef(null);
|
|
224
|
+
const contextMountRef = useRef(null);
|
|
225
|
+
const [contextMount, setContextMount] = useState(null);
|
|
226
|
+
// A show() that arrives before the final layer mounts is replayed when its
|
|
227
|
+
// popover ref attaches.
|
|
228
|
+
const pendingShowRef = useRef(false);
|
|
190
229
|
|
|
191
230
|
// Ref mirrors isOpen for synchronous reads inside show/hide.
|
|
192
231
|
// State drives re-renders; the ref lets the imperative calls avoid
|
|
193
232
|
// stale-closure reads of the previous isOpen value.
|
|
194
233
|
const isOpenRef = useRef(false);
|
|
234
|
+
const showPopoverElement = useCallback(popover => {
|
|
235
|
+
// Finding infra-4: the Popover API is unsupported on Safari <17 and
|
|
236
|
+
// Firefox <125. On those browsers `showPopover` does not exist, so fall
|
|
237
|
+
// back to plain visibility instead of throwing.
|
|
238
|
+
if (typeof popover.showPopover === 'function') {
|
|
239
|
+
// The trigger is passed as the popover's invoker `source`: a layer
|
|
240
|
+
// hosted away from its trigger then still takes its sequential focus
|
|
241
|
+
// order (and its popover nesting) from the trigger rather than from its
|
|
242
|
+
// own DOM position. Browsers without the option ignore it.
|
|
243
|
+
popover.showPopover({
|
|
244
|
+
source: triggerRef.current ?? undefined
|
|
245
|
+
});
|
|
246
|
+
} else {
|
|
247
|
+
popover.style.display = 'block';
|
|
248
|
+
}
|
|
249
|
+
openedPopoverRef.current = popover;
|
|
250
|
+
}, []);
|
|
251
|
+
const isCurrentContextPopover = useCallback(popover => {
|
|
252
|
+
if (mode !== 'context') {
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
const mount = contextMountRef.current;
|
|
256
|
+
if (mount === null) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
const expectedParent = mount.portalTarget ?? sentinelRef.current?.parentElement ?? null;
|
|
260
|
+
return popover.parentElement === expectedParent;
|
|
261
|
+
}, [mode]);
|
|
262
|
+
const requestContextMount = useCallback(() => {
|
|
263
|
+
if (mode !== 'context') {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const sentinel = sentinelRef.current;
|
|
267
|
+
const inlineParent = sentinel?.parentElement ?? null;
|
|
268
|
+
if (!sentinel || !inlineParent) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const portalTarget = resolveLayerPortalTarget(inlineParent);
|
|
272
|
+
const mount = {
|
|
273
|
+
portalTarget,
|
|
274
|
+
portalStyle: portalTarget ? readPortalWritingContext(sentinel, portalTarget) : {}
|
|
275
|
+
};
|
|
276
|
+
contextMountRef.current = mount;
|
|
277
|
+
setContextMount(mount);
|
|
278
|
+
}, [mode]);
|
|
279
|
+
const clearContextMount = useCallback(() => {
|
|
280
|
+
if (mode !== 'context' || !lazyMount) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
contextMountRef.current = null;
|
|
284
|
+
setContextMount(null);
|
|
285
|
+
}, [mode, lazyMount]);
|
|
195
286
|
const show = useCallback(() => {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
popover.style.display = 'block';
|
|
208
|
-
}
|
|
287
|
+
// A context popover left over until React commits a previous hide must not
|
|
288
|
+
// be reopened. The synchronous mount ref is the source of truth.
|
|
289
|
+
const candidate = popoverRef.current;
|
|
290
|
+
const popover = candidate && isCurrentContextPopover(candidate) ? candidate : null;
|
|
291
|
+
if (!popover) {
|
|
292
|
+
pendingShowRef.current = true;
|
|
293
|
+
requestContextMount();
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (!isOpenRef.current) {
|
|
297
|
+
showPopoverElement(popover);
|
|
209
298
|
isOpenRef.current = true;
|
|
210
299
|
setIsOpen(true);
|
|
211
300
|
onShow?.();
|
|
212
301
|
}
|
|
213
|
-
}, [onShow]);
|
|
302
|
+
}, [onShow, requestContextMount, showPopoverElement, isCurrentContextPopover]);
|
|
214
303
|
const hide = useCallback(() => {
|
|
304
|
+
pendingShowRef.current = false;
|
|
215
305
|
if (isOpenRef.current) {
|
|
216
306
|
const el = popoverRef.current;
|
|
217
307
|
// See finding infra-4 note in `show`: mirror the same guard on hide so
|
|
@@ -223,11 +313,13 @@ export function useLayer(options) {
|
|
|
223
313
|
el.style.display = 'none';
|
|
224
314
|
}
|
|
225
315
|
}
|
|
316
|
+
openedPopoverRef.current = null;
|
|
226
317
|
isOpenRef.current = false;
|
|
227
318
|
setIsOpen(false);
|
|
228
319
|
onHide?.();
|
|
229
320
|
}
|
|
230
|
-
|
|
321
|
+
clearContextMount();
|
|
322
|
+
}, [onHide, clearContextMount]);
|
|
231
323
|
|
|
232
324
|
// Ref for trigger element (context mode only)
|
|
233
325
|
const ref = mode === 'context' ? el => {
|
|
@@ -255,11 +347,13 @@ export function useLayer(options) {
|
|
|
255
347
|
const handleToggle = useCallback(e => {
|
|
256
348
|
const toggleEvent = e;
|
|
257
349
|
if (toggleEvent.newState === 'closed' && isOpenRef.current) {
|
|
350
|
+
openedPopoverRef.current = null;
|
|
258
351
|
isOpenRef.current = false;
|
|
259
352
|
setIsOpen(false);
|
|
260
353
|
onHide?.();
|
|
354
|
+
clearContextMount();
|
|
261
355
|
}
|
|
262
|
-
}, [onHide]);
|
|
356
|
+
}, [onHide, clearContextMount]);
|
|
263
357
|
|
|
264
358
|
// Ref callback for popover element — sets up the `toggle` listener.
|
|
265
359
|
// Tracks the element + handler currently bound so the listener is removed
|
|
@@ -283,7 +377,24 @@ export function useLayer(options) {
|
|
|
283
377
|
const popoverRefCallback = useCallback(el => {
|
|
284
378
|
popoverRef.current = el;
|
|
285
379
|
bindToggleListener(el, handleToggle);
|
|
286
|
-
|
|
380
|
+
if (el && pendingShowRef.current) {
|
|
381
|
+
pendingShowRef.current = false;
|
|
382
|
+
show();
|
|
383
|
+
} else if (el && isOpenRef.current && openedPopoverRef.current !== el && isCurrentContextPopover(el)) {
|
|
384
|
+
// Changing a portal target remounts the popover. Preserve the logical
|
|
385
|
+
// open state without firing onShow again for the replacement element.
|
|
386
|
+
showPopoverElement(el);
|
|
387
|
+
}
|
|
388
|
+
}, [handleToggle, bindToggleListener, show, showPopoverElement, isCurrentContextPopover]);
|
|
389
|
+
const sentinelRefCallback = useCallback(el => {
|
|
390
|
+
sentinelRef.current = el;
|
|
391
|
+
if (el && (!lazyMount || pendingShowRef.current || isOpenRef.current)) {
|
|
392
|
+
// The render call may have moved while the hook stayed mounted.
|
|
393
|
+
// Resolve again from the newly attached marker rather than reusing a
|
|
394
|
+
// portal target from its previous JSX position.
|
|
395
|
+
requestContextMount();
|
|
396
|
+
}
|
|
397
|
+
}, [lazyMount, requestContextMount]);
|
|
287
398
|
|
|
288
399
|
// Re-bind when the handler identity changes while the element stays mounted,
|
|
289
400
|
// and detach on unmount.
|
|
@@ -302,6 +413,17 @@ export function useLayer(options) {
|
|
|
302
413
|
|
|
303
414
|
// Render function for context mode
|
|
304
415
|
const renderContext = useCallback((children, props) => {
|
|
416
|
+
// Keep the marker mounted after resolving the layer. Apart from giving
|
|
417
|
+
// us the real JSX parent on first show, this lets its ref report when a
|
|
418
|
+
// persistent hook's render call moves to a different host.
|
|
419
|
+
const sentinel = /*#__PURE__*/_jsx("template", {
|
|
420
|
+
ref: sentinelRefCallback
|
|
421
|
+
});
|
|
422
|
+
if (contextMount === null) {
|
|
423
|
+
return /*#__PURE__*/_jsx(_Fragment, {
|
|
424
|
+
children: sentinel
|
|
425
|
+
});
|
|
426
|
+
}
|
|
305
427
|
const {
|
|
306
428
|
placement = 'above',
|
|
307
429
|
alignment = 'center',
|
|
@@ -332,10 +454,10 @@ export function useLayer(options) {
|
|
|
332
454
|
const stylexResult = stylex.props(styles.base, offsetStyle, xstyle);
|
|
333
455
|
const combinedClassName = extraClassName ? `${extraClassName} ${stylexResult.className ?? ''}` : stylexResult.className;
|
|
334
456
|
|
|
335
|
-
//
|
|
336
|
-
//
|
|
337
|
-
//
|
|
338
|
-
|
|
457
|
+
// The marker gives us the actual JSX parent without mounting arbitrary
|
|
458
|
+
// children there. Safe positions preserve the existing DOM order and
|
|
459
|
+
// cascade; unsafe positions use the nearest corrective portal target.
|
|
460
|
+
const layer = /*#__PURE__*/_jsx(Container, {
|
|
339
461
|
ref: popoverRefCallback,
|
|
340
462
|
id: id,
|
|
341
463
|
role: role,
|
|
@@ -345,13 +467,17 @@ export function useLayer(options) {
|
|
|
345
467
|
style: {
|
|
346
468
|
...stylexResult.style,
|
|
347
469
|
...anchorStyle,
|
|
470
|
+
...contextMount.portalStyle,
|
|
348
471
|
...extraStyle
|
|
349
472
|
},
|
|
350
473
|
onMouseEnter: onMouseEnter,
|
|
351
474
|
onMouseLeave: onMouseLeave,
|
|
352
475
|
children: children
|
|
353
476
|
});
|
|
354
|
-
|
|
477
|
+
return /*#__PURE__*/_jsxs(_Fragment, {
|
|
478
|
+
children: [sentinel, contextMount.portalTarget ? /*#__PURE__*/createPortal(layer, contextMount.portalTarget) : layer]
|
|
479
|
+
});
|
|
480
|
+
}, [anchorId, contextMount, id, lightDismiss, popoverRefCallback, sentinelRefCallback]);
|
|
355
481
|
|
|
356
482
|
// Render function for fixed mode
|
|
357
483
|
const renderFixed = useCallback((children, props) => {
|