@react-hive/honey-layout 17.13.0 → 18.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5012,10 +5012,12 @@ __webpack_require__.r(__webpack_exports__);
5012
5012
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_1__);
5013
5013
  /* harmony import */ var _floating_ui_react__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @floating-ui/react */ "./node_modules/.pnpm/@floating-ui+react@0.27.19_react-dom@19.2.7_react@19.2.7__react@19.2.7/node_modules/@floating-ui/react/dist/floating-ui.react.mjs");
5014
5014
  /* harmony import */ var _react_hive_honey_utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @react-hive/honey-utils */ "./node_modules/.pnpm/@react-hive+honey-utils@4.0.0/node_modules/@react-hive/honey-utils/dist/index.mjs");
5015
- /* harmony import */ var _components__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../../components */ "./src/components/index.ts");
5016
- /* harmony import */ var _HoneyPopupStyled__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./HoneyPopupStyled */ "./src/components/HoneyPopup/HoneyPopupStyled.ts");
5017
- /* harmony import */ var _HoneyPopupPortal__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./HoneyPopupPortal */ "./src/components/HoneyPopup/HoneyPopupPortal.tsx");
5018
- /* harmony import */ var _hooks__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./hooks */ "./src/components/HoneyPopup/hooks/index.ts");
5015
+ /* harmony import */ var _react_hive_honey_hooks__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @react-hive/honey-hooks */ "./node_modules/.pnpm/@react-hive+honey-hooks@3.1.0_react@19.2.7/node_modules/@react-hive/honey-hooks/dist/index.mjs");
5016
+ /* harmony import */ var _components__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../../components */ "./src/components/index.ts");
5017
+ /* harmony import */ var _HoneyPopupStyled__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./HoneyPopupStyled */ "./src/components/HoneyPopup/HoneyPopupStyled.ts");
5018
+ /* harmony import */ var _HoneyPopupPortal__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./HoneyPopupPortal */ "./src/components/HoneyPopup/HoneyPopupPortal.tsx");
5019
+ /* harmony import */ var _hooks__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./hooks */ "./src/components/HoneyPopup/hooks/index.ts");
5020
+
5019
5021
 
5020
5022
 
5021
5023
 
@@ -5026,17 +5028,18 @@ __webpack_require__.r(__webpack_exports__);
5026
5028
 
5027
5029
  const HoneyPopupContent = ({ children, referenceProps, content, contentProps, focusManagerProps, arrowProps, portalProps, adjustStyles, context, ...popupOptions }) => {
5028
5030
  const { useArrow, onClose } = popupOptions;
5029
- const { nodeId, floating, isOpen, arrowRef, interactions, transition } = (0,_hooks__WEBPACK_IMPORTED_MODULE_7__.useHoneyPopup)(popupOptions);
5031
+ const onCloseRef = (0,_react_hive_honey_hooks__WEBPACK_IMPORTED_MODULE_4__.useHoneyLatest)(onClose);
5032
+ const { nodeId, floating, isOpen, arrowRef, interactions, transition } = (0,_hooks__WEBPACK_IMPORTED_MODULE_8__.useHoneyPopup)(popupOptions);
5030
5033
  const handleDeactivateOverlay = (0,react__WEBPACK_IMPORTED_MODULE_1__.useCallback)(() => {
5031
- onClose?.('escape-key');
5032
- }, [onClose]);
5034
+ onCloseRef.current?.('escape-key');
5035
+ }, []);
5033
5036
  const popupContext = (0,react__WEBPACK_IMPORTED_MODULE_1__.useMemo)(() => ({
5034
5037
  context,
5035
5038
  floatingContext: floating.context,
5036
5039
  }), [context, floating.context]);
