@inf-monkeys-tech/monkeys-design 1.0.38 → 1.0.39

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 (58) hide show
  1. package/dist/components/agent-workbench/index.js +9 -0
  2. package/dist/components/agent-workbench/index.js.map +1 -1
  3. package/dist/components/agent-workbench/index.mjs +9 -0
  4. package/dist/components/agent-workbench/index.mjs.map +1 -1
  5. package/dist/components/base/index.js +9 -0
  6. package/dist/components/base/index.js.map +1 -1
  7. package/dist/components/base/index.mjs +9 -0
  8. package/dist/components/base/index.mjs.map +1 -1
  9. package/dist/components/data-explorer/index.js +9 -0
  10. package/dist/components/data-explorer/index.js.map +1 -1
  11. package/dist/components/data-explorer/index.mjs +9 -0
  12. package/dist/components/data-explorer/index.mjs.map +1 -1
  13. package/dist/components/layout/index.js +9 -0
  14. package/dist/components/layout/index.js.map +1 -1
  15. package/dist/components/layout/index.mjs +9 -0
  16. package/dist/components/layout/index.mjs.map +1 -1
  17. package/dist/components/runtime/index.js +9 -0
  18. package/dist/components/runtime/index.js.map +1 -1
  19. package/dist/components/runtime/index.mjs +9 -0
  20. package/dist/components/runtime/index.mjs.map +1 -1
  21. package/dist/components/toast/index.d.mts +1 -1
  22. package/dist/components/toast/index.d.ts +1 -1
  23. package/dist/components/toast/index.js +9 -0
  24. package/dist/components/toast/index.js.map +1 -1
  25. package/dist/components/toast/index.mjs +9 -0
  26. package/dist/components/toast/index.mjs.map +1 -1
  27. package/dist/components/toolbar/index.js +9 -0
  28. package/dist/components/toolbar/index.js.map +1 -1
  29. package/dist/components/toolbar/index.mjs +9 -0
  30. package/dist/components/toolbar/index.mjs.map +1 -1
  31. package/dist/components/workbench/index.d.mts +218 -6
  32. package/dist/components/workbench/index.d.ts +218 -6
  33. package/dist/components/workbench/index.js +1088 -97
  34. package/dist/components/workbench/index.js.map +1 -1
  35. package/dist/components/workbench/index.mjs +1089 -99
  36. package/dist/components/workbench/index.mjs.map +1 -1
  37. package/dist/index.d.mts +2 -2
  38. package/dist/index.d.ts +2 -2
  39. package/dist/index.js +838 -100
  40. package/dist/index.js.map +1 -1
  41. package/dist/index.mjs +838 -101
  42. package/dist/index.mjs.map +1 -1
  43. package/dist/locale/index.d.mts +20 -2
  44. package/dist/locale/index.d.ts +20 -2
  45. package/dist/locale/index.js +22 -0
  46. package/dist/locale/index.js.map +1 -1
  47. package/dist/locale/index.mjs +22 -0
  48. package/dist/locale/index.mjs.map +1 -1
  49. package/dist/provider/index.d.mts +2 -2
  50. package/dist/provider/index.d.ts +2 -2
  51. package/dist/provider/index.js +22 -0
  52. package/dist/provider/index.js.map +1 -1
  53. package/dist/provider/index.mjs +22 -0
  54. package/dist/provider/index.mjs.map +1 -1
  55. package/dist/styles.css +1 -1
  56. package/dist/{types-b8d18b1454c2.d.mts → types-453d18eab188.d.mts} +10 -0
  57. package/dist/{types-b8d18b1454c2.d.ts → types-453d18eab188.d.ts} +10 -0
  58. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3072,6 +3072,15 @@ var enUS = {
3072
3072
  }
3073
3073
  },
