@dataverse-kit/grid-kit 0.1.0 → 0.3.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.
@@ -6,7 +6,8 @@ import type { CardGridProps } from '../types';
6
6
  * ratings, etc. look identical). Mirrors the Grid Customizer's "Card List" type.
7
7
  *
8
8
  * Selection is checkbox-based (manual Set), independent of Fluent's DetailsList
9
- * Selection.
9
+ * Selection. `rowCommands` adds a per-card right-click context menu (parity with
10
+ * `DataGrid` / `NestedCardParent`), suppressing the native browser menu.
10
11
  */
11
12
  export declare function CardGrid<T extends Record<string, unknown>>(props: CardGridProps<T>): JSX.Element;
12
13
  //# sourceMappingURL=CardGrid.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"CardGrid.d.ts","sourceRoot":"","sources":["../../src/hosts/CardGrid.tsx"],"names":[],"mappings":";AAEA,OAAO,KAAK,EAAE,aAAa,EAAa,MAAM,UAAU,CAAC;AASzD;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,OAAO,CA6GhG"}
1
+ {"version":3,"file":"CardGrid.d.ts","sourceRoot":"","sources":["../../src/hosts/CardGrid.tsx"],"names":[],"mappings":";AAEA,OAAO,KAAK,EAAE,aAAa,EAAa,MAAM,UAAU,CAAC;AAUzD;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,OAAO,CAoIhG"}
@@ -1 +1 @@
1
- {"version":3,"file":"NestedTriggerCell.d.ts","sourceRoot":"","sources":["../../../src/hosts/nested/NestedTriggerCell.tsx"],"names":[],"mappings":";AASA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAUhD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE;IAC7G,MAAM,EAAE,CAAC,CAAC;IACV,MAAM,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;CAC5B,GAAG,GAAG,CAAC,OAAO,CAsHd"}
1
+ {"version":3,"file":"NestedTriggerCell.d.ts","sourceRoot":"","sources":["../../../src/hosts/nested/NestedTriggerCell.tsx"],"names":[],"mappings":";AASA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAUhD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE;IAC7G,MAAM,EAAE,CAAC,CAAC;IACV,MAAM,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;CAC5B,GAAG,GAAG,CAAC,OAAO,CAkId"}
@@ -8,7 +8,7 @@ export interface ChildEntry<C> {
8
8
  * never fetches — this just normalizes the consumer-supplied resolver and tracks
9
9
  * loading. Call `ensure(key, parent)` on demand (row expand / panel open / hover).
10
10
  */
11
- export declare function useChildren<T, C>(getChildren: (parent: T) => C[] | Promise<C[]>): {
11
+ export declare function useChildren<T, C>(getChildren: (parent: T) => C[] | Promise<C[]>, reloadToken?: string | number): {
12
12
  get: (key: string) => ChildEntry<C> | undefined;
13
13
  ensure: (key: string, parent: T) => void;
14
14
  };
@@ -1 +1 @@
1
- {"version":3,"file":"useChildren.d.ts","sourceRoot":"","sources":["../../../src/hosts/nested/useChildren.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,CAAC,EAAE,CAAC;CACX;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;eA8BhD,MAAM,KAAG,WAAW,CAAC,CAAC,GAAG,SAAS;kBAfxD,MAAM,UAAU,CAAC;EAkB1B"}
1
+ {"version":3,"file":"useChildren.d.ts","sourceRoot":"","sources":["../../../src/hosts/nested/useChildren.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,CAAC,EAAE,CAAC;CACX;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,CAAC,EAC9B,WAAW,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,EAC9C,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM;eA6DC,MAAM,KAAG,WAAW,CAAC,CAAC,GAAG,SAAS;kBAtCxD,MAAM,UAAU,CAAC;EAyC1B"}
package/dist/index.esm.js CHANGED
@@ -2134,10 +2134,18 @@ function resolveCardLayout(columns, card) {
2134
2134
  * ratings, etc. look identical). Mirrors the Grid Customizer's "Card List" type.
2135
2135
  *
2136
2136
  * Selection is checkbox-based (manual Set), independent of Fluent's DetailsList
2137
- * Selection.
2137
+ * Selection. `rowCommands` adds a per-card right-click context menu (parity with
2138
+ * `DataGrid` / `NestedCardParent`), suppressing the native browser menu.
2138
2139
  */
2139
2140
  function CardGrid(props) {
2140
- const { items, columns, card, selectionMode = 'none', onSelectionChanged, toolbar, pagination, onPageChange, onActiveItemChanged, fill, height, } = props;
2141
+ const { items, columns, card, selectionMode = 'none', onSelectionChanged, toolbar, pagination, onPageChange, onActiveItemChanged, fill, height, rowCommands, } = props;
2142
+ // Per-row right-click menu (parity with DataGrid / NestedCardParent). For these
2143
+ // raw-div cards we invoke the hook fn from a native onContextMenu and suppress the
2144
+ // browser menu with e.preventDefault() ourselves (the hook's `return false` only
2145
+ // matters to Fluent's DetailsList onItemContextMenu contract). CardGrid is FLAT — no
2146
+ // nested child region — so (unlike NestedCardParent) nothing needs contextmenu
2147
+ // stopPropagation.
2148
+ const { onItemContextMenu, contextMenuElement } = useRowContextMenu(rowCommands);
2141
2149
  const edit = useEditState();
2142
2150
  const { getKeyFn, ctx } = useGridContext(props, edit);
2143
2151
  // Card layout derivation is shared (and unit-tested) via resolveCardLayout —
@@ -2176,10 +2184,21 @@ function CardGrid(props) {
2176
2184
  paddingTop: 4,
2177
2185
  }, children: items.map((item) => {
2178
2186
  const key = getKeyFn(item);
2179
- return (jsxs("div", { className: cardClass, style: cardHeight ? { height: cardHeight } : undefined, onClick: () => onActiveItemChanged?.(item), children: [jsxs(Stack, { horizontal: true, horizontalAlign: "space-between", verticalAlign: "start", children: [jsx(Text, { variant: "mediumPlus", styles: { root: { fontWeight: 600 } }, children: renderField(byField.get(titleField ?? ''), item) ?? rawValue(item, titleField) }), selectionMode !== 'none' && (
2187
+ return (jsxs("div", { className: cardClass, style: cardHeight ? { height: cardHeight } : undefined, onClick: () => onActiveItemChanged?.(item),
2188
+ // Right-click anywhere on the card opens its row menu. The card is the
2189
+ // row-equivalent target: the title/body cells have no handler to intercept
2190
+ // it, and the selection checkbox below only stops LEFT-click (its onClick
2191
+ // stopPropagation), so a right-click on any of them bubbles here
2192
+ // (preventDefault suppresses the browser menu).
2193
+ onContextMenu: onItemContextMenu
2194
+ ? (e) => {
2195
+ e.preventDefault();
2196
+ onItemContextMenu(item, undefined, e.nativeEvent);
2197
+ }
2198
+ : undefined, children: [jsxs(Stack, { horizontal: true, horizontalAlign: "space-between", verticalAlign: "start", children: [jsx(Text, { variant: "mediumPlus", styles: { root: { fontWeight: 600 } }, children: renderField(byField.get(titleField ?? ''), item) ?? rawValue(item, titleField) }), selectionMode !== 'none' && (
2180
2199
  // Stop the click from bubbling to the card's onActiveItemChanged.
2181
2200
  jsx("span", { onClick: (e) => e.stopPropagation(), children: jsx(Checkbox, { checked: selected.has(key), onChange: (_, checked) => toggleSelect(item, checked), ariaLabel: "Select card" }) }))] }), imageField && rawValue(item, imageField) && (jsx("img", { className: cardImageClass, src: rawValue(item, imageField), alt: "" })), subtitleField && (jsx(Text, { variant: "small", styles: { root: { color: '#605e5c' } }, children: rawValue(item, subtitleField) })), bodyColumns.map((col) => (jsxs("div", { className: bodyRowClass, children: [jsx(Text, { variant: "small", styles: { root: { color: '#605e5c' } }, children: col.name }), jsx("span", { children: renderField(col, item) })] }, col.key)))] }, key));
2182
- }) }) }), pagination && jsx(GridPaginationFooter, { pagination: pagination, onPageChange: onPageChange })] }));
2201
+ }) }) }), pagination && jsx(GridPaginationFooter, { pagination: pagination, onPageChange: onPageChange }), contextMenuElement] }));
2183
2202
  }
