@astryxdesign/core 0.4.1 → 0.4.2-canary.01592f7

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.
Files changed (217) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/dist/Avatar/Avatar.d.ts +4 -1
  3. package/dist/Avatar/Avatar.d.ts.map +1 -1
  4. package/dist/Avatar/Avatar.js +19 -23
  5. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
  6. package/dist/AvatarGroup/AvatarGroupOverflow.js +7 -2
  7. package/dist/Button/Button.d.ts.map +1 -1
  8. package/dist/Button/Button.js +9 -7
  9. package/dist/Chat/ChatMessage.d.ts +7 -0
  10. package/dist/Chat/ChatMessage.d.ts.map +1 -1
  11. package/dist/Chat/ChatMessageBubble.d.ts +13 -1
  12. package/dist/Chat/ChatMessageBubble.d.ts.map +1 -1
  13. package/dist/Chat/ChatMessageBubble.js +19 -1
  14. package/dist/Chat/ChatTokenizedText.js +1 -1
  15. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  16. package/dist/CheckboxInput/CheckboxInput.js +5 -0
  17. package/dist/CommandPalette/CommandPaletteFooter.d.ts.map +1 -1
  18. package/dist/CommandPalette/CommandPaletteFooter.js +5 -3
  19. package/dist/ComplexSelector/ComplexSelector.d.ts +38 -4
  20. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  21. package/dist/ComplexSelector/ComplexSelector.js +96 -35
  22. package/dist/ComplexSelector/index.d.ts +2 -2
  23. package/dist/ComplexSelector/index.d.ts.map +1 -1
  24. package/dist/ComplexSelector/index.js +1 -1
  25. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  26. package/dist/DateTimeInput/DateTimeInput.js +2 -2
  27. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  28. package/dist/DropdownMenu/DropdownMenuSubMenu.js +19 -15
  29. package/dist/HoverCard/HoverCard.d.ts +1 -1
  30. package/dist/HoverCard/HoverCard.d.ts.map +1 -1
  31. package/dist/HoverCard/HoverCard.js +5 -13
  32. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  33. package/dist/HoverCard/useHoverCard.js +6 -5
  34. package/dist/InputGroup/groupStyles.d.ts.map +1 -1
  35. package/dist/InputGroup/groupStyles.js +7 -2
  36. package/dist/Layer/layerHost.d.ts +24 -0
  37. package/dist/Layer/layerHost.d.ts.map +1 -0
  38. package/dist/Layer/layerHost.js +79 -0
  39. package/dist/Layer/useLayer.d.ts +14 -6
  40. package/dist/Layer/useLayer.d.ts.map +1 -1
  41. package/dist/Layer/useLayer.js +158 -30
  42. package/dist/Markdown/parser.d.ts.map +1 -1
  43. package/dist/Markdown/parser.js +55 -12
  44. package/dist/NavItem/navItemStyles.stylex.d.ts +17 -5
  45. package/dist/NavItem/navItemStyles.stylex.d.ts.map +1 -1
  46. package/dist/NavItem/navItemStyles.stylex.js +11 -5
  47. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  48. package/dist/RadioList/RadioListItem.js +5 -0
  49. package/dist/SideNav/SideNav.d.ts +7 -9
  50. package/dist/SideNav/SideNav.d.ts.map +1 -1
  51. package/dist/SideNav/SideNav.js +32 -5
  52. package/dist/SideNav/SideNavCollapseButton.d.ts +25 -9
  53. package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
  54. package/dist/SideNav/SideNavCollapseButton.js +39 -15
  55. package/dist/SideNav/SideNavCollapseContext.d.ts +21 -0
  56. package/dist/SideNav/SideNavCollapseContext.d.ts.map +1 -1
  57. package/dist/SideNav/SideNavCollapseContext.js +16 -2
  58. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  59. package/dist/SideNav/SideNavHeading.js +89 -32
  60. package/dist/SideNav/SideNavItem.d.ts +7 -2
  61. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  62. package/dist/SideNav/SideNavItem.js +119 -75
  63. package/dist/SideNav/SideNavSection.d.ts.map +1 -1
  64. package/dist/SideNav/SideNavSection.js +7 -16
  65. package/dist/SideNav/index.d.ts +1 -1
  66. package/dist/SideNav/index.d.ts.map +1 -1
  67. package/dist/Slider/Slider.d.ts.map +1 -1
  68. package/dist/Slider/Slider.js +56 -15
  69. package/dist/Switch/Switch.d.ts.map +1 -1
  70. package/dist/Switch/Switch.js +5 -0
  71. package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
  72. package/dist/Thumbnail/Thumbnail.js +5 -0
  73. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  74. package/dist/TopNav/TopNavHeading.js +14 -6
  75. package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
  76. package/dist/TopNav/TopNavMegaMenu.js +25 -93
  77. package/dist/TopNav/TopNavMegaMenuItem.js +1 -1
  78. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  79. package/dist/TopNav/TopNavMenu.js +10 -5
  80. package/dist/astryx.css +40 -8
  81. package/dist/astryx.umd.js +50 -50
  82. package/dist/astryx.umd.js.map +4 -4
  83. package/dist/hooks/containerReveal.stylex.d.ts +71 -4
  84. package/dist/hooks/containerReveal.stylex.d.ts.map +1 -1
  85. package/dist/hooks/containerReveal.stylex.js +57 -5
  86. package/dist/hooks/index.d.ts +2 -2
  87. package/dist/hooks/index.d.ts.map +1 -1
  88. package/dist/hooks/index.js +1 -1
  89. package/dist/hooks/useContainerReveal.d.ts +60 -3
  90. package/dist/hooks/useContainerReveal.d.ts.map +1 -1
  91. package/dist/hooks/useContainerReveal.js +27 -5
  92. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  93. package/dist/hooks/useFocusTrap.js +16 -2
  94. package/dist/hooks/useMenuHover.d.ts +50 -5
  95. package/dist/hooks/useMenuHover.d.ts.map +1 -1
  96. package/dist/hooks/useMenuHover.js +171 -51
  97. package/dist/theme/defineTheme.d.ts +16 -5
  98. package/dist/theme/defineTheme.d.ts.map +1 -1
  99. package/dist/theme/defineTheme.js +44 -48
  100. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  101. package/dist/theme/derivedVarRegistry.js +7 -0
  102. package/dist/theme/expandColorScale.d.ts.map +1 -1
  103. package/dist/theme/expandColorScale.js +1 -0
  104. package/dist/theme/expandMotionScale.d.ts +1 -0
  105. package/dist/theme/expandMotionScale.d.ts.map +1 -1
  106. package/dist/theme/expandMotionScale.js +1 -0
  107. package/dist/theme/expandRadiusScale.d.ts +1 -0
  108. package/dist/theme/expandRadiusScale.d.ts.map +1 -1
  109. package/dist/theme/expandRadiusScale.js +1 -0
  110. package/dist/theme/expandTypeScale.d.ts +1 -0
  111. package/dist/theme/expandTypeScale.d.ts.map +1 -1
  112. package/dist/theme/expandTypeScale.js +1 -0
  113. package/dist/theme/mergeComponents.d.ts +20 -0
  114. package/dist/theme/mergeComponents.d.ts.map +1 -0
  115. package/dist/theme/mergeComponents.js +56 -0
  116. package/dist/theme/onMediaTokens.d.ts +6 -1
  117. package/dist/theme/onMediaTokens.d.ts.map +1 -1
  118. package/dist/theme/onMediaTokens.js +11 -3
  119. package/dist/theme/tokens.stylex.d.ts +3 -1
  120. package/dist/theme/tokens.stylex.d.ts.map +1 -1
  121. package/dist/theme/tokens.stylex.js +3 -1
  122. package/locales/en.json +84 -0
  123. package/locales/pseudo.json +63 -0
  124. package/package.json +3 -3
  125. package/src/Avatar/Avatar.doc.mjs +5 -2
  126. package/src/Avatar/Avatar.test.tsx +51 -0
  127. package/src/Avatar/Avatar.tsx +30 -20
  128. package/src/AvatarGroup/AvatarGroup.doc.mjs +1 -1
  129. package/src/AvatarGroup/AvatarGroup.test.tsx +14 -0
  130. package/src/AvatarGroup/AvatarGroupOverflow.tsx +3 -2
  131. package/src/Button/Button.tsx +5 -3
  132. package/src/ButtonGroup/ButtonGroup.test.tsx +7 -4
  133. package/src/Card/Card.doc.mjs +2 -0
  134. package/src/Chat/Chat.doc.mjs +4 -1
  135. package/src/Chat/ChatMessage.doc.mjs +3 -3
  136. package/src/Chat/ChatMessage.tsx +7 -0
  137. package/src/Chat/ChatMessageBubble.doc.mjs +7 -0
  138. package/src/Chat/ChatMessageBubble.test.tsx +95 -6
  139. package/src/Chat/ChatMessageBubble.tsx +25 -0
  140. package/src/Chat/ChatTokenizedText.tsx +1 -1
  141. package/src/CheckboxInput/CheckboxInput.tsx +20 -0
  142. package/src/CodeBlock/CodeBlock.doc.mjs +3 -0
  143. package/src/CommandPalette/CommandPaletteFooter.tsx +6 -3
  144. package/src/ComplexSelector/ComplexSelector.doc.mjs +55 -6
  145. package/src/ComplexSelector/ComplexSelector.test.tsx +197 -6
  146. package/src/ComplexSelector/ComplexSelector.tsx +153 -32
  147. package/src/ComplexSelector/index.ts +3 -1
  148. package/src/DateTimeInput/DateTimeInput.tsx +4 -2
  149. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +72 -1
  150. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +24 -20
  151. package/src/HoverCard/HoverCard.doc.mjs +3 -3
  152. package/src/HoverCard/HoverCard.test.tsx +283 -55
  153. package/src/HoverCard/HoverCard.tsx +5 -13
  154. package/src/HoverCard/useHoverCard.tsx +8 -11
  155. package/src/InputGroup/groupStyles.ts +7 -8
  156. package/src/Item/Item.doc.mjs +4 -0
  157. package/src/Layer/layerHost.test.ts +99 -0
  158. package/src/Layer/layerHost.ts +141 -0
  159. package/src/Layer/useLayer.doc.mjs +14 -4
  160. package/src/Layer/useLayer.test.tsx +332 -7
  161. package/src/Layer/useLayer.tsx +235 -36
  162. package/src/Markdown/parser.test.ts +53 -0
  163. package/src/Markdown/parser.ts +53 -12
  164. package/src/MobileNav/MobileNav.doc.mjs +7 -0
  165. package/src/NavItem/navItemStyles.stylex.ts +31 -5
  166. package/src/RadioList/RadioListItem.tsx +20 -0
  167. package/src/SideNav/SideNav.doc.mjs +13 -4
  168. package/src/SideNav/SideNav.test.tsx +858 -2
  169. package/src/SideNav/SideNav.tsx +37 -16
  170. package/src/SideNav/SideNavCollapseButton.doc.mjs +28 -6
  171. package/src/SideNav/SideNavCollapseButton.tsx +67 -15
  172. package/src/SideNav/SideNavCollapseContext.ts +29 -9
  173. package/src/SideNav/SideNavHeading.tsx +53 -25
  174. package/src/SideNav/SideNavItem.doc.mjs +13 -0
  175. package/src/SideNav/SideNavItem.tsx +97 -84
  176. package/src/SideNav/SideNavSection.tsx +6 -20
  177. package/src/SideNav/index.ts +2 -0
  178. package/src/Slider/Slider.test.tsx +69 -33
  179. package/src/Slider/Slider.tsx +65 -13
  180. package/src/Switch/Switch.tsx +20 -0
  181. package/src/TabList/TabList.doc.mjs +3 -0
  182. package/src/Text/Text.doc.mjs +1 -1
  183. package/src/Thumbnail/Thumbnail.doc.mjs +3 -0
  184. package/src/Thumbnail/Thumbnail.tsx +10 -0
  185. package/src/Timestamp/Timestamp.test.tsx +40 -22
  186. package/src/Toolbar/Toolbar.test.tsx +14 -14
  187. package/src/TopNav/TopNav.test.tsx +86 -1
  188. package/src/TopNav/TopNavHeading.tsx +21 -14
  189. package/src/TopNav/TopNavMegaMenu.test.tsx +43 -0
  190. package/src/TopNav/TopNavMegaMenu.tsx +29 -105
  191. package/src/TopNav/TopNavMegaMenuItem.tsx +1 -1
  192. package/src/TopNav/TopNavMenu.test.tsx +41 -0
  193. package/src/TopNav/TopNavMenu.tsx +14 -4
  194. package/src/TreeList/TreeList.doc.mjs +1 -0
  195. package/src/hooks/containerReveal.stylex.ts +140 -9
  196. package/src/hooks/index.ts +6 -1
  197. package/src/hooks/useContainerReveal.doc.mjs +11 -5
  198. package/src/hooks/useContainerReveal.test.tsx +70 -3
  199. package/src/hooks/useContainerReveal.ts +86 -7
  200. package/src/hooks/useFocusTrap.test.tsx +16 -0
  201. package/src/hooks/useFocusTrap.ts +20 -2
  202. package/src/hooks/useMenuHover.test.tsx +364 -0
  203. package/src/hooks/useMenuHover.ts +243 -47
  204. package/src/theme/defineTheme.test.ts +127 -0
  205. package/src/theme/defineTheme.ts +57 -51
  206. package/src/theme/derivedVarRegistry.test.ts +78 -12
  207. package/src/theme/derivedVarRegistry.ts +4 -0
  208. package/src/theme/expandColorScale.ts +1 -0
  209. package/src/theme/expandMotionScale.ts +1 -0
  210. package/src/theme/expandRadiusScale.ts +1 -0
  211. package/src/theme/expandTypeScale.ts +1 -0
  212. package/src/theme/mergeComponents.ts +59 -0
  213. package/src/theme/onMediaTokens.ts +9 -2
  214. package/src/theme/themingTargets.test.ts +152 -26
  215. package/src/theme/tokens.stylex.ts +3 -1
  216. package/src/theme/tokens.test.ts +12 -0
  217. package/src/theme/useTheme.test.tsx +18 -0
