@react-hive/honey-layout 18.0.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.
@@ -6022,41 +6022,49 @@ __webpack_require__.r(__webpack_exports__);
6022
6022
  /* harmony import */ var _hooks__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../hooks */ "./src/hooks/index.ts");
6023
6023
 
6024
6024
 
6025
+ const subscribeToNothing = () => () => undefined;
6025
6026
  /**
6026
- * 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.
6027
6028
  *
6028
- * This hook looks up an overlay registered in `HoneyLayoutProvider` by its ID.
6029
- * 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.
6030
6033
  *
6031
6034
  * @param targetOverlayId - The ID of the active overlay to find.
6032
- * @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.
6033
6036
  *
6034
- * @returns The matching active overlay instance, or `null` when the hook is disabled
6035
- * 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.
6036
6039
  *
6037
6040
  * @remarks
6038
- * - The hook only works with overlays that are currently active.
6039
- * - If `enabled` is `false`, the hook returns `null` and does not attach listeners.
6040
- * - If the overlay is inactive, unregistered, or already removed from the stack, `null` is returned.
6041
- * - The `onKeyUp` listener is attached only to the matched overlay while the hook is enabled.
6042
- * - Event listeners are automatically cleaned up when the overlay, handler, enabled state,
6043
- * 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.
6044
6046
  *
6045
6047
  * @example
6046
6048
  * ```tsx
6047
6049
  * const overlay = useHoneyOverlay('my-overlay-id', {
6048
6050
  * enabled: isOpen,
6049
- * onKeyUp: (keyCode, overlay, e) => {
6051
+ * onKeyUp: keyCode => {
6050
6052
  * if (keyCode === 'Escape') {
6051
- * // Handle Escape key.
6053
+ * closeOverlay();
6052
6054
  * }
6053
6055
  * },
6054
6056
  * });
6055
6057
  * ```
6056
6058
  */
6057
6059
  const useHoneyOverlay = (targetOverlayId, { enabled = true, onKeyUp } = {}) => {
6058
- const { overlays } = (0,_hooks__WEBPACK_IMPORTED_MODULE_1__.useHoneyLayout)();
6059
- 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);
6060
6068
  (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
6061
6069
  if (!overlay || !onKeyUp) {
6062
6070
  return;
@@ -6141,16 +6149,39 @@ __webpack_require__.r(__webpack_exports__);
6141
6149
 
6142
6150
 
6143
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
+ */
6144
6174
  const HoneyLayoutProvider = ({ children, theme, mediaQueryOptions, ...props }) => {
6145
6175
  const screenState = (0,_hooks__WEBPACK_IMPORTED_MODULE_3__.useHoneyMediaQuery)(theme, mediaQueryOptions);
6146
- const { overlays, registerOverlay, unregisterOverlay } = (0,_hooks__WEBPACK_IMPORTED_MODULE_5__.useHoneyOverlays)();
6176
+ const { getOverlaysSnapshot, registerOverlay, subscribeOverlays, unregisterOverlay } = (0,_hooks__WEBPACK_IMPORTED_MODULE_5__.useHoneyOverlays)();
6147
6177
  const contextValue = (0,react__WEBPACK_IMPORTED_MODULE_1__.useMemo)(() => ({
6148
6178
  theme,
6149
6179
  screenState,
6150
- overlays,
6180
+ getOverlaysSnapshot,
6151
6181
  registerOverlay,
6182
+ subscribeOverlays,
6152
6183
  unregisterOverlay,
6153
- }), [theme, screenState, overlays]);
6184
+ }), [theme, screenState]);
6154
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 }) }));
6155
6186
  };
6156
6187
 
@@ -6232,17 +6263,51 @@ __webpack_require__.r(__webpack_exports__);
6232
6263
 
6233
6264
 
6234
6265
  /**
6235
- * Manages the active overlay stack and global keyboard event dispatching.
6266
+ * Manages the active overlay stack and dispatches global keyboard events.
6236
6267
  *
6237
- * The hook keeps registered overlays in stack order, where the latest registered overlay
6238
- * is treated as the top-level overlay. Keyboard events are forwarded only to the top-level
6239
- * overlay, allowing nested or overlapping overlays to handle key interactions predictably.
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.
6240
6271
  *
6241
- * @returns An object containing the active overlays stack and helper methods for registering
6242
- * and unregistering overlays.
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.
6274
+ *
6275
+ * @returns A stable overlay store containing methods for reading and subscribing to the stack,
6276
+ * together with helpers for registering and unregistering overlays.
6243
6277
  */
6244
6278
  const useHoneyOverlays = () => {
6245
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
+ }, []);
6246
6311
  (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
6247
6312
  /**
6248
6313
  * Handles global keyup events and forwards them to the top-level overlay.
@@ -6273,6 +6338,9 @@ const useHoneyOverlays = () => {
6273
6338
  * subscribing to overlay events, removing event listeners, and notifying registered
6274
6339
  * listeners when matching events occur.
6275
6340
  *
6341
+ * Registering creates a new stack snapshot and notifies overlay stack subscribers without
6342
+ * re-rendering the component that owns this hook.
6343
+ *
6276
6344
  * @param overlayConfig - The overlay configuration, including an optional ID, optional keyup
6277
6345
  * handler, and optional list of keyboard codes the overlay should listen to.
6278
6346
  *
@@ -6348,23 +6416,30 @@ const useHoneyOverlays = () => {
6348
6416
  },
6349
6417
  };
6350
6418
  overlaysRef.current = [...overlaysRef.current, overlay];
6419
+ notifyOverlaySubscribers();
6351
6420
  return overlay;
6352
6421
  }, []);
6353
6422
  /**
6354
6423
  * Unregisters an overlay by ID and removes it from the overlay stack.
6355
6424
  *
6356
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.
6357
6427
  *
6358
6428
  * @param targetOverlayId - The ID of the overlay to remove.
6359
6429
  */
6360
6430
  const unregisterOverlay = (0,react__WEBPACK_IMPORTED_MODULE_0__.useCallback)(targetOverlayId => {
6361
- overlaysRef.current = overlaysRef.current.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
+ }
6362
6436
  }, []);
6363
- return {
6364
- overlays: overlaysRef.current,
6437
+ return (0,react__WEBPACK_IMPORTED_MODULE_0__.useMemo)(() => ({
6438
+ getOverlaysSnapshot,
6365
6439
  registerOverlay,
6440
+ subscribeOverlays,
6366
6441
  unregisterOverlay,
6367
- };
6442
+ }), []);
6368
6443
  };
6369
6444
 
6370
6445