2184
2203
 
2185
2204
  /**
@@ -2289,10 +2308,17 @@ function GroupedGrid(props) {
2289
2308
  * never fetches — this just normalizes the consumer-supplied resolver and tracks
2290
2309
  * loading. Call `ensure(key, parent)` on demand (row expand / panel open / hover).
2291
2310
  */
2292
- function useChildren(getChildren) {
2311
+ function useChildren(getChildren, reloadToken) {
2293
2312
  const [cache, setCache] = useState({});
2294
2313
  const cacheRef = useRef(cache);
2295
2314
  cacheRef.current = cache;
2315
+ // Remember the parent object per cached key so a `reloadToken` bump can re-run
2316
+ // `getChildren(parent)` for already-loaded rows.
2317
+ const parentsRef = useRef({});
2318
+ // `getChildren` accessed via a ref inside the reload effect → the effect can depend
2319
+ // on `reloadToken` alone without a stale closure or an exhaustive-deps warning.
2320
+ const getChildrenRef = useRef(getChildren);
2321
+ getChildrenRef.current = getChildren;
2296
2322
  // Guard against setState after unmount (e.g. host swapped while a child fetch
2297
2323
  // is in flight) — mirrors NestedTriggerCell / DetailPaneChildren.
2298
2324
  const mounted = useRef(true);
@@ -2305,6 +2331,7 @@ function useChildren(getChildren) {
2305
2331
  const ensure = useCallback((key, parent) => {
2306
2332
  if (cacheRef.current[key])
2307
2333
  return;
2334
+ parentsRef.current[key] = parent;
2308
2335
  const result = getChildren(parent);
2309
2336
  if (Array.isArray(result)) {
2310
2337
  setCache((p) => ({ ...p, [key]: { loading: false, rows: result } }));
@@ -2317,6 +2344,30 @@ function useChildren(getChildren) {
2317
2344
  });
2318
2345
  }
2319
2346
  }, [getChildren]);
2347
+ // Re-fetch every already-loaded parent's children when `reloadToken` changes. Compare
2348
+ // the token by VALUE (not a first-run counter) so it's safe under StrictMode's
2349
+ // double-invoke. Crucially we do NOT flip `loading: true` here — the old rows stay
2350
+ // rendered until the fresh ones land, so there's no spinner flash and (since `ensure`
2351
+ // is only re-called on expand) no perpetual spinner for an already-expanded row.
2352
+ const prevToken = useRef(reloadToken);
2353
+ useEffect(() => {
2354
+ if (reloadToken === prevToken.current)
2355
+ return;
2356
+ prevToken.current = reloadToken;
2357
+ for (const key of Object.keys(parentsRef.current)) {
2358
+ const parent = parentsRef.current[key];
2359
+ const result = getChildrenRef.current(parent);
2360
+ if (Array.isArray(result)) {
2361
+ setCache((p) => ({ ...p, [key]: { loading: false, rows: result } }));
2362
+ }
2363
+ else {
2364
+ Promise.resolve(result).then((rows) => {
2365
+ if (mounted.current)
2366
+ setCache((p) => ({ ...p, [key]: { loading: false, rows } }));
2367
+ });
2368
+ }
2369
+ }
2370
+ }, [reloadToken]);
2320
2371
  const get = useCallback((key) => cache[key], [cache]);
2321
2372
  return { get, ensure };
2322
2373
  }
@@ -2343,7 +2394,7 @@ function NestedInline(props) {
2343
2394
  const { items, columns, nested, selectionMode, isLoading, compact, alternateRowColors, pagination, onPageChange, aggregateItems, onSelectionChanged, rowCommands, } = parentProps;
2344
2395
  const { onItemContextMenu, contextMenuElement } = useRowContextMenu(rowCommands);
2345
2396
  const [expanded, setExpanded] = useState(new Set());
2346
- const { get, ensure } = useChildren(nested.getChildren);
2397
+ const { get, ensure } = useChildren(nested.getChildren, nested.reloadToken);
2347
2398
  // Forward parent-row selection to the consumer (DataGrid does this; inline did
2348
2399
  // not). A ref keeps the once-created Selection's callback from going stale when
2349
2400
  // the prop changes (mirrors DataGrid's onSelectionChangedRef).
@@ -2471,7 +2522,7 @@ function NestedCardParent(props) {
2471
2522
  const hasAggregate = columns.some((c) => c.aggregate);
2472
2523
  const [selected, setSelected] = useState(new Set());
2473
2524
  const [expanded, setExpanded] = useState(new Set());
2474
- const { get, ensure } = useChildren(nested.getChildren);
2525
+ const { get, ensure } = useChildren(nested.getChildren, nested.reloadToken);
2475
2526
  // requires-parent gating needs a Fluent Selection over the parent rows; cards have
2476
2527
  // none, so surface the unsupported config and fall back to independent selection.
2477
2528
  React.useEffect(() => {
@@ -2594,6 +2645,18 @@ function NestedTriggerCell(props) {
2594
2645
  clearTimeout(hoverTimer.current);
2595
2646
  };
2596
2647
  }, []);
2648
+ // Invalidate the on-demand cache when `reloadToken` changes (e.g. after associating a
2649
+ // new child): drop the loaded `entry` so the next open/hover re-fetches via `load()`
2650
+ // (which early-returns on a non-null `entry`). Value-compared so it's StrictMode-safe.
2651
+ // Edge: if the panel/callout is OPEN at bump time it shows the spinner until reopened —
2652
+ // acceptable, these modes are on-demand and usually closed during a toolbar action.
2653
+ const prevReloadToken = useRef(nested.reloadToken);
2654
+ useEffect(() => {
2655
+ if (nested.reloadToken === prevReloadToken.current)
2656
+ return;
2657
+ prevReloadToken.current = nested.reloadToken;
2658
+ setEntry(null);
2659
+ }, [nested.reloadToken]);
2597
2660
  // Resolve children once, on demand.
2598
2661
  const load = useCallback(() => {
2599
2662
  setEntry((prev) => {
@@ -2771,7 +2834,9 @@ function DetailPaneChildren(props) {
2771
2834
  return () => {
2772
2835
  alive = false;
2773
2836
  };
2774
- }, [parent, nested]);
2837
+ // `nested.reloadToken` makes the refetch explicit on a child mutation (e.g. addExisting)
2838
+ // rather than relying on `nested`'s inline-literal identity changing each render.
2839
+ }, [parent, nested, nested.reloadToken]);
2775
2840
  if (rows === null)
2776
2841
  return jsx(Text, { variant: "small", children: "Loading\u2026" });
2777
2842
  return (jsx(DataGrid, { items: rows, columns: nested.childColumns, registry: nested.childRegistry, compact: true, selectionMode: nested.childSelectionMode, onSelectionChanged: nested.onChildSelectionChanged ? (sel) => nested.onChildSelectionChanged(parent, sel) : undefined, editable: nested.childEditable, editTrigger: nested.childEditTrigger, onValueChange: nested.onChildValueChange