@@ -4,8 +4,8 @@
4
4
 
5
5
  /**
6
6
  * @file ComplexSelector.tsx
7
- * @input Uses React, StyleX, Field, usePopover
8
- * @output Exports ComplexSelector component for custom selector surfaces
7
+ * @input Uses React, StyleX, Field, Icon slots, Layer positioning, and usePopover
8
+ * @output Exports a rich-selector shell with exact token-sized input and ghost triggers, plus an imperative open/close handle
9
9
  * @position Core implementation; consumed by index.ts
10
10
  *
11
11
  * SYNC: When modified, update:
@@ -13,17 +13,18 @@
13
13
  * - /packages/core/src/ComplexSelector/ComplexSelector.test.tsx
14
14
  * - /packages/core/src/ComplexSelector/index.ts
15
15
  * - /apps/storybook/stories/ComplexSelector.stories.tsx
16
+ * - /packages/cli/assets/templates/blocks/components/ComplexSelector/ (showcase blocks)
16
17
  */
17
- import React, { useCallback, useId, useOptimistic, useTransition } from 'react';
18
+ import React, { useCallback, useId, useImperativeHandle, useOptimistic, useRef, useTransition } from 'react';
18
19
  import * as stylex from '@stylexjs/stylex';
19
20
  import { Field, inputWrapperStyles } from "../Field/index.js";