3074
3074
  workbench: {
3075
+ collection: {
3076
+ label: "Collection",
3077
+ loading: "Loading items",
3078
+ empty: "No items",
3079
+ loadError: "Unable to load items",
3080
+ refreshError: "Unable to refresh items",
3081
+ retry: "Retry",
3082
+ loadingMore: "Loading more items"
3083
+ },
3075
3084
  interactiveTable: {
3076
3085
  columnActions: "Column actions",
3077
3086
  freezeColumn: "Freeze to left",
@@ -3343,6 +3352,15 @@ var zhCN = {
3343
3352
  }
3344
3353
  },
3345
3354
  workbench: {
3355
+ collection: {
3356
+ label: "\u5185\u5BB9\u96C6\u5408",
3357
+ loading: "\u6B63\u5728\u52A0\u8F7D\u5185\u5BB9",
3358
+ empty: "\u6682\u65E0\u5185\u5BB9",
3359
+ loadError: "\u65E0\u6CD5\u52A0\u8F7D\u5185\u5BB9",
3360
+ refreshError: "\u65E0\u6CD5\u5237\u65B0\u5185\u5BB9",
3361
+ retry: "\u91CD\u8BD5",
3362
+ loadingMore: "\u6B63\u5728\u52A0\u8F7D\u66F4\u591A\u5185\u5BB9"
3363
+ },
3346
3364
  interactiveTable: {
3347
3365
  columnActions: "\u5217\u64CD\u4F5C",
3348
3366
  freezeColumn: "\u56FA\u5B9A\u5230\u5DE6\u4FA7",
@@ -3634,6 +3652,10 @@ function mergeMonkeysLocaleMessages(base, override) {
3634
3652
  }
3635
3653
  },
3636
3654
  workbench: {
3655
+ collection: {
3656
+ ...base.workbench.collection,
3657
+ ...override.workbench?.collection
3658
+ },
3637
3659
  interactiveTable: {
3638
3660
  ...base.workbench.interactiveTable,
3639
3661
  ...override.workbench?.interactiveTable
@@ -22010,6 +22032,7 @@ var DEFAULT_MASONRY_GAP_PX = 12;
22010
22032
  var DEFAULT_MASONRY_ROW_HEIGHT_PX = 8;
22011
22033
  var DEFAULT_MASONRY_COLUMN_WIDTH_PX = 280;
22012
22034
  var DEFAULT_MASONRY_ITEM_HEIGHT_PX = 240;
22035
+ var useIsomorphicLayoutEffect = typeof window === "undefined" ? React2.useEffect : React2.useLayoutEffect;
22013
22036
  function clamp3(value, min5, max5) {
22014
22037
  return Math.min(Math.max(value, min5), max5);
22015
22038
  }
@@ -22114,9 +22137,12 @@ function WorkbenchMasonryLayout({
22114
22137
  getItemKey = (item, index) => item?.id ?? item?.key ?? index,
22115
22138
  getItemSize,
22116
22139
  columnWidth = DEFAULT_MASONRY_COLUMN_WIDTH_PX,
22140
+ columnCount: requestedColumnCount,
22117
22141
  gap = DEFAULT_MASONRY_GAP_PX,
22118
22142
  rowHeight = DEFAULT_MASONRY_ROW_HEIGHT_PX,
22119
22143
  placementStrategy = "shelf",
22144
+ containerProps,
22145
+ getItemProps,
22120
22146
  className
22121
22147
  }) {
22122
22148
  const containerRef = React2.useRef(null);
@@ -22147,8 +22173,9 @@ function WorkbenchMasonryLayout({
22147
22173
  }, []);
22148
22174
  React2.useEffect(() => {
22149
22175
  setMeasuredHeights((prev) => {
22176
+ const currentItemKeys = new Set(itemKeys);
22150
22177
  const next = Object.fromEntries(
22151
- Object.entries(prev).filter(([key]) => itemKeys.includes(key))
22178
+ Object.entries(prev).filter(([key]) => currentItemKeys.has(key))
22152
22179
  );
22153
22180
  return areMeasurementMapsEqual(prev, next) ? prev : next;
22154
22181
  });
@@ -22159,7 +22186,8 @@ function WorkbenchMasonryLayout({
22159
22186
  containerRef.current
22160
22187
  );
22161
22188
  const isDensePlacement = placementStrategy === "dense";
22162
- const columnCount = getMasonryColumnCount({
22189
+ const normalizedRequestedColumnCount = Number.isFinite(requestedColumnCount) && Number(requestedColumnCount) > 0 ? clamp3(Math.round(Number(requestedColumnCount)), 1, 24) : void 0;
22190
+ const columnCount = normalizedRequestedColumnCount || getMasonryColumnCount({
22163
22191
  containerWidth,
22164
22192
  itemCount: items.length,
22165
22193
  targetColumnWidth: resolvedTargetColumnWidth,
@@ -22205,7 +22233,7 @@ function WorkbenchMasonryLayout({
22205
22233
  resolvedColumnWidth,
22206
22234
  rowHeight
22207
22235
  ]);
22208
- React2.useLayoutEffect(() => {
22236
+ useIsomorphicLayoutEffect(() => {
22209
22237
  const nextMeasurements = {};
22210
22238
  itemNodesRef.current.forEach((node, key) => {
22211
22239
  const nextHeight = Math.ceil(node.getBoundingClientRect().height);
@@ -22267,10 +22295,21 @@ function WorkbenchMasonryLayout({
22267
22295
  const positionedItems = layoutItems.map(({ key, item, columnSpan, rowSpan, size }) => {
22268
22296
  const resolvedSpan = clamp3(columnSpan, 1, columnCount);
22269
22297
  let startColumn = nextColumnIndex;
22270
- if (startColumn + resolvedSpan > columnCount) {
22271
- startColumn = 0;
22298
+ if (placementStrategy === "shortest") {
22299
+ let shortestBottom = Number.POSITIVE_INFINITY;
22300
+ for (let candidate = 0; candidate <= columnCount - resolvedSpan; candidate += 1) {
22301
+ const candidateBottom = Math.max(...columnBottoms.slice(candidate, candidate + resolvedSpan));
22302
+ if (candidateBottom < shortestBottom) {
22303
+ shortestBottom = candidateBottom;
22304
+ startColumn = candidate;
22305
+ }
22306
+ }
22307
+ } else {
22308
+ if (startColumn + resolvedSpan > columnCount) {
22309
+ startColumn = 0;
22310
+ }
22311
+ startColumn = Math.min(startColumn, columnCount - resolvedSpan);
22272
22312
  }
22273
- startColumn = Math.min(startColumn, columnCount - resolvedSpan);
22274
22313
  const itemWidth = resolvedColumnWidth * resolvedSpan + gap * Math.max(resolvedSpan - 1, 0);
22275
22314
  const itemHeight = getResolvedItemHeight({
22276
22315
  size,
@@ -22283,9 +22322,11 @@ function WorkbenchMasonryLayout({
22283
22322
  columnBottoms[columnIndex] = bottom + gap;
22284
22323
  }
22285
22324
  maxBottom = Math.max(maxBottom, bottom);
22286
- nextColumnIndex = startColumn + resolvedSpan;
22287
- if (nextColumnIndex >= columnCount) {
22288
- nextColumnIndex = 0;
22325
+ if (placementStrategy !== "shortest") {
22326
+ nextColumnIndex = startColumn + resolvedSpan;
22327
+ if (nextColumnIndex >= columnCount) {
22328
+ nextColumnIndex = 0;
22329
+ }
22289
22330
  }
22290
22331
  return {
22291
22332
  key,
@@ -22308,33 +22349,38 @@ function WorkbenchMasonryLayout({
22308
22349
  "div",
22309
22350
  {
22310
22351
  ref: containerRef,
22311
- className: cn("relative w-full", className),
22312
- style: { height: `${orderedLayout.containerHeight}px` },
22352
+ ...containerProps,
22353
+ className: cn("relative w-full", className, containerProps?.className),
22354
+ style: { ...containerProps?.style, height: `${orderedLayout.containerHeight}px` },
22313
22355
  children: orderedLayout.positionedItems.map(({ key, item, columnSpan, rowSpan, size, itemWidth, left, top }) => {
22314
22356
  const unresolvedSingleColumnWidth = containerWidth <= 0 && columnCount === 1;
22315
22357
  const itemWidthValue = unresolvedSingleColumnWidth ? "100%" : `${itemWidth}px`;
22358
+ const layoutContext = {
22359
+ columnCount,
22360
+ columnSpan,
22361
+ columnWidth: resolvedColumnWidth,
22362
+ containerWidth,
22363
+ gap,
22364
+ itemWidth,
22365
+ placementStrategy,
22366
+ rowSpan,
22367
+ rowHeight,
22368
+ size
22369
+ };
22370
+ const itemProps = getItemProps?.(item, layoutContext);
22316
22371
  return /* @__PURE__ */ jsxRuntime.jsx(
22317
22372
  "div",
22318
22373
  {
22319
- className: "absolute min-w-0",
22374
+ ...itemProps,
22375
+ className: cn("absolute min-w-0", itemProps?.className),
22320
22376
  style: {
22377
+ ...itemProps?.style,
22321
22378
  left: unresolvedSingleColumnWidth ? 0 : `${left}px`,
22322
22379
  top: `${top}px`,
22323
22380
  width: itemWidthValue,
22324
22381
  maxWidth: itemWidthValue
22325
22382
  },
22326
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: (node) => registerItemNode(key, node), className: "min-w-0", children: renderItem(item, {
22327
- columnCount,
22328
- columnSpan,
22329
- columnWidth: resolvedColumnWidth,
22330
- containerWidth,
22331
- gap,
22332
- itemWidth,
22333
- placementStrategy,
22334
- rowSpan,
22335
- rowHeight,
22336
- size
22337
- }) })
22383
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: (node) => registerItemNode(key, node), role: "presentation", className: "min-w-0", children: renderItem(item, layoutContext) })
22338
22384
  },
22339
22385
  key
22340
22386
  );
@@ -22346,35 +22392,717 @@ function WorkbenchMasonryLayout({
22346
22392
  "div",
22347
22393
  {
22348
22394
  ref: containerRef,
22349
- className: cn("grid w-full items-start", className),
22395
+ ...containerProps,
22396
+ className: cn("grid w-full items-start", className, containerProps?.className),
22350
22397
  style: {
22398
+ ...containerProps?.style,
22351
22399
  gap: `${gap}px`,
22352
22400
  gridAutoFlow: "row dense",
22353
22401
  gridAutoRows: `${rowHeight}px`,
22354
22402
  gridTemplateColumns: `repeat(${columnCount}, minmax(0, 1fr))`
22355
22403
  },
22356
- children: layoutItems.map(({ key, item, columnSpan, rowSpan, size }) => /* @__PURE__ */ jsxRuntime.jsx(
22357
- "div",
22404
+ children: layoutItems.map(({ key, item, columnSpan, rowSpan, size }) => {
22405
+ const layoutContext = {
22406
+ columnCount,
22407
+ columnSpan,
22408
+ columnWidth: resolvedColumnWidth,
22409
+ containerWidth,
22410
+ gap,
22411
+ placementStrategy,
22412
+ rowSpan,
22413
+ rowHeight,
22414
+ size
22415
+ };
22416
+ const itemProps = getItemProps?.(item, layoutContext);
22417
+ return /* @__PURE__ */ jsxRuntime.jsx(
22418
+ "div",
22419
+ {
22420
+ ...itemProps,
22421
+ className: cn("min-w-0", itemProps?.className),
22422
+ style: {
22423
+ ...itemProps?.style,
22424
+ gridColumn: `span ${columnSpan}`,
22425
+ gridRow: `span ${rowSpan}`
22426
+ },
22427
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: (node) => registerItemNode(key, node), role: "presentation", className: "min-w-0", children: renderItem(item, layoutContext) })
22428
+ },
22429
+ key
22430
+ );
22431
+ })
22432
+ }
22433
+ );
22434
+ }
22435
+ var DEFAULT_GAP_PX = 12;
22436
+ var DEFAULT_ROW_HEIGHT_PX = 8;
22437
+ var DEFAULT_CARD_WIDTH_PX = 280;
22438
+ var BREAKPOINT_TABLET_PX = 640;
22439
+ var BREAKPOINT_DESKTOP_PX = 1024;
22440
+ var MAX_COLUMN_COUNT = 24;
22441
+ function clamp4(value, min5, max5) {
22442
+ return Math.min(Math.max(value, min5), max5);
22443
+ }
22444
+ function resolveRenderableSlot(slot, context) {
22445
+ return typeof slot === "function" ? slot(context) : slot;
22446
+ }
22447
+ function resolveLengthToPixels2(value, fallback, node) {
22448
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) return value;
22449
+ if (typeof value !== "string") return fallback;
22450
+ const normalized = value.trim().toLowerCase();
22451
+ if (!normalized) return fallback;
22452
+ if (/^\d+(\.\d+)?(px)?$/.test(normalized)) {
22453
+ const parsed = Number.parseFloat(normalized);
22454
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
22455
+ }
22456
+ if (normalized.endsWith("rem")) {
22457
+ const parsed = Number.parseFloat(normalized);
22458
+ const rootFontSize = typeof window === "undefined" ? 16 : Number.parseFloat(window.getComputedStyle(node || document.documentElement).fontSize) || 16;
22459
+ return Number.isFinite(parsed) && parsed > 0 ? parsed * rootFontSize : fallback;
22460
+ }
22461
+ return fallback;
22462
+ }
22463
+ function normalizeColumns(value) {
22464
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return void 0;
22465
+ return clamp4(Math.round(value), 1, MAX_COLUMN_COUNT);
22466
+ }
22467
+ function resolveResponsiveColumns(columns, containerWidth) {
22468
+ if (columns === "auto" || columns === void 0) return void 0;
22469
+ if (typeof columns === "number") return normalizeColumns(columns);
22470
+ const mobile = normalizeColumns(columns.mobile);
22471
+ const tablet = normalizeColumns(columns.tablet);
22472
+ const desktop = normalizeColumns(columns.desktop);
22473
+ if (containerWidth >= BREAKPOINT_DESKTOP_PX) return desktop ?? tablet ?? mobile;
22474
+ if (containerWidth >= BREAKPOINT_TABLET_PX) return tablet ?? mobile ?? desktop;
22475
+ return mobile ?? tablet ?? desktop;
22476
+ }
22477
+ function resolveColumnCount({
22478
+ containerWidth,
22479
+ itemCount,
22480
+ gap,
22481
+ columns,
22482
+ minCardWidth,
22483
+ idealCardWidth,
22484
+ maxCardWidth,
22485
+ node
22486
+ }) {
22487
+ const fixedColumns = resolveResponsiveColumns(columns, containerWidth);
22488
+ if (fixedColumns) return fixedColumns;
22489
+ if (!containerWidth) return 1;
22490
+ const idealWidth = resolveLengthToPixels2(idealCardWidth, DEFAULT_CARD_WIDTH_PX, node);
22491
+ const minWidth = resolveLengthToPixels2(minCardWidth, idealWidth * 0.78, node);
22492
+ const maxWidth = Math.max(
22493
+ minWidth,
22494
+ resolveLengthToPixels2(maxCardWidth, idealWidth * 1.35, node)
22495
+ );
22496
+ const safeItemCount = Math.max(itemCount, 1);
22497
+ const maxCandidate = Math.max(
22498
+ 1,
22499
+ Math.min(
22500
+ safeItemCount,
22501
+ Math.ceil((containerWidth + gap) / (Math.max(minWidth * 0.75, 1) + gap))
22502
+ )
22503
+ );
22504
+ let bestCount = 1;
22505
+ let bestScore = Number.POSITIVE_INFINITY;
22506
+ for (let candidate = 1; candidate <= maxCandidate; candidate += 1) {
22507
+ const width = (containerWidth - gap * Math.max(candidate - 1, 0)) / candidate;
22508
+ if (!Number.isFinite(width) || width <= 0) continue;
22509
+ const score = Math.abs(width - idealWidth) + Math.max(0, minWidth - width) * 3 + Math.max(0, width - maxWidth) * 2;
22510
+ if (score < bestScore) {
22511
+ bestScore = score;
22512
+ bestCount = candidate;
22513
+ }
22514
+ }
22515
+ return bestCount;
22516
+ }
22517
+ function normalizeGap(value) {
22518
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : DEFAULT_GAP_PX;
22519
+ }
22520
+ function normalizeRowHeight(value) {
22521
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : DEFAULT_ROW_HEIGHT_PX;
22522
+ }
22523
+ function normalizeThreshold(value) {
22524
+ return typeof value === "number" && Number.isFinite(value) ? clamp4(value, 0, 1) : 0;
22525
+ }
22526
+ function normalizeItemSize(size, aspectRatio, columnWidth) {
22527
+ const normalizedAspectRatio = Number.isFinite(aspectRatio) && Number(aspectRatio) > 0 ? Number(aspectRatio) : void 0;
22528
+ const normalizedHeight = size?.height ?? (normalizedAspectRatio ? Math.max(1, columnWidth / normalizedAspectRatio) : void 0);
22529
+ return {
22530
+ ...size,
22531
+ height: normalizedHeight
22532
+ };
22533
+ }
22534
+ function getDefaultItemKey(item, index) {
22535
+ if (item && typeof item === "object") {
22536
+ const candidate = item;
22537
+ if (typeof candidate.id === "string" || typeof candidate.id === "number") return candidate.id;
22538
+ if (typeof candidate.key === "string" || typeof candidate.key === "number") return candidate.key;
22539
+ }
22540
+ return index;
22541
+ }
22542
+ function getScrollStorageKey(key) {
22543
+ return `@inf-monkeys-tech/monkeys-design:workbench-collection:${key}`;
22544
+ }
22545
+ function getParentScrollOwner(node) {
22546
+ let current = node?.parentElement ?? null;
22547
+ while (current) {
22548
+ if (typeof window === "undefined") return current;
22549
+ const overflowY = window.getComputedStyle(current).overflowY;
22550
+ if (overflowY === "auto" || overflowY === "scroll") return current;
22551
+ current = current.parentElement;
22552
+ }
22553
+ return null;
22554
+ }
22555
+ function setsEqual(left, right) {
22556
+ if (left.size !== right.size) return false;
22557
+ for (const value of left) {
22558
+ if (!right.has(value)) return false;
22559
+ }
22560
+ return true;
22561
+ }
22562
+ function isInteractiveEventTarget(target, currentTarget) {
22563
+ if (typeof Element === "undefined" || !(target instanceof Element) || target === currentTarget) return false;
22564
+ const control = target.closest(
22565
+ 'a, button, input, select, textarea, summary, [contenteditable="true"], [data-collection-action]'
22566
+ );
22567
+ return Boolean(control && currentTarget.contains(control));
22568
+ }
22569
+ function warnOnDuplicateKeys(keys) {
22570
+ if (typeof process !== "undefined" && process.env?.NODE_ENV === "production") return;
22571
+ const seen = /* @__PURE__ */ new Set();
22572
+ const duplicates = /* @__PURE__ */ new Set();
22573
+ keys.forEach((key) => {
22574
+ const stringKey = String(key);
22575
+ if (seen.has(stringKey)) duplicates.add(stringKey);
22576
+ seen.add(stringKey);
22577
+ });
22578
+ if (duplicates.size) {
22579
+ console.warn(`WorkbenchCollection received duplicate item keys: ${Array.from(duplicates).join(", ")}`);
22580
+ }
22581
+ }
22582
+ function WorkbenchCollection({
22583
+ items,
22584
+ config,
22585
+ renderItem,
22586
+ slots,
22587
+ callbacks,
22588
+ className,
22589
+ classNames,
22590
+ style,
22591
+ ariaLabel
22592
+ }) {
22593
+ const componentAttributes = useMonkeysComponentAttributes();
22594
+ const localeMessages = useMonkeysLocaleMessages().workbench.collection;
22595
+ const componentId = React2.useId().replace(/:/g, "");
22596
+ const rootRef = React2.useRef(null);
22597
+ const itemsRef = React2.useRef(null);
22598
+ const loadMoreRef = React2.useRef(null);
22599
+ const lastSelectedIndexRef = React2.useRef(null);
22600
+ const loadMoreLockedRef = React2.useRef(false);
22601
+ const layoutReadyRef = React2.useRef(false);
22602
+ const [containerWidth, setContainerWidth] = React2.useState(0);
22603
+ const [itemsNode, setItemsNode] = React2.useState(null);
22604
+ const [internalSelectedKeys, setInternalSelectedKeys] = React2.useState(
22605
+ () => new Set(config.selection?.defaultSelectedKeys ?? [])
22606
+ );
22607
+ const callbacksRef = React2.useRef(callbacks);
22608
+ callbacksRef.current = callbacks;
22609
+ const registerItemsNode = React2.useCallback((node) => {
22610
+ itemsRef.current = node;
22611
+ setItemsNode(node);
22612
+ }, []);
22613
+ const mode = config.layout.mode;
22614
+ if (mode !== "grid" && mode !== "masonry" && mode !== "dense" && mode !== "rail") {
22615
+ throw new TypeError(`WorkbenchCollection layout.mode "${String(mode)}" is not supported.`);
22616
+ }
22617
+ const gap = normalizeGap(config.layout.gap);
22618
+ const rowHeight = normalizeRowHeight(config.layout.rowHeight);
22619
+ const getItemKey = config.getItemKey ?? getDefaultItemKey;
22620
+ const normalizedItems = React2.useMemo(() => items.map((item, index) => {
22621
+ const key = getItemKey(item, index);
22622
+ return { item, index, key, stringKey: String(key) };
22623
+ }), [getItemKey, items]);
22624
+ const keys = React2.useMemo(
22625
+ () => normalizedItems.map(({ key }) => key),
22626
+ [normalizedItems]
22627
+ );
22628
+ const keysSignature = normalizedItems.map(({ stringKey }) => stringKey).join("");
22629
+ React2.useEffect(() => warnOnDuplicateKeys(keys), [keys, keysSignature]);
22630
+ const selectionMode = config.selection?.mode ?? "none";
22631
+ const controlledSelection = config.selection?.selectedKeys !== void 0;
22632
+ const selectedKeys = React2.useMemo(
22633
+ () => selectionMode === "none" ? /* @__PURE__ */ new Set() : controlledSelection ? new Set(config.selection?.selectedKeys ?? []) : internalSelectedKeys,
22634
+ [config.selection?.selectedKeys, controlledSelection, internalSelectedKeys, selectionMode]
22635
+ );
22636
+ React2.useEffect(() => {
22637
+ if (controlledSelection) return;
22638
+ const availableKeys = new Set(keys);
22639
+ setInternalSelectedKeys((previous) => {
22640
+ const next = new Set(Array.from(previous).filter((key) => availableKeys.has(key)));
22641
+ return setsEqual(previous, next) ? previous : next;
22642
+ });
22643
+ }, [controlledSelection, keys, keysSignature]);
22644
+ React2.useEffect(() => {
22645
+ const node = itemsNode;
22646
+ if (!node) return void 0;
22647
+ const updateWidth = () => {
22648
+ const nextWidth = Math.round(node.getBoundingClientRect().width);
22649
+ setContainerWidth((previous) => previous === nextWidth ? previous : nextWidth);
22650
+ };
22651
+ updateWidth();
22652
+ if (typeof ResizeObserver === "function") {
22653
+ const observer = new ResizeObserver(updateWidth);
22654
+ observer.observe(node);
22655
+ return () => observer.disconnect();
22656
+ }
22657
+ if (typeof window === "undefined") return void 0;
22658
+ window.addEventListener("resize", updateWidth);
22659
+ return () => window.removeEventListener("resize", updateWidth);
22660
+ }, [itemsNode]);
22661
+ const columnCount = resolveColumnCount({
22662
+ containerWidth,
22663
+ itemCount: normalizedItems.length,
22664
+ gap,
22665
+ columns: config.layout.columns,
22666
+ minCardWidth: config.layout.minCardWidth,
22667
+ idealCardWidth: config.layout.idealCardWidth ?? config.layout.columnWidth,
22668
+ maxCardWidth: config.layout.maxCardWidth,
22669
+ node: itemsRef.current
22670
+ });
22671
+ const columnWidth = containerWidth > 0 ? Math.max(0, (containerWidth - gap * Math.max(columnCount - 1, 0)) / columnCount) : resolveLengthToPixels2(
22672
+ config.layout.cardWidth ?? config.layout.columnWidth ?? config.layout.idealCardWidth,
22673
+ DEFAULT_CARD_WIDTH_PX,
22674
+ itemsRef.current
22675
+ );
22676
+ const placementStrategy = config.layout.placementStrategy ?? (mode === "dense" ? "dense" : mode === "masonry" ? "shortest" : "shelf");
22677
+ const labels = React2.useMemo(() => ({
22678
+ collection: config.labels?.collection ?? localeMessages.label,
22679
+ loading: config.labels?.loading ?? localeMessages.loading,
22680
+ empty: config.labels?.empty ?? localeMessages.empty,
22681
+ loadError: config.labels?.loadError ?? localeMessages.loadError,
22682
+ refreshError: config.labels?.refreshError ?? localeMessages.refreshError,
22683
+ retry: config.labels?.retry ?? localeMessages.retry,
22684
+ loadingMore: config.labels?.loadingMore ?? localeMessages.loadingMore
22685
+ }), [config.labels, localeMessages]);
22686
+ const virtualizationError = React2.useMemo(() => {
22687
+ const virtualization = config.virtualization;
22688
+ if (virtualization?.enabled !== true || virtualization.fallback !== "error") return null;
22689
+ if (!virtualization.adapter) {
22690
+ return new Error("WorkbenchCollection virtualization requires an adapter.");
22691
+ }
22692
+ if (virtualization.adapter.supports?.(mode) === false) {
22693
+ return new Error(`WorkbenchCollection virtualization adapter does not support mode "${mode}".`);
22694
+ }
22695
+ return null;
22696
+ }, [config.virtualization, mode]);
22697
+ const layoutSnapshot = React2.useMemo(() => ({
22698
+ mode,
22699
+ columnCount,
22700
+ columnWidth,
22701
+ containerWidth,
22702
+ gap,
22703
+ itemCount: normalizedItems.length
22704
+ }), [columnCount, columnWidth, containerWidth, gap, mode, normalizedItems.length]);
22705
+ React2.useEffect(() => {
22706
+ callbacksRef.current?.onLayoutChange?.(layoutSnapshot);
22707
+ if (containerWidth > 0 && !layoutReadyRef.current) {
22708
+ layoutReadyRef.current = true;
22709
+ callbacksRef.current?.onLayoutReady?.(layoutSnapshot);
22710
+ }
22711
+ }, [layoutSnapshot, containerWidth]);
22712
+ React2.useEffect(() => {
22713
+ layoutReadyRef.current = false;
22714
+ }, [mode]);
22715
+ const resolveScrollOwner = React2.useCallback(() => {
22716
+ const requestedOwner = config.layout.scrollOwner;
22717
+ if (requestedOwner && requestedOwner !== "self" && requestedOwner !== "parent") return requestedOwner;
22718
+ if (requestedOwner === "parent") return getParentScrollOwner(rootRef.current);
22719
+ return rootRef.current;
22720
+ }, [config.layout.scrollOwner]);
22721
+ React2.useEffect(() => {
22722
+ const restorationKey = config.scrollRestorationKey?.trim();
22723
+ if (!restorationKey || typeof window === "undefined") return void 0;
22724
+ const owner = resolveScrollOwner();
22725
+ if (!owner) return void 0;
22726
+ const storageKey = getScrollStorageKey(restorationKey);
22727
+ try {
22728
+ const stored = Number.parseFloat(window.sessionStorage.getItem(storageKey) ?? "");
22729
+ if (Number.isFinite(stored) && stored >= 0) owner.scrollTop = stored;
22730
+ } catch {
22731
+ }
22732
+ const saveScrollPosition = () => {
22733
+ try {
22734
+ window.sessionStorage.setItem(storageKey, String(owner.scrollTop));
22735
+ } catch {
22736
+ }
22737
+ };
22738
+ owner.addEventListener("scroll", saveScrollPosition, { passive: true });
22739
+ return () => {
22740
+ saveScrollPosition();
22741
+ owner.removeEventListener("scroll", saveScrollPosition);
22742
+ };
22743
+ }, [config.scrollRestorationKey, resolveScrollOwner]);
22744
+ const state = config.state;
22745
+ const loading = state?.loading === true;
22746
+ const loadingMore = state?.loadingMore === true;
22747
+ const hasMore = state?.hasMore === true;
22748
+ const canLoadMore = typeof callbacks?.onLoadMore === "function";
22749
+ const effectiveError = state?.error ?? virtualizationError;
22750
+ React2.useEffect(() => {
22751
+ if (!loadingMore || !hasMore) loadMoreLockedRef.current = false;
22752
+ }, [hasMore, loadingMore, normalizedItems.length, keysSignature]);
22753
+ React2.useEffect(() => {
22754
+ const sentinel = loadMoreRef.current;
22755
+ if (!sentinel || !canLoadMore || !hasMore || loadingMore) return void 0;
22756
+ if (typeof IntersectionObserver !== "function") return void 0;
22757
+ const owner = resolveScrollOwner();
22758
+ const observer = new IntersectionObserver((entries) => {
22759
+ if (!entries.some((entry) => entry.isIntersecting) || loadMoreLockedRef.current) return;
22760
+ loadMoreLockedRef.current = true;
22761
+ try {
22762
+ const result = callbacksRef.current?.onLoadMore?.();
22763
+ if (result && typeof result.then === "function") {
22764
+ void result.catch(() => {
22765
+ loadMoreLockedRef.current = false;
22766
+ });
22767
+ }
22768
+ } catch (error) {
22769
+ loadMoreLockedRef.current = false;
22770
+ throw error;
22771
+ }
22772
+ }, {
22773
+ root: owner === rootRef.current ? owner : owner || null,
22774
+ rootMargin: config.loading?.rootMargin ?? "240px",
22775
+ threshold: normalizeThreshold(config.loading?.threshold)
22776
+ });
22777
+ observer.observe(sentinel);
22778
+ return () => observer.disconnect();
22779
+ }, [canLoadMore, config.loading?.rootMargin, config.loading?.threshold, hasMore, keysSignature, loadingMore, normalizedItems.length, resolveScrollOwner]);
22780
+ const groups = React2.useMemo(() => {
22781
+ if (!config.grouping) {
22782
+ return [{ key: "__all__", stringKey: "__all__", items: normalizedItems }];
22783
+ }
22784
+ const grouped = /* @__PURE__ */ new Map();
22785
+ normalizedItems.forEach((entry) => {
22786
+ const groupKey = config.grouping?.getGroupKey(entry.item, entry.index) ?? "__all__";
22787
+ const stringKey = String(groupKey);
22788
+ const existing = grouped.get(stringKey);
22789
+ if (existing) {
22790
+ existing.items.push(entry);
22791
+ return;
22792
+ }
22793
+ grouped.set(stringKey, {
22794
+ key: groupKey,
22795
+ stringKey,
22796
+ label: config.grouping?.getGroupLabel?.(groupKey, entry.item),
22797
+ items: [entry]
22798
+ });
22799
+ });
22800
+ return Array.from(grouped.values());
22801
+ }, [config.grouping, normalizedItems]);
22802
+ const setSelection = React2.useCallback((entry, behavior = config.selection?.behavior ?? (selectionMode === "multiple" ? "toggle" : "replace")) => {
22803
+ if (selectionMode === "none" || config.selection?.isItemDisabled?.(entry.item, entry.index)) return;
22804
+ const current = new Set(selectedKeys);
22805
+ const next = /* @__PURE__ */ new Set();
22806
+ if (selectionMode === "single") {
22807
+ if (!(behavior === "toggle" && current.has(entry.key))) next.add(entry.key);
22808
+ } else if (behavior === "range" && lastSelectedIndexRef.current !== null) {
22809
+ current.forEach((key) => next.add(key));
22810
+ const start = Math.min(lastSelectedIndexRef.current, entry.index);
22811
+ const end = Math.max(lastSelectedIndexRef.current, entry.index);
22812
+ normalizedItems.slice(start, end + 1).forEach((candidate) => {
22813
+ if (!config.selection?.isItemDisabled?.(candidate.item, candidate.index)) next.add(candidate.key);
22814
+ });
22815
+ } else if (behavior === "replace") {
22816
+ next.add(entry.key);
22817
+ } else {
22818
+ current.forEach((key) => next.add(key));
22819
+ if (next.has(entry.key)) next.delete(entry.key);
22820
+ else next.add(entry.key);
22821
+ }
22822
+ lastSelectedIndexRef.current = entry.index;
22823
+ if (!controlledSelection) setInternalSelectedKeys(next);
22824
+ callbacks?.onSelectionChange?.(next, { item: entry.item, key: entry.key, behavior });
22825
+ }, [callbacks, config.selection, controlledSelection, normalizedItems, selectedKeys, selectionMode]);
22826
+ const createItemContext = React2.useCallback((entry, layout) => {
22827
+ const disabled = config.selection?.isItemDisabled?.(entry.item, entry.index) ?? false;
22828
+ const context = {
22829
+ item: entry.item,
22830
+ index: entry.index,
22831
+ key: entry.key,
22832
+ status: config.getItemStatus?.(entry.item, entry.index) ?? "idle",
22833
+ disabled,
22834
+ selected: selectedKeys.has(entry.key),
22835
+ selectionMode,
22836
+ layout,
22837
+ select: (behavior) => setSelection(entry, behavior),
22838
+ preview: () => callbacks?.onItemPreview?.(entry.item, context),
22839
+ detail: () => callbacks?.onItemDetail?.(entry.item, context),
22840
+ renderSlot: (name) => {
22841
+ return slots?.item?.[name]?.(entry.item, context);
22842
+ },
22843
+ notifyMediaLoad: () => callbacks?.onMediaLoad?.(entry.item, context),
22844
+ notifyMediaError: (error) => callbacks?.onMediaError?.(entry.item, context, error)
22845
+ };
22846
+ return context;
22847
+ }, [callbacks, config.getItemStatus, config.selection, selectedKeys, selectionMode, setSelection, slots?.item]);
22848
+ const getItemInteractionProps = React2.useCallback((entry, context) => {
22849
+ const handleKeyboard = (event) => {
22850
+ if (event.target !== event.currentTarget || event.key !== "Enter" && event.key !== " ") return;
22851
+ event.preventDefault();
22852
+ if (selectionMode !== "none") context.select(event.shiftKey ? "range" : void 0);
22853
+ else callbacks?.onItemClick?.(entry.item, context, event);
22854
+ };
22855
+ return {
22856
+ id: `${componentId}-item-${entry.index}`,
22857
+ "data-monkeys-collection-item": "",
22858
+ "data-item-key": entry.stringKey,
22859
+ "data-selected": context.selected || void 0,
22860
+ "data-disabled": context.disabled || void 0,
22861
+ "data-status": context.status,
22862
+ role: selectionMode === "none" ? "listitem" : "option",
22863
+ "aria-selected": selectionMode === "none" ? void 0 : context.selected,
22864
+ "aria-disabled": context.disabled || void 0,
22865
+ tabIndex: callbacks?.onItemClick || selectionMode !== "none" ? 0 : void 0,
22866
+ draggable: callbacks?.onItemDragStart ? true : void 0,
22867
+ className: cn(
22868
+ "min-w-0 outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
22869
+ config.appearance?.hover === "lift" && "transition-transform hover:-translate-y-0.5 motion-reduce:transform-none",
22870
+ config.appearance?.selection === "ring" && context.selected && "ring-2 ring-primary",
22871
+ config.appearance?.selection === "outline" && context.selected && "outline outline-2 outline-primary",
22872
+ classNames?.item
22873
+ ),
22874
+ onClick: (event) => {
22875
+ if (isInteractiveEventTarget(event.target, event.currentTarget)) return;
22876
+ if (selectionMode !== "none") context.select(event.shiftKey ? "range" : void 0);
22877
+ callbacks?.onItemClick?.(entry.item, context, event);
22878
+ },
22879
+ onDoubleClick: (event) => {
22880
+ if (isInteractiveEventTarget(event.target, event.currentTarget)) return;
22881
+ callbacks?.onItemDoubleClick?.(entry.item, context, event);
22882
+ },
22883
+ onKeyDown: handleKeyboard,
22884
+ onDragStart: (event) => callbacks?.onItemDragStart?.(entry.item, context, event),
22885
+ onDragOver: callbacks?.onItemDrop ? (event) => event.preventDefault() : void 0,
22886
+ onDrop: (event) => callbacks?.onItemDrop?.(entry.item, context, event)
22887
+ };
22888
+ }, [callbacks, classNames?.item, componentId, config.appearance, selectionMode]);
22889
+ const renderEntry = React2.useCallback((entry, layout, includeShell = true) => {
22890
+ const context = createItemContext(entry, layout);
22891
+ if (!includeShell) return renderItem(entry.item, context);
22892
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ...getItemInteractionProps(entry, context), children: renderItem(entry.item, context) });
22893
+ }, [createItemContext, getItemInteractionProps, renderItem]);
22894
+ const baseLayoutContext = React2.useMemo(() => ({
22895
+ mode,
22896
+ columnCount,
22897
+ columnWidth,
22898
+ containerWidth,
22899
+ gap,
22900
+ placementStrategy,
22901
+ rowHeight
22902
+ }), [columnCount, columnWidth, containerWidth, gap, mode, placementStrategy, rowHeight]);
22903
+ const renderGroupItems = (group) => {
22904
+ const virtualization = config.virtualization;
22905
+ const adapter = virtualization?.adapter;
22906
+ const virtualizationRequested = virtualization?.enabled === true;
22907
+ if (virtualizationRequested && (!adapter || adapter.supports?.(mode) === false)) {
22908
+ if (virtualization?.fallback === "error") {
22909
+ return null;
22910
+ }
22911
+ }
22912
+ if (virtualizationRequested && adapter && adapter.supports?.(mode) !== false) {
22913
+ return adapter.render({
22914
+ items: group.items.map(({ item }) => item),
22915
+ keys: group.items.map(({ key }) => key),
22916
+ layout: config.layout,
22917
+ scrollOwner: resolveScrollOwner(),
22918
+ renderItem: (item, localIndex, layout) => {
22919
+ const entry = group.items[localIndex];
22920
+ return entry ? renderEntry(entry, layout) : null;
22921
+ }
22922
+ });
22923
+ }
22924
+ if (mode === "masonry" || mode === "dense") {
22925
+ return /* @__PURE__ */ jsxRuntime.jsx(
22926
+ WorkbenchMasonryLayout,
22358
22927
  {
22359
- className: "min-w-0",
22360
- style: {
22361
- gridColumn: `span ${columnSpan}`,
22362
- gridRow: `span ${rowSpan}`
22928
+ items: group.items,
22929
+ columnCount,
22930
+ columnWidth: config.layout.cardWidth ?? config.layout.columnWidth ?? config.layout.idealCardWidth,
22931
+ gap,
22932
+ rowHeight,
22933
+ placementStrategy: mode === "dense" ? "dense" : placementStrategy,
22934
+ className: classNames?.layout,
22935
+ containerProps: {
22936
+ role: selectionMode === "none" ? "list" : "listbox",
22937
+ "aria-label": ariaLabel ?? labels.collection,
22938
+ "aria-multiselectable": selectionMode === "multiple" || void 0
22363
22939
  },
22364
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: (node) => registerItemNode(key, node), className: "h-full", children: renderItem(item, {
22365
- columnCount,
22366
- columnSpan,
22367
- columnWidth: resolvedColumnWidth,
22368
- containerWidth,
22369
- gap,
22370
- placementStrategy,
22371
- rowSpan,
22372
- rowHeight,
22373
- size
22374
- }) })
22940
+ getItemProps: (entry, masonryLayout) => {
22941
+ const context = createItemContext(entry, { ...masonryLayout, mode });
22942
+ return getItemInteractionProps(entry, context);
22943
+ },
22944
+ getItemKey: (entry) => entry.key,
22945
+ getItemSize: (entry, masonryLayout) => {
22946
+ const layout = {
22947
+ ...masonryLayout,
22948
+ mode
22949
+ };
22950
+ const customSize = config.layout.getItemSize?.(entry.item, layout);
22951
+ const aspectRatio = config.layout.getItemAspectRatio?.(entry.item, entry.index);
22952
+ return normalizeItemSize(customSize, aspectRatio, masonryLayout.columnWidth);
22953
+ },
22954
+ renderItem: (entry, masonryLayout) => renderEntry(entry, {
22955
+ ...masonryLayout,
22956
+ mode
22957
+ }, false)
22958
+ }
22959
+ );
22960
+ }
22961
+ if (mode === "rail") {
22962
+ const cardWidth = config.layout.cardWidth ?? config.layout.idealCardWidth ?? DEFAULT_CARD_WIDTH_PX;
22963
+ return /* @__PURE__ */ jsxRuntime.jsx(
22964
+ "div",
22965
+ {
22966
+ role: selectionMode === "none" ? "list" : "listbox",
22967
+ "aria-label": ariaLabel ?? labels.collection,
22968
+ "aria-multiselectable": selectionMode === "multiple" || void 0,
22969
+ className: cn("grid min-w-0 grid-flow-col items-start overflow-x-auto", classNames?.layout),
22970
+ style: { gap, gridAutoColumns: typeof cardWidth === "number" ? `${cardWidth}px` : cardWidth },
22971
+ children: group.items.map((entry) => /* @__PURE__ */ jsxRuntime.jsx(React2.Fragment, { children: renderEntry(entry, baseLayoutContext) }, entry.stringKey))
22972
+ }
22973
+ );
22974
+ }
22975
+ return /* @__PURE__ */ jsxRuntime.jsx(
22976
+ "div",
22977
+ {
22978
+ role: selectionMode === "none" ? "list" : "listbox",
22979
+ "aria-label": ariaLabel ?? labels.collection,
22980
+ "aria-multiselectable": selectionMode === "multiple" || void 0,
22981
+ className: cn("grid min-w-0 items-start", classNames?.layout),
22982
+ style: {
22983
+ gap,
22984
+ gridTemplateColumns: `repeat(${columnCount}, minmax(0, 1fr))`
22375
22985
  },
22376
- key
22377
- ))
22986
+ children: group.items.map((entry) => {
22987
+ const customSize = config.layout.getItemSize?.(entry.item, baseLayoutContext);
22988
+ const span = clamp4(Math.round(Number(customSize?.columnSpan) || 1), 1, columnCount);
22989
+ const aspectRatio = config.layout.getItemAspectRatio?.(entry.item, entry.index);
22990
+ return /* @__PURE__ */ jsxRuntime.jsx(
22991
+ "div",
22992
+ {
22993
+ ...getItemInteractionProps(
22994
+ entry,
22995
+ createItemContext(entry, { ...baseLayoutContext, columnSpan: span, size: customSize })
22996
+ ),
22997
+ style: {
22998
+ gridColumn: span > 1 ? `span ${span}` : void 0,
22999
+ aspectRatio: Number.isFinite(aspectRatio) && Number(aspectRatio) > 0 ? String(aspectRatio) : void 0
23000
+ },
23001
+ children: renderEntry(entry, { ...baseLayoutContext, columnSpan: span, size: customSize }, false)
23002
+ },
23003
+ entry.stringKey
23004
+ );
23005
+ })
23006
+ }
23007
+ );
23008
+ };
23009
+ const slotContext = {
23010
+ items,
23011
+ error: effectiveError,
23012
+ loading,
23013
+ loadingMore,
23014
+ selectedKeys,
23015
+ retry: () => callbacks?.onRetry?.()
23016
+ };
23017
+ const header = resolveRenderableSlot(slots?.header, slotContext);
23018
+ const toolbar = resolveRenderableSlot(slots?.toolbar, slotContext);
23019
+ const selectionToolbar = selectedKeys.size > 0 ? resolveRenderableSlot(slots?.selectionToolbar, slotContext) : null;
23020
+ const beforeItems = resolveRenderableSlot(slots?.beforeItems, slotContext);
23021
+ const footer = resolveRenderableSlot(slots?.footer, slotContext);
23022
+ const showInitialLoading = loading && items.length === 0;
23023
+ const showInitialError = Boolean(effectiveError) && items.length === 0 && !showInitialLoading;
23024
+ const showEmpty = !showInitialLoading && !showInitialError && (state?.empty === true || items.length === 0);
23025
+ const loadingSlot = resolveRenderableSlot(slots?.loading, slotContext);
23026
+ const errorSlot = resolveRenderableSlot(slots?.error, slotContext);
23027
+ const emptySlot = resolveRenderableSlot(slots?.empty, slotContext);
23028
+ const loadMoreSlot = resolveRenderableSlot(slots?.loadMore, slotContext);
23029
+ const selfScrollable = config.layout.scrollOwner === "self" || config.layout.scrollOwner === void 0 && (config.layout.fillHeight || config.layout.maxHeight);
23030
+ const rootStyle = {
23031
+ ...style,
23032
+ padding: config.layout.padding ?? style?.padding,
23033
+ height: config.layout.fillHeight ? "100%" : style?.height,
23034
+ maxHeight: config.layout.maxHeight ?? style?.maxHeight,
23035
+ overflow: selfScrollable ? "auto" : style?.overflow
23036
+ };
23037
+ return /* @__PURE__ */ jsxRuntime.jsxs(
23038
+ "section",
23039
+ {
23040
+ ref: rootRef,
23041
+ ...componentAttributes,
23042
+ "data-monkeys-component": "workbench-collection",
23043
+ "data-layout-mode": mode,
23044
+ "data-density": config.appearance?.density,
23045
+ "data-radius": config.appearance?.radius,
23046
+ "data-media-fit": config.appearance?.mediaFit,
23047
+ "aria-label": ariaLabel ?? labels.collection,
23048
+ "aria-busy": loading || loadingMore || void 0,
23049
+ className: cn("min-h-0 min-w-0", config.layout.fillHeight && "h-full", classNames?.root, className),
23050
+ style: rootStyle,
23051
+ children: [
23052
+ hasRenderableNode(header) ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: classNames?.header, children: header }) : null,
23053
+ hasRenderableNode(toolbar) ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: classNames?.toolbar, children: toolbar }) : null,
23054
+ hasRenderableNode(selectionToolbar) ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: classNames?.selectionToolbar, children: selectionToolbar }) : null,
23055
+ hasRenderableNode(beforeItems) ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: classNames?.beforeItems, children: beforeItems }) : null,
23056
+ showInitialLoading ? hasRenderableNode(loadingSlot) ? loadingSlot : /* @__PURE__ */ jsxRuntime.jsx(BaseLoadingState, { title: labels.loading }) : showInitialError ? hasRenderableNode(errorSlot) ? errorSlot : /* @__PURE__ */ jsxRuntime.jsx(
23057
+ BaseNotice,
23058
+ {
23059
+ tone: "danger",
23060
+ role: "alert",
23061
+ "aria-live": "assertive",
23062
+ title: labels.loadError,
23063
+ actions: callbacks?.onRetry ? /* @__PURE__ */ jsxRuntime.jsx(BaseButton, { size: "sm", label: labels.retry, onClick: callbacks.onRetry }) : void 0
23064
+ }
23065
+ ) : showEmpty ? hasRenderableNode(emptySlot) ? emptySlot : /* @__PURE__ */ jsxRuntime.jsx(BaseEmptyState, { title: labels.empty }) : /* @__PURE__ */ jsxRuntime.jsx(
23066
+ "div",
23067
+ {
23068
+ ref: registerItemsNode,
23069
+ className: cn("min-w-0", classNames?.items),
23070
+ children: groups.map((group, groupIndex) => {
23071
+ const groupContext = {
23072
+ key: group.key,
23073
+ label: group.label,
23074
+ items: group.items.map(({ item }) => item),
23075
+ groupIndex
23076
+ };
23077
+ const heading = config.grouping ? slots?.groupHeading?.(groupContext, slotContext) ?? group.label : null;
23078
+ const headingId = `${componentId}-group-${groupIndex}`;
23079
+ return /* @__PURE__ */ jsxRuntime.jsxs(
23080
+ "div",
23081
+ {
23082
+ "data-collection-group": group.stringKey,
23083
+ className: classNames?.group,
23084
+ children: [
23085
+ hasRenderableNode(heading) ? /* @__PURE__ */ jsxRuntime.jsx("div", { id: headingId, role: "heading", "aria-level": 2, className: classNames?.groupHeading, children: heading }) : null,
23086
+ renderGroupItems(group)
23087
+ ]
23088
+ },
23089
+ group.stringKey
23090
+ );
23091
+ })
23092
+ }
23093
+ ),
23094
+ items.length > 0 && effectiveError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: classNames?.status, children: hasRenderableNode(errorSlot) ? errorSlot : /* @__PURE__ */ jsxRuntime.jsx(
23095
+ BaseNotice,
23096
+ {
23097
+ tone: "danger",
23098
+ role: "alert",
23099
+ title: labels.refreshError,
23100
+ actions: callbacks?.onRetry ? /* @__PURE__ */ jsxRuntime.jsx(BaseButton, { size: "sm", label: labels.retry, onClick: callbacks.onRetry }) : void 0
23101
+ }
23102
+ ) }) : null,
23103
+ items.length > 0 && (hasMore || loadingMore) ? /* @__PURE__ */ jsxRuntime.jsx("div", { ref: loadMoreRef, className: classNames?.loadMore, "data-collection-load-more": "", children: hasRenderableNode(loadMoreSlot) ? loadMoreSlot : loadingMore ? /* @__PURE__ */ jsxRuntime.jsx(BaseLoadingState, { title: labels.loadingMore }) : null }) : null,
23104
+ hasRenderableNode(footer) ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: classNames?.footer, children: footer }) : null
23105
+ ]
22378
23106
  }
22379
23107
  );
22380
23108
  }
@@ -22815,45 +23543,54 @@ function WorkbenchGalleryView({
22815
23543
  );
22816
23544
  if (isMasonry) {
22817
23545
  return /* @__PURE__ */ jsxRuntime.jsx(
22818
- WorkbenchMasonryLayout,
23546
+ WorkbenchCollection,
22819
23547
  {
22820
23548
  items: rows,
22821
- className,
22822
- gap: GALLERY_GRID_GAP_PX,
22823
- columnWidth: resolvedMasonryColumnWidth,
22824
- placementStrategy: masonryPlacementStrategy,
22825
- getItemKey: (row, rowIndex) => row?.id || row?.linkKey || rowIndex,
22826
- getItemSize: (row, layout) => {
22827
- const defaultSize = getDefaultGalleryMasonryItemSize({
22828
- row,
22829
- columns,
22830
- galleryFieldKey,
22831
- galleryDensity,
22832
- galleryBentoColumns,
22833
- galleryDetailVisibility,
22834
- galleryDetailLimit,
22835
- getFieldValue: resolvedGetFieldValue,
22836
- isMediaColumn: resolvedIsMediaColumn,
22837
- isTitleCandidate: resolvedIsTitleCandidate,
22838
- isSummaryCandidate: resolvedIsSummaryCandidate,
22839
- getDisplayText: resolvedGetDisplayText,
22840
- isImageField: resolvedIsImageField,
22841
- isAudioField: resolvedIsAudioField,
22842
- cardVariant,
22843
- renderPreview,
22844
- renderLeadingVisual,
22845
- hiddenDetailFieldKeys,
22846
- layout,
22847
- resolveRecordVisual
22848
- });
22849
- if (typeof getMasonryItemSize !== "function") return defaultSize;
22850
- const customSize = getMasonryItemSize(row, { ...layout, defaultSize }) || {};
22851
- return {
22852
- ...defaultSize,
22853
- ...customSize
22854
- };
23549
+ classNames: { layout: className },
23550
+ config: {
23551
+ getItemKey: (row, rowIndex) => row?.id || row?.linkKey || rowIndex,
23552
+ layout: {
23553
+ mode: masonryPlacementStrategy === "dense" ? "dense" : "masonry",
23554
+ gap: GALLERY_GRID_GAP_PX,
23555
+ idealCardWidth: resolvedMasonryColumnWidth,
23556
+ placementStrategy: masonryPlacementStrategy,
23557
+ getItemSize: (row, layout) => {
23558
+ const defaultSize = getDefaultGalleryMasonryItemSize({
23559
+ row,
23560
+ columns,
23561
+ galleryFieldKey,
23562
+ galleryDensity,
23563
+ galleryBentoColumns,
23564
+ galleryDetailVisibility,
23565
+ galleryDetailLimit,
23566
+ getFieldValue: resolvedGetFieldValue,
23567
+ isMediaColumn: resolvedIsMediaColumn,
23568
+ isTitleCandidate: resolvedIsTitleCandidate,
23569
+ isSummaryCandidate: resolvedIsSummaryCandidate,
23570
+ getDisplayText: resolvedGetDisplayText,
23571
+ isImageField: resolvedIsImageField,
23572
+ isAudioField: resolvedIsAudioField,
23573
+ cardVariant,
23574
+ renderPreview,
23575
+ renderLeadingVisual,
23576
+ hiddenDetailFieldKeys,
23577
+ layout,
23578
+ resolveRecordVisual
23579
+ });
23580
+ if (typeof getMasonryItemSize !== "function") return defaultSize;
23581
+ const customSize = getMasonryItemSize(row, {
23582
+ ...layout,
23583
+ placementStrategy: layout.placementStrategy === "dense" ? "dense" : "shelf",
23584
+ defaultSize
23585
+ }) || {};
23586
+ return {
23587
+ ...defaultSize,
23588
+ ...customSize
23589
+ };
23590
+ }
23591
+ }
22855
23592
  },
22856
- renderItem: (row) => renderGalleryCard(row, rows.indexOf(row))
23593
+ renderItem: (row, context) => renderGalleryCard(row, context.index)
22857
23594
  }
22858
23595
  );
22859
23596
  }
@@ -23371,7 +24108,7 @@ var getDomain = (values, min5, max5) => {
23371
24108
  return lower < upper ? [lower, upper] : [upper, lower];
23372
24109
  };
23373
24110
  var scale = (value, domain, start, end) => start + (value - domain[0]) / (domain[1] - domain[0]) * (end - start);
23374
- var clamp4 = (value, min5, max5) => Math.min(max5, Math.max(min5, value));
24111
+ var clamp5 = (value, min5, max5) => Math.min(max5, Math.max(min5, value));
23375
24112
  var activatePoint = (event, point, onPointSelect) => {
23376
24113
  if (!onPointSelect || event.key !== "Enter" && event.key !== " ") return;
23377
24114
  event.preventDefault();
@@ -23453,9 +24190,9 @@ function WorkbenchScatterPlot({
23453
24190
  xThreshold === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(
23454
24191
  "line",
23455
24192
  {
23456
- x1: clamp4(xThreshold, plotLeft, plotRight),
24193
+ x1: clamp5(xThreshold, plotLeft, plotRight),
23457
24194
  y1: plotTop,
23458
- x2: clamp4(xThreshold, plotLeft, plotRight),
24195
+ x2: clamp5(xThreshold, plotLeft, plotRight),
23459
24196
  y2: plotBottom,
23460
24197
  className: "stroke-muted-foreground/50",
23461
24198
  strokeDasharray: "5 5"
@@ -23465,9 +24202,9 @@ function WorkbenchScatterPlot({
23465
24202
  "line",
23466
24203
  {
23467
24204
  x1: plotLeft,
23468
- y1: clamp4(yThreshold, plotTop, plotBottom),
24205
+ y1: clamp5(yThreshold, plotTop, plotBottom),
23469
24206
  x2: plotRight,
23470
- y2: clamp4(yThreshold, plotTop, plotBottom),
24207
+ y2: clamp5(yThreshold, plotTop, plotBottom),
23471
24208
  className: "stroke-muted-foreground/50",
23472
24209
  strokeDasharray: "5 5"
23473
24210
  }
@@ -23494,17 +24231,17 @@ function WorkbenchScatterPlot({
23494
24231
  }
23495
24232
  ),
23496
24233
  validPoints.map((point) => {
23497
- const x = clamp4(
24234
+ const x = clamp5(
23498
24235
  scale(point.x, xDomain, plotLeft, plotRight),
23499
24236
  plotLeft,
23500
24237
  plotRight
23501
24238
  );
23502
- const y = clamp4(
24239
+ const y = clamp5(
23503
24240
  scale(point.y, yDomain, plotBottom, plotTop),
23504
24241
  plotTop,
23505
24242
  plotBottom
23506
24243
  );
23507
- const radius = point.size === void 0 ? 9 : clamp4(scale(point.size, sizeDomain, 7, 18), 7, 18);
24244
+ const radius = point.size === void 0 ? 9 : clamp5(scale(point.size, sizeDomain, 7, 18), 7, 18);
23508
24245
  const selected = selectedPointId === point.id;
23509
24246
  return /* @__PURE__ */ jsxRuntime.jsxs(
23510
24247
  "g",
@@ -24036,7 +24773,7 @@ var getDomain2 = (values, min5, max5) => {
24036
24773
  return lower < upper ? [lower, upper] : [upper, lower];
24037
24774
  };
24038
24775
  var scale2 = (value, domain, start, end) => start + (value - domain[0]) / (domain[1] - domain[0]) * (end - start);
24039
- var clamp5 = (value, min5, max5) => Math.min(max5, Math.max(min5, value));
24776
+ var clamp6 = (value, min5, max5) => Math.min(max5, Math.max(min5, value));
24040
24777
  var toneVariable = {
24041
24778
  primary: "--radar-series-one",
24042
24779
  secondary: "--radar-series-two",
@@ -24099,7 +24836,7 @@ function WorkbenchRadarMatrix({
24099
24836
  const plotRight = width;
24100
24837
  const plotTop = 0;
24101
24838
  const plotBottom = height;
24102
- const xThreshold = clamp5(
24839
+ const xThreshold = clamp6(
24103
24840
  scale2(
24104
24841
  Number.isFinite(thresholds?.x) ? Number(thresholds?.x) : (xDomain[0] + xDomain[1]) / 2,
24105
24842
  xDomain,
@@ -24109,7 +24846,7 @@ function WorkbenchRadarMatrix({
24109
24846
  plotLeft,
24110
24847
  plotRight
24111
24848
  );
24112
- const yThreshold = clamp5(
24849
+ const yThreshold = clamp6(
24113
24850
  scale2(
24114
24851
  Number.isFinite(thresholds?.y) ? Number(thresholds?.y) : (yDomain[0] + yDomain[1]) / 2,
24115
24852
  yDomain,
@@ -24217,13 +24954,13 @@ function WorkbenchRadarMatrix({
24217
24954
  ] }, quadrant.id);
24218
24955
  }),
24219
24956
  validPoints.map((point, index) => {
24220
- const x = clamp5(scale2(point.x, xDomain, 40, width - 40), 40, width - 40);
24221
- const y = clamp5(scale2(point.y, yDomain, height - 48, 54), 54, height - 48);
24957
+ const x = clamp6(scale2(point.x, xDomain, 40, width - 40), 40, width - 40);
24958
+ const y = clamp6(scale2(point.y, yDomain, height - 48, 54), 54, height - 48);
24222
24959
  const tone = point.tone || (index % 2 === 0 ? "primary" : "accent");
24223
24960
  const selected = selectedPointId === point.id;
24224
24961
  const labelled = labelledPointIds.has(point.id);
24225
- const radius = labelled ? clamp5(scale2(point.size ?? 1, sizeDomain, 5, 9), 5, 9) : 3.5;
24226
- const fontSize = labelled ? clamp5(scale2(point.size ?? 1, sizeDomain, 14, 31), 14, 31) : 0;
24962
+ const radius = labelled ? clamp6(scale2(point.size ?? 1, sizeDomain, 5, 9), 5, 9) : 3.5;
24963
+ const fontSize = labelled ? clamp6(scale2(point.size ?? 1, sizeDomain, 14, 31), 14, 31) : 0;
24227
24964
  const pointStyle = {
24228
24965
  fill: toneColor(tone),
24229
24966
  stroke: "hsl(var(--foreground) / 0.74)"
@@ -24325,8 +25062,8 @@ function WorkbenchRadarMatrix({
24325
25062
  validPoints.map((point, index) => /* @__PURE__ */ jsxRuntime.jsx(
24326
25063
  "circle",
24327
25064
  {
24328
- cx: clamp5(scale2(point.x, xDomain, 40, width - 40), 40, width - 40),
24329
- cy: clamp5(scale2(point.y, yDomain, height - 48, 54), 54, height - 48),
25065
+ cx: clamp6(scale2(point.x, xDomain, 40, width - 40), 40, width - 40),
25066
+ cy: clamp6(scale2(point.y, yDomain, height - 48, 54), 54, height - 48),
24330
25067
  r: "8",
24331
25068
  style: { fill: toneColor(point.tone || (index % 2 === 0 ? "primary" : "accent")) }
24332
25069
  },
@@ -27811,7 +28548,7 @@ var DEFAULT_ZOOM_STEP = 1.18;
27811
28548
  var DEFAULT_STAGE_PADDING = 16;
27812
28549
  var MAX_SAFE_SCALE = 1e6;
27813
28550
  var MIN_SAFE_SCALE = 1e-6;
27814
- var clamp6 = (value, min5, max5) => Math.min(max5, Math.max(min5, value));
28551
+ var clamp7 = (value, min5, max5) => Math.min(max5, Math.max(min5, value));
27815
28552
  var isNoDragTarget = (target) => target instanceof Element && Boolean(target.closest('[data-no-drag="true"]'));
27816
28553
  function ResetIcon() {
27817
28554
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -27935,7 +28672,7 @@ function DataExplorerImagePreview({
27935
28672
  availableWidth / size.width,
27936
28673
  availableHeight / size.height
27937
28674
  );
27938
- const nextScale = clamp6(
28675
+ const nextScale = clamp7(
27939
28676
  fitScale,
27940
28677
  normalizedMinScale,
27941
28678
  normalizedMaxScale
@@ -27989,7 +28726,7 @@ function DataExplorerImagePreview({
27989
28726
  if (!Number.isFinite(factor) || factor === 1) return;
27990
28727
  if (!Number.isFinite(stageX) || !Number.isFinite(stageY)) return;
27991
28728
  setView((current) => {
27992
- const nextScale = clamp6(
28729
+ const nextScale = clamp7(
27993
28730
  current.scale * factor,
27994
28731
  normalizedMinScale,
27995
28732
  normalizedMaxScale
@@ -29961,6 +30698,7 @@ exports.ToastProvider = MonkeysToastProvider;
29961
30698
  exports.Toaster = MonkeysToaster;
29962
30699
  exports.UserAccountMenu = UserAccountMenu;
29963
30700
  exports.WorkbenchAssetGallery = WorkbenchAssetGallery;
30701
+ exports.WorkbenchCollection = WorkbenchCollection;
29964
30702
  exports.WorkbenchContentPane = WorkbenchContentPane;
29965
30703
  exports.WorkbenchContentToolbar = WorkbenchContentToolbar;
29966
30704
  exports.WorkbenchDetailSidebar = WorkbenchDetailSidebar;