5037
- return ((0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_HoneyPopupStyled__WEBPACK_IMPORTED_MODULE_5__.HoneyPopupStyled, { ref: floating.refs.setReference, ...interactions.getReferenceProps({
5040
+ return ((0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_HoneyPopupStyled__WEBPACK_IMPORTED_MODULE_6__.HoneyPopupStyled, { ref: floating.refs.setReference, ...interactions.getReferenceProps({
5038
5041
  onClick: e => e.stopPropagation(),
5039
- }), ...referenceProps, "data-testid": "honey-popup", children: (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)(_components__WEBPACK_IMPORTED_MODULE_4__.HoneyPopupContext, { value: popupContext, children: [(0,_react_hive_honey_utils__WEBPACK_IMPORTED_MODULE_3__.invokeIfFunction)(children, popupContext), (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_floating_ui_react__WEBPACK_IMPORTED_MODULE_2__.FloatingNode, { id: nodeId, children: transition.isMounted && ((0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_HoneyPopupPortal__WEBPACK_IMPORTED_MODULE_6__.HoneyPopupPortal, { ...portalProps, children: (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_floating_ui_react__WEBPACK_IMPORTED_MODULE_2__.FloatingFocusManager, { context: floating.context, disabled: !isOpen, ...focusManagerProps, children: (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)(_components__WEBPACK_IMPORTED_MODULE_4__.HoneyOverlay, { ref: floating.refs.setFloating, active: isOpen, style: adjustStyles?.({
5042
+ }), ...referenceProps, "data-testid": "honey-popup", children: (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)(_components__WEBPACK_IMPORTED_MODULE_5__.HoneyPopupContext, { value: popupContext, children: [(0,_react_hive_honey_utils__WEBPACK_IMPORTED_MODULE_3__.invokeIfFunction)(children, popupContext), (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_floating_ui_react__WEBPACK_IMPORTED_MODULE_2__.FloatingNode, { id: nodeId, children: transition.isMounted && ((0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_HoneyPopupPortal__WEBPACK_IMPORTED_MODULE_7__.HoneyPopupPortal, { ...portalProps, children: (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_floating_ui_react__WEBPACK_IMPORTED_MODULE_2__.FloatingFocusManager, { context: floating.context, disabled: !isOpen, ...focusManagerProps, children: (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)(_components__WEBPACK_IMPORTED_MODULE_5__.HoneyOverlay, { ref: floating.refs.setFloating, active: isOpen, style: adjustStyles?.({
5040
5043
  ...floating.floatingStyles,
5041
5044
  ...transition.styles,
5042
5045
  }, {
@@ -6019,41 +6022,49 @@ __webpack_require__.r(__webpack_exports__);
6019
6022
  /* harmony import */ var _hooks__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../hooks */ "./src/hooks/index.ts");
6020
6023
 
6021
6024
 
6025
+ const subscribeToNothing = () => () => undefined;
6022
6026
  /**
6023
- * Returns an active overlay by ID and optionally attaches overlay event listeners.
6027
+ * Returns an active overlay by ID and optionally attaches a keyup event listener.
6024
6028
  *
6025
- * This hook looks up an overlay registered in `HoneyLayoutProvider` by its ID.
6026
- * It can also subscribe to overlay-level events, such as `keyup`, for the matched overlay.
6029
+ * The hook reads the overlay through the external overlay store exposed by
6030
+ * `HoneyLayoutProvider`. It subscribes to stack changes with `useSyncExternalStore`, but
6031
+ * selects only the requested overlay. Consequently, unrelated overlay stack changes do not
6032
+ * re-render the consuming component when the selected overlay instance remains unchanged.
6027
6033
  *
6028
6034
  * @param targetOverlayId - The ID of the active overlay to find.
6029
- * @param options - Optional configuration for resolving the overlay and attaching event handlers.
6035
+ * @param options - Optional configuration for resolving the overlay and handling keyup events.
6030
6036
  *
6031
- * @returns The matching active overlay instance, or `null` when the hook is disabled
6032
- * or the overlay is not currently registered.
6037
+ * @returns The matching active overlay, or `null` when the hook is disabled or the overlay is
6038
+ * not currently registered.
6033
6039
  *
6034
6040
  * @remarks
6035
- * - The hook only works with overlays that are currently active.
6036
- * - If `enabled` is `false`, the hook returns `null` and does not attach listeners.
6037
- * - If the overlay is inactive, unregistered, or already removed from the stack, `null` is returned.
6038
- * - The `onKeyUp` listener is attached only to the matched overlay while the hook is enabled.
6039
- * - Event listeners are automatically cleaned up when the overlay, handler, enabled state,
6040
- * or component lifecycle changes.
6041
+ * - The hook resolves only overlays that are currently registered.
6042
+ * - If `enabled` is `false`, the snapshot is `null` and no keyup listener is attached.
6043
+ * - If the target overlay is registered or unregistered, the consuming component is updated.
6044
+ * - Changes to unrelated overlays do not cause a re-render when the selected snapshot is equal.
6045
+ * - The `onKeyUp` listener is automatically cleaned up when its dependencies change.
6041
6046
  *
6042
6047
  * @example
6043
6048
  * ```tsx
6044
6049
  * const overlay = useHoneyOverlay('my-overlay-id', {
6045
6050
  * enabled: isOpen,
6046
- * onKeyUp: (keyCode, overlay, e) => {
6051
+ * onKeyUp: keyCode => {
6047
6052
  * if (keyCode === 'Escape') {
6048
- * // Handle Escape key.
6053
+ * closeOverlay();
6049
6054
  * }
6050
6055
  * },
6051
6056
  * });
6052
6057
  * ```
6053
6058
  */
6054
6059
  const useHoneyOverlay = (targetOverlayId, { enabled = true, onKeyUp } = {}) => {
6055
- const { overlays } = (0,_hooks__WEBPACK_IMPORTED_MODULE_1__.useHoneyLayout)();
6056
- const overlay = (0,react__WEBPACK_IMPORTED_MODULE_0__.useMemo)(() => (enabled ? (overlays.find(overlay => overlay.id === targetOverlayId) ?? null) : null), [enabled, overlays, targetOverlayId]);
6060
+ const { getOverlaysSnapshot, subscribeOverlays } = (0,_hooks__WEBPACK_IMPORTED_MODULE_1__.useHoneyLayout)();
6061
+ const getOverlaySnapshot = (0,react__WEBPACK_IMPORTED_MODULE_0__.useCallback)(() => {
6062
+ if (!enabled) {
6063
+ return null;
6064
+ }
6065
+ return getOverlaysSnapshot().find(overlay => overlay.id === targetOverlayId) ?? null;
6066
+ }, [enabled, targetOverlayId]);
6067
+ const overlay = (0,react__WEBPACK_IMPORTED_MODULE_0__.useSyncExternalStore)(enabled ? subscribeOverlays : subscribeToNothing, getOverlaySnapshot, getOverlaySnapshot);
6057
6068
  (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
6058
6069
  if (!overlay || !onKeyUp) {
6059
6070
  return;
@@ -6138,16 +6149,39 @@ __webpack_require__.r(__webpack_exports__);
6138
6149
 
6139
6150
 
6140
6151
 
6152
+ /**
6153
+ * Provides Honey styling, responsive screen state, and overlay management to its descendants.
6154
+ *
6155
+ * Overlay state is held in a ref-backed external store. Registering or unregistering an overlay
6156
+ * notifies subscribed overlay consumers without updating this provider's React state or context
6157
+ * value. This prevents overlay stack changes from re-rendering the full layout subtree.
6158
+ *
6159
+ * The context value changes only when the theme or responsive screen state changes. Overlay
6160
+ * consumers should read the store through `useHoneyOverlay` or `useSyncExternalStore` rather
6161
+ * than attempting to read the overlay ref directly.
6162
+ *
6163
+ * @param props - The provider props, including the theme, children, style-provider options,
6164
+ * and optional media-query configuration.
6165
+ * @returns The configured Honey style and layout providers.
6166
+ *
6167
+ * @example
6168
+ * ```tsx
6169
+ * <HoneyLayoutProvider theme={theme} mediaQueryOptions={mediaQueryOptions}>
6170
+ * <App />
6171
+ * </HoneyLayoutProvider>
6172
+ * ```
6173
+ */
6141
6174
  const HoneyLayoutProvider = ({ children, theme, mediaQueryOptions, ...props }) => {
6142
6175
  const screenState = (0,_hooks__WEBPACK_IMPORTED_MODULE_3__.useHoneyMediaQuery)(theme, mediaQueryOptions);
6143
- const { overlays, registerOverlay, unregisterOverlay } = (0,_hooks__WEBPACK_IMPORTED_MODULE_5__.useHoneyOverlays)();
6176
+ const { getOverlaysSnapshot, registerOverlay, subscribeOverlays, unregisterOverlay } = (0,_hooks__WEBPACK_IMPORTED_MODULE_5__.useHoneyOverlays)();
6144
6177
  const contextValue = (0,react__WEBPACK_IMPORTED_MODULE_1__.useMemo)(() => ({
6145
6178
  theme,
6146
6179
  screenState,
6147
- overlays,
6180
+ getOverlaysSnapshot,
6148
6181
  registerOverlay,
6182
+ subscribeOverlays,
6149
6183
  unregisterOverlay,
6150
- }), [theme, screenState, overlays]);
6184
+ }), [theme, screenState]);
6151
6185
  return ((0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_react_hive_honey_style__WEBPACK_IMPORTED_MODULE_2__.HoneyStyleProvider, { theme: theme, ...props, children: (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_contexts__WEBPACK_IMPORTED_MODULE_4__.HoneyLayoutContext, { value: contextValue, children: children }) }));
6152
6186
  };
6153
6187
 
@@ -6229,17 +6263,51 @@ __webpack_require__.r(__webpack_exports__);
6229
6263
 
6230
6264
 
6231
6265
  /**
6232
- * Manages the active overlay stack and global keyboard event dispatching.
6266
+ * Manages the active overlay stack and dispatches global keyboard events.
6267
+ *
6268
+ * Registered overlays are stored in a ref so adding or removing an overlay does not re-render
6269
+ * the component that owns this hook. The hook exposes a snapshot getter and subscription
6270
+ * function that consumers can use with `useSyncExternalStore` to react to stack changes.
6233
6271
  *
6234
- * The hook keeps registered overlays in stack order, where the latest registered overlay
6235
- * is treated as the top-level overlay. Keyboard events are forwarded only to the top-level
6236
- * overlay, allowing nested or overlapping overlays to handle key interactions predictably.
6272
+ * Overlays are kept in stack order. The most recently registered overlay is treated as the
6273
+ * top-level overlay and is the only overlay that receives global keyboard events.
6237
6274
  *
6238
- * @returns An object containing the active overlays stack and helper methods for registering
6239
- * and unregistering overlays.
6275
+ * @returns A stable overlay store containing methods for reading and subscribing to the stack,
6276
+ * together with helpers for registering and unregistering overlays.
6240
6277
  */
6241
6278
  const useHoneyOverlays = () => {
6242
- const [overlays, setOverlays] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)([]);
6279
+ const overlaysRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)([]);
6280
+ const subscribersRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(new Set());
6281
+ /**
6282
+ * Returns the current overlay stack snapshot.
6283
+ *
6284
+ * The snapshot keeps the same array identity until the stack changes, making this getter
6285
+ * compatible with `useSyncExternalStore`.
6286
+ *
6287
+ * @returns The current active overlays in registration order.
6288
+ */
6289
+ const getOverlaysSnapshot = (0,react__WEBPACK_IMPORTED_MODULE_0__.useCallback)(() => overlaysRef.current, []);
6290
+ /**
6291
+ * Subscribes to overlay stack changes.
6292
+ *
6293
+ * The subscriber is notified after an overlay is registered or successfully unregistered.
6294
+ * Subscribing does not itself cause the component that owns this hook to re-render.
6295
+ *
6296
+ * @param subscriber - Callback invoked whenever the overlay stack snapshot changes.
6297
+ * @returns A cleanup function that removes the subscriber.
6298
+ */
6299
+ const subscribeOverlays = (0,react__WEBPACK_IMPORTED_MODULE_0__.useCallback)((subscriber) => {
6300
+ subscribersRef.current.add(subscriber);
6301
+ return () => {
6302
+ subscribersRef.current.delete(subscriber);
6303
+ };
6304
+ }, []);
6305
+ /**
6306
+ * Notifies every overlay stack subscriber that a new snapshot is available.
6307
+ */
6308
+ const notifyOverlaySubscribers = (0,react__WEBPACK_IMPORTED_MODULE_0__.useCallback)(() => {
6309
+ subscribersRef.current.forEach(subscriber => subscriber());
6310
+ }, []);
6243
6311
  (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
6244
6312
  /**
6245
6313
  * Handles global keyup events and forwards them to the top-level overlay.
@@ -6250,18 +6318,18 @@ const useHoneyOverlays = () => {
6250
6318
  * @param e - The native keyboard event emitted by the document.
6251
6319
  */
6252
6320
  const handleKeyUp = (e) => {
6253
- if (!overlays.length) {
6321
+ if (!overlaysRef.current.length) {
6254
6322
  // No overlays to handle key events.
6255
6323
  return;
6256
6324
  }
6257
- const topLevelOverlay = overlays[overlays.length - 1];
6325
+ const topLevelOverlay = overlaysRef.current[overlaysRef.current.length - 1];
6258
6326
  topLevelOverlay.notifyListeners('keyup', e.code, e);
6259
6327
  };
6260
6328
  document.addEventListener('keyup', handleKeyUp);
6261
6329
  return () => {
6262
6330
  document.removeEventListener('keyup', handleKeyUp);
6263
6331
  };
6264
- }, [overlays]);
6332
+ }, []);
6265
6333
  /**
6266
6334
  * Registers a new overlay and adds it to the top of the overlay stack.
6267
6335
  *
@@ -6270,6 +6338,9 @@ const useHoneyOverlays = () => {
6270
6338
  * subscribing to overlay events, removing event listeners, and notifying registered
6271
6339
  * listeners when matching events occur.
6272
6340
  *
6341
+ * Registering creates a new stack snapshot and notifies overlay stack subscribers without
6342
+ * re-rendering the component that owns this hook.
6343
+ *
6273
6344
  * @param overlayConfig - The overlay configuration, including an optional ID, optional keyup
6274
6345
  * handler, and optional list of keyboard codes the overlay should listen to.
6275
6346
  *
@@ -6344,24 +6415,31 @@ const useHoneyOverlays = () => {
6344
6415
  }
6345
6416
  },
6346
6417
  };
6347
- setOverlays(prevOverlays => [...prevOverlays, overlay]);
6418
+ overlaysRef.current = [...overlaysRef.current, overlay];
6419
+ notifyOverlaySubscribers();
6348
6420
  return overlay;
6349
6421
  }, []);
6350
6422
  /**
6351
6423
  * Unregisters an overlay by ID and removes it from the overlay stack.
6352
6424
  *
6353
6425
  * This should usually be called when an overlay is deactivated or unmounted.
6426
+ * Subscribers are notified only when an overlay with the supplied ID was present.
6354
6427
  *
6355
6428
  * @param targetOverlayId - The ID of the overlay to remove.
6356
6429
  */
6357
6430
  const unregisterOverlay = (0,react__WEBPACK_IMPORTED_MODULE_0__.useCallback)(targetOverlayId => {
6358
- setOverlays(prevOverlays => prevOverlays.filter(overlay => overlay.id !== targetOverlayId));
6431
+ const nextOverlays = overlaysRef.current.filter(overlay => overlay.id !== targetOverlayId);
6432
+ if (nextOverlays.length !== overlaysRef.current.length) {
6433
+ overlaysRef.current = nextOverlays;
6434
+ notifyOverlaySubscribers();
6435
+ }
6359
6436
  }, []);
6360
- return {
6361
- overlays,
6437
+ return (0,react__WEBPACK_IMPORTED_MODULE_0__.useMemo)(() => ({
6438
+ getOverlaysSnapshot,
6362
6439
  registerOverlay,
6440
+ subscribeOverlays,
6363
6441
  unregisterOverlay,
6364
- };
6442
+ }), []);
6365
6443
  };
6366
6444
 
6367
6445