20
- import { Icon } from "../Icon/index.js";
21
+ import { Icon, renderIconSlot } from "../Icon/index.js";
21
22
  import { Spinner } from "../Spinner/index.js";
22
23
  import { useTranslator } from "../i18n/index.js";
23
24
  import { layerAnimations } from "../Layer/layerAnimations.stylex.js";
24
25
  import { usePopover } from "../Popover/usePopover.js";
25
26
  import "../theme/tokens.stylex.js";
26
- import { colorVars, durationVars, easeVars, radiusVars, sizeVars, spacingVars, typographyVars, typeScaleVars } from "../theme/tokens.stylex.js";
27
+ import { colorVars, durationVars, easeVars, fontWeightVars, radiusVars, sizeVars, spacingVars, typographyVars, typeScaleVars } from "../theme/tokens.stylex.js";
27
28
  import { mergeProps } from "../utils/index.js";
28
29
  import { composeEventHandlers } from "../utils/composeEventHandlers.js";
29
30
  import { focusOutlineStyles } from "../utils/focusOutline.stylex.js";
@@ -82,6 +83,22 @@ const styles = {
82
83
  kMwMTN: "xv1l7n4",
83
84
  $$css: true
84
85
  },
86
+ triggerGhost: {
87
+ kzqmXN: "x14atkfc",
88
+ kMzoRj: "xc342km",
89
+ kWkggS: "xjbqb8w",
90
+ kKwaWg: "x1ilzqfv xq8i9tn",
91
+ kGVxlE: "x1gnnqk1 x1irk71m x15ysqaf",
92
+ k63SB2: "x1e4wzip",
93
+ k1ekBW: "xrafxwg",
94
+ k3aq6I: "x3oybdh xk4oym4",
95
+ $$css: true
96
+ },
97
+ triggerGhostDisabled: {
98
+ kKwaWg: "x18o3ruo",
99
+ k3aq6I: "x1c071of x1pdlv7q",
100
+ $$css: true
101
+ },
85
102
  triggerIcon: {
86
103
  kmuXW: "x2lah0s",
87
104
  $$css: true
@@ -109,15 +126,15 @@ const styles = {
109
126
  $$css: true
110
127
  },
111
128
  sm: {
112
- kAzted: "x1gdqauq",
129
+ kZKoxP: "x6k0iem",
113
130
  $$css: true
114
131
  },
115
132
  md: {
116
- kAzted: "x1uogy3y",
133
+ kZKoxP: "x1ueg155",
117
134
  $$css: true
118
135
  },
119
136
  lg: {
120
- kAzted: "x1xf4nls",
137
+ kZKoxP: "xssyfek",
121
138
  $$css: true
122
139
  },
123
140
  disabled: {
@@ -125,6 +142,16 @@ const styles = {
125
142
  $$css: true
126
143
  }
127
144
  };
145
+
146
+ /**
147
+ * Imperative control surface for ComplexSelector, accessed via the `handleRef`
148
+ * prop. Methods drive the same popover machinery as the built-in trigger, so
149
+ * they respect focus restoration, light dismiss, and Escape. Prefer these
150
+ * callbacks over mirroring open state in the parent — the selector owns its
151
+ * visibility, and imperative calls avoid the focus-management pitfalls of
152
+ * syncing an external `isOpen` prop.
153
+ */
154
+
128
155
  /**
129
156
  * A selector shell for rich, custom selection surfaces.
130
157
  *
@@ -170,8 +197,12 @@ export function ComplexSelector({
170
197
  statusVariant = 'attached',
171
198
  labelTooltip,
172
199
  size = 'md',
200
+ variant = 'input',
201
+ startIcon,
173
202
  width,
174
203
  placement = 'below',
204
+ alignment = 'start',
205
+ handleRef,
175
206
  contentXstyle,
176
207
  xstyle,
177
208
  className,
@@ -182,28 +213,62 @@ export function ComplexSelector({
182
213
  }) {
183
214
  const t = useTranslator();
184
215
  const placeholder = placeholderFromProps ?? t('@astryx.selector.placeholder');
216
+ const effectiveStatusVariant = variant === 'ghost' && statusVariant === 'attached' ? 'detached' : statusVariant;
185
217
  const triggerId = useId();
186
218
  const labelId = useId();
187
219
  const contentId = useId();
188
220
  const descriptionId = useId();
189
221
  const statusMessageId = useId();
190
222
  const ariaDescribedBy = [description ? descriptionId : null, status?.message ? statusMessageId : null].filter(id => id != null).join(' ') || undefined;
223
+ const triggerRef = useRef(null);
224
+ const lastHideTimeRef = useRef(0);
191
225
  const [isPending, startTransition] = useTransition();
192
226
  const [optimisticValue, setOptimisticValue] = useOptimistic(value);
193
227
  const isBusy = isLoading || isPending;
228
+ const handlePopoverHide = useCallback(() => {
229
+ lastHideTimeRef.current = Date.now();
230
+ triggerRef.current?.focus();
231
+ }, []);
194
232
  const popover = usePopover({
195
233
  dialogLabel: label,
196
234
  hasCloseButton: false,
197
235
  hasAutoFocus: true,
198
- // The popup's theme target belongs on the SURFACE — the element painting
199
- // background, radius and elevation — which `usePopover` owns. Rendered on
200
- // the content box below it, a theme's background or radius rule paints the
201
- // wrong box.
202
236
  surfaceTarget: 'complex-selector-popup',
203
- onHide: () => {
204
- document.getElementById(triggerId)?.focus();
205
- }
237
+ onHide: handlePopoverHide
206
238
  });
239
+ const isOpen = popover.isOpen;
240
+ const handleTriggerClick = useCallback(() => {
241
+ if (isDisabled || Date.now() - lastHideTimeRef.current < 50) {
242
+ return;
243
+ }
244
+ if (popover.isOpen) {
245
+ popover.hide();
246
+ } else {
247
+ popover.show();
248
+ }
249
+ }, [isDisabled, popover]);
250
+ const close = useCallback(() => {
251
+ popover.hide();
252
+ }, [popover]);
253
+ useImperativeHandle(handleRef, () => ({
254
+ open: () => {
255
+ if (!isDisabled) {
256
+ popover.show();
257
+ }
258
+ },
259
+ close: () => popover.hide(),
260
+ toggle: () => {
261
+ if (isDisabled) {
262
+ return;
263
+ }
264
+ if (popover.isOpen) {
265
+ popover.hide();
266
+ } else {
267
+ popover.show();
268
+ }
269
+ },
270
+ isOpen: () => popover.isOpen
271
+ }), [isDisabled, popover]);
207
272
  const commitValue = useCallback(nextValue => {
208
273
  onChange?.(nextValue);
209
274
  if (changeAction) {
@@ -217,8 +282,8 @@ export function ComplexSelector({
217
282
  const content = /*#__PURE__*/_jsx("div", {
218
283
  id: contentId,
219
284
  ...stylex.props(styles.content, contentXstyle),
220
- children: children(optimisticValue, commitValue, popover.hide, {
221
- isOpen: popover.isOpen,
285
+ children: children(optimisticValue, commitValue, close, {
286
+ isOpen,
222
287
  isBusy,
223
288
  triggerId,
224
289
  contentId
@@ -229,25 +294,21 @@ export function ComplexSelector({
229
294
  ref: popover.triggerRef,
230
295
  "data-testid": testId,
231
296
  ...props,
232
- onClick: composeEventHandlers(onClickProp, () => {
233
- if (!isDisabled) {
234
- popover.toggle();
235
- }
236
- }),
297
+ onClick: composeEventHandlers(onClickProp, handleTriggerClick),
237
298
  ...mergeProps(themeProps('complex-selector', {
299
+ variant,
238
300
  size,
239
301
  status: status?.type ?? null
240
- }), stylex.props(inputWrapperStyles.base, styles.triggerContainer, styles[size],
241
- // The ring belongs to the wrapper (the focusable `<button>` sits
242
- // inside it), but it must still be a KEYBOARD ring: `:focus-within`
243
- // matched a mouse click on the trigger and drew the outline for
244
- // pointer users too. `focusWithin` here is `:has(:focus-visible)`.
245
- focusOutlineStyles.focusWithin, isDisabled && inputWrapperStyles.disabled, isDisabled && styles.disabled, triggerLabel == null && styles.placeholder, xstyle), className, style),
246
- children: [/*#__PURE__*/_jsx("button", {
302
+ }), stylex.props(inputWrapperStyles.base, styles.triggerContainer, styles[size], variant === 'input' && focusOutlineStyles.focusWithin, variant === 'ghost' && styles.triggerGhost, variant === 'ghost' && focusOutlineStyles.focusWithin, isDisabled && inputWrapperStyles.disabled, variant === 'ghost' && isDisabled && styles.triggerGhostDisabled, isDisabled && styles.disabled, triggerLabel == null && styles.placeholder, xstyle), className, style),
303
+ children: [startIcon && renderIconSlot(startIcon, {
304
+ size: 'sm',
305
+ color: 'secondary'
306
+ }), /*#__PURE__*/_jsx("button", {
307
+ ref: triggerRef,
247
308
  id: triggerId,
248
309
  type: "button",
249
310
  "aria-haspopup": "dialog",
250
- "aria-expanded": popover.isOpen,
311
+ "aria-expanded": isOpen,
251
312
  "aria-controls": contentId,
252
313
  "aria-describedby": ariaDescribedBy,
253
314
  "aria-labelledby": labelId,
@@ -256,7 +317,7 @@ export function ComplexSelector({
256
317
  "aria-busy": isBusy || undefined,
257
318
  disabled: isDisabled,
258
319
  onKeyDown: event => {
259
- if (event.key === 'ArrowDown' && !popover.isOpen && !isDisabled) {
320
+ if (event.key === 'ArrowDown' && !isOpen && !isDisabled) {
260
321
  event.preventDefault();
261
322
  popover.show();
262
323
  }
@@ -281,14 +342,14 @@ export function ComplexSelector({
281
342
  // IS the trigger's icon element — one node carrying the box, the
282
343
  // color, the rotation, and the theme target.
283
344
  ,
284
- xstyle: [styles.triggerIcon, styles.triggerIconRotation, popover.isOpen && styles.triggerIconOpen],
345
+ xstyle: [styles.triggerIcon, styles.triggerIconRotation, isOpen && styles.triggerIconOpen],
285
346
  ...themeProps('complex-selector-indicator-icon', {
286
- state: popover.isOpen ? 'expanded' : 'collapsed'
347
+ state: isOpen ? 'expanded' : 'collapsed'
287
348
  })
288
349
  })]
289
350
  }), popover.render(content, {
290
351
  placement,
291
- alignment: 'start',
352
+ alignment,
292
353
  offset: spacingVars['--spacing-1'],
293
354
  xstyle: [styles.popover, layerAnimations[placement]]
294
355
  })]
@@ -308,7 +369,7 @@ export function ComplexSelector({
308
369
  message: status.message,
309
370
  messageID: status.message ? statusMessageId : undefined
310
371
  } : undefined,
311
- statusVariant: statusVariant,
372
+ statusVariant: effectiveStatusVariant,
312
373
  labelTooltip: labelTooltip,
313
374
  width: width,
314
375
  children: selectorContent
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @file index.ts
3
- * @output Exports ComplexSelector and types
3
+ * @output Exports ComplexSelector and its trigger-size contract types
4
4
  * @position Public API entry point
5
5
  */
6
- export { ComplexSelector, type ComplexSelectorProps, type ComplexSelectorRenderState, type ComplexSelectorSize, type ComplexSelectorStatus, } from './ComplexSelector';
6
+ export { ComplexSelector, type ComplexSelectorHandle, type ComplexSelectorProps, type ComplexSelectorRenderState, type ComplexSelectorSize, type ComplexSelectorStatus, type ComplexSelectorVariant, } from './ComplexSelector';
7
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ComplexSelector/index.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AAEH,OAAO,EACL,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,0BAA0B,EAC/B,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,GAC3B,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ComplexSelector/index.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AAEH,OAAO,EACL,eAAe,EACf,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,0BAA0B,EAC/B,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,GAC5B,MAAM,mBAAmB,CAAC"}
@@ -4,7 +4,7 @@
4
4
 
5
5
  /**
6
6
  * @file index.ts
7
- * @output Exports ComplexSelector and types
7
+ * @output Exports ComplexSelector and its trigger-size contract types
8
8
  * @position Public API entry point
9
9
  */
10
10
  export { ComplexSelector } from "./ComplexSelector.js";
@@ -1 +1 @@
1
- {"version":3,"file":"DateTimeInput.d.ts","sourceRoot":"","sources":["../../src/DateTimeInput/DateTimeInput.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DateTimeInput.d.ts","sourceRoot":"","sources":["../../src/DateTimeInput/DateTimeInput.tsx"],"names":[],"mappings":"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"}
@@ -287,10 +287,10 @@ export function DateTimeInput({
287
287
  }, [timePendingInput, hasSeconds, timeMin, timeMax]);
288
288
  const resolvedTimePlaceholder = useMemo(() => {
289
289
  if (isTimeFocused && !timeDisplayValue) {
290
- return hourFormat === '12h' ? 'e.g., 2:30 PM' : 'e.g., 14:30';
290
+ return hourFormat === '12h' ? t('@astryx.dateTimeInput.timeHint12h') : t('@astryx.dateTimeInput.timeHint24h');
291
291
  }
292
292
  return timePlaceholder;
293
- }, [isTimeFocused, timeDisplayValue, hourFormat, timePlaceholder]);
293
+ }, [isTimeFocused, timeDisplayValue, hourFormat, timePlaceholder, t]);
294
294
 
295
295
  // --- Unified change handler ---
296
296
  const fireChange = useCallback(newValue => {
@@ -1 +1 @@
1
- {"version":3,"file":"DropdownMenuSubMenu.d.ts","sourceRoot":"","sources":["../../src/DropdownMenu/DropdownMenuSubMenu.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAc,EAOZ,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,EAAuB,KAAK,QAAQ,EAAC,MAAM,SAAS,CAAC;AAoB5D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAqF5C,UAAU,4BAA6B,SAAQ,IAAI,CACjD,SAAS,EACT,QAAQ,GAAG,WAAW,GAAG,OAAO,CACjC;IACC,2DAA2D;IAC3D,IAAI,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC5B,8CAA8C;IAC9C,KAAK,EAAE,SAAS,CAAC;IACjB,4DAA4D;IAC5D,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,8CAA8C;IAC9C,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACzC,mCAAmC;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mCAAmC;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,wBAAyB,SAAQ,4BAA4B;IAC5E;;;;;;;;OAQG;IACH,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,wBAAwB,GAC9B,SAAS,CAoVX;yBAtVe,mBAAmB"}
1
+ {"version":3,"file":"DropdownMenuSubMenu.d.ts","sourceRoot":"","sources":["../../src/DropdownMenu/DropdownMenuSubMenu.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAc,EAOZ,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,EAAuB,KAAK,QAAQ,EAAC,MAAM,SAAS,CAAC;AAoB5D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAqF5C,UAAU,4BAA6B,SAAQ,IAAI,CACjD,SAAS,EACT,QAAQ,GAAG,WAAW,GAAG,OAAO,CACjC;IACC,2DAA2D;IAC3D,IAAI,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC5B,8CAA8C;IAC9C,KAAK,EAAE,SAAS,CAAC;IACjB,4DAA4D;IAC5D,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,8CAA8C;IAC9C,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACzC,mCAAmC;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mCAAmC;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,wBAAyB,SAAQ,4BAA4B;IAC5E;;;;;;;;OAQG;IACH,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,wBAAwB,GAC9B,SAAS,CAwVX;yBA1Ve,mBAAmB"}
@@ -198,9 +198,13 @@ export function DropdownMenuSubMenu(props) {
198
198
 
199
199
  // Hover-intent: entering the trigger opens after a short delay; leaving
200
200
  // either surface closes after a delay. Hover-open does not steal focus.
201
+ // Hover intent and the shared hover→click guard only: this level owns its own
202
+ // click handling, roving focus and typeahead. popover="manual", so the
203
+ // invoker wiring other consumers need does not apply.
201
204
  const {
202
205
  triggerProps,
203
- contentProps
206
+ contentProps,
207
+ confirmHoverOpen
204
208
  } = useMenuHover({
205
209
  show: showLayer,
206
210
  hide: hideLayer,
@@ -213,18 +217,12 @@ export function DropdownMenuSubMenu(props) {
213
217
  }
214
218
  layer.show();
215
219
  if (options?.focusFirst) {
216
- requestAnimationFrame(() => {
217
- // Move focus into the flyout. When it has no focusable items yet
218
- // (e.g. an async submenu showing only a disabled "Loading…" row via
219
- // hasSpinner), focusFirst() finds nothing — fall back to focusing the
220
- // flyout container itself so keyboard ownership still transfers off
221
- // the parent list. Otherwise the parent would keep focus, letting
222
- // arrow keys rove the parent while the empty flyout stays open.
223
- const focusedItem = focusFirst();
224
- if (!focusedItem) {
225
- menuRef.current?.focus();
226
- }
227
- });
220
+ // Synchronous by design — see the focus note in useMenuHover. A
221
+ // still-loading flyout has no focusable item, so fall back to the
222
+ // container: keyboard ownership must leave the parent list either way.
223
+ if (!focusFirst()) {
224
+ menuRef.current?.focus();
225
+ }
228
226
  }
229
227
  }, [canOpen, layer, focusFirst, menuRef]);
230
228
  const close = useCallback(options => {
@@ -244,8 +242,14 @@ export function DropdownMenuSubMenu(props) {
244
242
  if (isDisabled) {
245
243
  return;
246
244
  }
247
- // Click toggles the flyout, moving focus into it on open.
245
+ // Toggles, except for the click that follows a hover-open (#3121).
248
246
  if (isOpen) {
247
+ if (confirmHoverOpen()) {
248
+ if (!focusFirst()) {
249
+ menuRef.current?.focus();
250
+ }
251
+ return;
252
+ }
249
253
  close({
250
254
  focusTrigger: true
251
255
  });
@@ -254,7 +258,7 @@ export function DropdownMenuSubMenu(props) {
254
258
  focusFirst: true
255
259
  });
256
260
  }
257
- }, [isDisabled, isOpen, open, close]);
261
+ }, [isDisabled, isOpen, open, close, confirmHoverOpen, focusFirst, menuRef]);
258
262
  const handleTriggerKeyDown = useCallback(e => {
259
263
  if (isDisabled) {
260
264
  return;
@@ -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; uses inline-safe trigger wrapper and renders the floating layer inline
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,CAiH/B;yBAlIe,SAAS"}
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; uses inline-safe trigger wrapper and renders the floating layer inline
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
- // Render the floating layer inline, in the same place on the server and the
131
- // client. The layer is a `popover` element opened via the Popover API, so the
132
- // browser promotes it to the top layer when shown that already escapes
133
- // ancestor clipping, stacking, and transform containing-block traps, and CSS
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;AAgC3B;;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,CAiT5E"}
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
- // span keeps `styles.content` for padding.
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
- // Render the layer as inline-safe phrasing markup so HoverCard stays
262
- // valid (and hydration-stable) inside inline contexts like a `<p>`.
263
- as: 'span'
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("span", {
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;AAG3C,eAAO,MAAM,WAAW;;;;;;;;;;;;;;EAmCtB,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 x11xp8u1 xpufgd0 x1ir7nn6",
22
- kT0f0o: "x16uus16 x747jw7 xxd14kb x1vej5at",
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
+ }