@cosxai/ui 0.4.6 → 0.4.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosxai/ui",
3
- "version": "0.4.6",
3
+ "version": "0.4.7",
4
4
  "description": "COSX design system — React 19 component primitives shared across product-meta and other consumers",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -1,15 +1,33 @@
1
- import { useContext, useEffect, useMemo } from "react";
1
+ import { useContext, useEffect } from "react";
2
2
  import { ActionBarContext } from "./actionbar-context";
3
3
  import type { ActionBarItem } from "./types";
4
4
 
5
5
  // Register a set of items into the global action-bar registry.
6
6
  // Auto-unregisters on unmount + on sourceKey change.
7
7
  //
8
- // `items` is the live array — pass a useMemo'd array to avoid
9
- // re-registration thrash. The provider's register() does shallow
10
- // equality, so passing a NEW array with identical refs is fine,
11
- // but a NEW array with all-new item objects (from inline JSX
12
- // blocks) will re-register every render.
8
+ // **Pass a `useMemo`'d array.** The effect re-registers whenever
9
+ // the array identity changes; the provider's `register()` then
10
+ // dedups by item-by-item identity, so a fresh array with identical
11
+ // item refs is cheap (state stays === and no re-render fires) but
12
+ // a fresh array with NEW item objects (e.g. consumers whose
13
+ // onClick closures captured updated state) does re-register —
14
+ // downstream renders pick up the fresh handlers.
15
+ //
16
+ // Inline-array consumers (`useActionBarItems(key, [{...}])`)
17
+ // will allocate new items every render and loop infinitely
18
+ // through `register → setState → re-render → register`. The
19
+ // useMemo on the caller side is what keeps that loop bounded
20
+ // to "items whose deps actually changed".
21
+ //
22
+ // Previous versions wrapped items in a local `useMemo` keyed by
23
+ // `[items.length, items.map(i => i.key).join('|')]`. That gate
24
+ // suppressed identity updates whenever the keys + length stayed
25
+ // stable, which is precisely the case for a selection-mode
26
+ // toolbar whose buttons are fixed but whose onClicks close over
27
+ // changing state — they ended up registered ONCE with the first
28
+ // render's closures and never refreshed, shipping stale state to
29
+ // every subsequent click. The gate is gone; the provider's
30
+ // shallow item-identity dedup is the single source of truth.
13
31
  //
14
32
  // **Important**: the useEffect deps below intentionally do NOT
15
33
  // include the ctx object itself — only its stable function refs
@@ -27,15 +45,10 @@ export function useActionBarItems(sourceKey: string, items: ActionBarItem[]) {
27
45
  throw new Error("useActionBarItems must be used within <ActionBarProvider>");
28
46
  }
29
47
  const { register, unregister } = ctx;
30
- const memo = useMemo(
31
- () => items,
32
- // eslint-disable-next-line react-hooks/exhaustive-deps
33
- [items.length, items.map((i) => i.key).join("|")],
34
- );
35
48
  useEffect(() => {
36
- register(sourceKey, memo);
49
+ register(sourceKey, items);
37
50
  return () => unregister(sourceKey);
38
- }, [register, unregister, sourceKey, memo]);
51
+ }, [register, unregister, sourceKey, items]);
39
52
  }
40
53
 
41
54
  export function useActionBarItemsContext() {