@inf-monkeys-tech/monkeys-design 1.0.53 → 1.0.54

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.
@@ -9097,7 +9097,11 @@ function WorkbenchCollection({
9097
9097
  const itemsRef = react.useRef(null);
9098
9098
  const loadMoreRef = react.useRef(null);
9099
9099
  const lastSelectedIndexRef = react.useRef(null);
9100
- const loadMoreLockedRef = react.useRef(false);
9100
+ const loadMoreRequestPendingRef = react.useRef(false);
9101
+ const loadMoreIntersectionActiveRef = react.useRef(false);
9102
+ const loadMoreIntersectionConsumedRef = react.useRef(false);
9103
+ const loadMoreScrollValidationPendingRef = react.useRef(false);
9104
+ const observedLoadMoreTargetRef = react.useRef(null);
9101
9105
  const layoutReadyRef = react.useRef(false);
9102
9106
  const [containerWidth, setContainerWidth] = react.useState(0);
9103
9107
  const [itemsNode, setItemsNode] = react.useState(null);
@@ -9255,38 +9259,91 @@ function WorkbenchCollection({
9255
9259
  const canLoadMore = typeof callbacks?.onLoadMore === "function";
9256
9260
  const effectiveError = state?.error ?? virtualizationError;
9257
9261
  react.useEffect(() => {
9258
- if (!loadingMore || !hasMore) loadMoreLockedRef.current = false;
9259
- }, [hasMore, loadingMore, normalizedItems.length, keysSignature]);
9262
+ if (!canLoadMore || !hasMore) {
9263
+ loadMoreIntersectionActiveRef.current = false;
9264
+ loadMoreIntersectionConsumedRef.current = false;
9265
+ loadMoreScrollValidationPendingRef.current = false;
9266
+ }
9267
+ }, [canLoadMore, hasMore]);
9260
9268
  const requestLoadMore = react.useCallback(() => {
9261
- if (!canLoadMore || !hasMore || loadingMore || loadMoreLockedRef.current) return;
9262
- loadMoreLockedRef.current = true;
9269
+ if (!canLoadMore || !hasMore || loadingMore || loadMoreRequestPendingRef.current) return;
9270
+ loadMoreRequestPendingRef.current = true;
9271
+ const releaseRequest = () => {
9272
+ loadMoreRequestPendingRef.current = false;
9273
+ };
9263
9274
  try {
9264
9275
  const result = callbacksRef.current?.onLoadMore?.();
9265
9276
  if (result && typeof result.then === "function") {
9266
- void result.catch(() => {
9267
- loadMoreLockedRef.current = false;
9268
- });
9277
+ void result.then(releaseRequest, releaseRequest);
9278
+ } else {
9279
+ releaseRequest();
9269
9280
  }
9270
9281
  } catch (error) {
9271
- loadMoreLockedRef.current = false;
9282
+ releaseRequest();
9272
9283
  throw error;
9273
9284
  }
9274
9285
  }, [canLoadMore, hasMore, loadingMore]);
9275
9286
  react.useEffect(() => {
9276
9287
  const sentinel = loadMoreRef.current;
9277
- if (!sentinel || !canLoadMore || !hasMore || loadingMore) return void 0;
9288
+ if (observedLoadMoreTargetRef.current !== sentinel) {
9289
+ observedLoadMoreTargetRef.current = sentinel;
9290
+ loadMoreIntersectionActiveRef.current = false;
9291
+ loadMoreIntersectionConsumedRef.current = false;
9292
+ loadMoreScrollValidationPendingRef.current = false;
9293
+ }
9294
+ if (!sentinel || !canLoadMore || !hasMore) return void 0;
9278
9295
  if (typeof IntersectionObserver !== "function") return void 0;
9279
9296
  const owner = resolveScrollOwner();
9297
+ let observerHasDelivered = false;
9280
9298
  const observer = new IntersectionObserver((entries) => {
9281
- if (!entries.some((entry) => entry.isIntersecting)) return;
9299
+ const isIntersecting = entries.some((entry) => entry.isIntersecting);
9300
+ if (loadMoreScrollValidationPendingRef.current) {
9301
+ observerHasDelivered = true;
9302
+ loadMoreScrollValidationPendingRef.current = false;
9303
+ loadMoreIntersectionActiveRef.current = isIntersecting;
9304
+ if (!isIntersecting) {
9305
+ loadMoreIntersectionConsumedRef.current = false;
9306
+ return;
9307
+ }
9308
+ if (loadMoreIntersectionConsumedRef.current) return;
9309
+ loadMoreIntersectionConsumedRef.current = true;
9310
+ requestLoadMore();
9311
+ return;
9312
+ }
9313
+ if (!observerHasDelivered) {
9314
+ observerHasDelivered = true;
9315
+ loadMoreIntersectionActiveRef.current = isIntersecting;
9316
+ if (!isIntersecting) loadMoreIntersectionConsumedRef.current = false;
9317
+ return;
9318
+ }
9319
+ if (!isIntersecting) {
9320
+ loadMoreIntersectionActiveRef.current = false;
9321
+ loadMoreIntersectionConsumedRef.current = false;
9322
+ return;
9323
+ }
9324
+ if (loadMoreIntersectionActiveRef.current) return;
9325
+ loadMoreIntersectionActiveRef.current = true;
9326
+ loadMoreIntersectionConsumedRef.current = true;
9282
9327
  requestLoadMore();
9283
9328
  }, {
9284
9329
  root: owner === rootRef.current ? owner : owner || null,
9285
9330
  rootMargin: config.loading?.rootMargin ?? "240px",
9286
9331
  threshold: normalizeThreshold(config.loading?.threshold)
9287
9332
  });
9333
+ const scrollTarget = owner ?? (typeof window === "undefined" ? null : window);
9334
+ const validateActiveIntersection = () => {
9335
+ if (!loadMoreIntersectionActiveRef.current || loadMoreIntersectionConsumedRef.current || loadMoreScrollValidationPendingRef.current) return;
9336
+ loadMoreScrollValidationPendingRef.current = true;
9337
+ observer.unobserve(sentinel);
9338
+ observer.takeRecords();
9339
+ observer.observe(sentinel);
9340
+ };
9288
9341
  observer.observe(sentinel);
9289
- return () => observer.disconnect();
9342
+ scrollTarget?.addEventListener("scroll", validateActiveIntersection, { passive: true });
9343
+ return () => {
9344
+ observer.disconnect();
9345
+ scrollTarget?.removeEventListener("scroll", validateActiveIntersection);
9346
+ };
9290
9347
  }, [canLoadMore, config.loading?.rootMargin, config.loading?.threshold, hasMore, keysSignature, loadingMore, normalizedItems.length, requestLoadMore, resolveScrollOwner]);
9291
9348
  react.useEffect(() => {
9292
9349
  if (!config.loading?.autoFill || !canLoadMore || !hasMore || loadingMore) return;
@@ -9294,7 +9351,10 @@ function WorkbenchCollection({
9294
9351
  if (!sentinel) return;
9295
9352
  const owner = resolveScrollOwner() ?? (config.layout.scrollOwner === "parent" ? rootRef.current?.parentElement ?? null : null) ?? rootRef.current;
9296
9353
  if (!owner || owner.clientHeight <= 0) return;
9297
- if (owner.scrollHeight <= owner.clientHeight + 1) requestLoadMore();
9354
+ if (owner.scrollHeight <= owner.clientHeight + 1) {
9355
+ loadMoreIntersectionConsumedRef.current = true;
9356
+ requestLoadMore();
9357
+ }
9298
9358
  }, [
9299
9359
  canLoadMore,
9300
9360
  config.layout.scrollOwner,
@@ -10138,6 +10198,187 @@ function WorkbenchGalleryView({
10138
10198
  }
10139
10199
  );
10140
10200
  }
10201
+ var DEFAULT_SECTIONS = [
10202
+ {
10203
+ id: "primary",
10204
+ layout: "rail",
10205
+ filterCount: 2,
10206
+ cardCount: 4,
10207
+ aspectRatios: [16 / 11]
10208
+ },
10209
+ {
10210
+ id: "secondary",
10211
+ layout: "grid",
10212
+ filterCount: 3,
10213
+ cardCount: 5,
10214
+ aspectRatios: [4 / 5, 1, 3 / 4, 5 / 4, 1]
10215
+ }
10216
+ ];
10217
+ function normalizeCount2(value, fallback, maximum) {
10218
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
10219
+ return fallback;
10220
+ return Math.min(Math.round(value), maximum);
10221
+ }
10222
+ function normalizeAspectRatio(value) {
10223
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0)
10224
+ return 1;
10225
+ return Math.min(Math.max(value, 0.5), 2);
10226
+ }
10227
+ function WorkbenchGallerySkeletonHeading({
10228
+ animated,
10229
+ filterCount
10230
+ }) {
10231
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-10 flex-wrap items-center justify-between gap-4", children: [
10232
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
10233
+ /* @__PURE__ */ jsxRuntime.jsx(
10234
+ BaseSkeleton,
10235
+ {
10236
+ variant: "line",
10237
+ animated,
10238
+ className: "h-6 w-36 rounded-md"
10239
+ }
10240
+ ),
10241
+ /* @__PURE__ */ jsxRuntime.jsx(BaseSkeleton, { variant: "line", animated, className: "h-3 w-12" })
10242
+ ] }),
10243
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap justify-end gap-2", children: Array.from({ length: filterCount }, (_, index) => /* @__PURE__ */ jsxRuntime.jsx(
10244
+ BaseSkeleton,
10245
+ {
10246
+ variant: "block",
10247
+ animated,
10248
+ className: "h-9 w-28 rounded-lg"
10249
+ },
10250
+ index
10251
+ )) })
10252
+ ] });
10253
+ }
10254
+ function WorkbenchGallerySkeleton({
10255
+ sections = DEFAULT_SECTIONS,
10256
+ animated = true,
10257
+ className,
10258
+ ...props
10259
+ }) {
10260
+ return /* @__PURE__ */ jsxRuntime.jsx(
10261
+ "div",
10262
+ {
10263
+ ...props,
10264
+ role: "status",
10265
+ "aria-busy": "true",
10266
+ "aria-label": "Loading gallery",
10267
+ "data-monkeys-component": "workbench-gallery-skeleton",
10268
+ className: cn("grid w-full gap-4", className),
10269
+ children: sections.map((section, sectionIndex) => {
10270
+ const layout = section.layout ?? "grid";
10271
+ const filterCount = normalizeCount2(section.filterCount, 2, 8);
10272
+ const cardCount = normalizeCount2(
10273
+ section.cardCount,
10274
+ layout === "rail" ? 4 : 5,
10275
+ 24
10276
+ );
10277
+ const ratios = section.aspectRatios?.length ? section.aspectRatios : [layout === "rail" ? 16 / 11 : 1];
10278
+ return /* @__PURE__ */ jsxRuntime.jsxs(
10279
+ "section",
10280
+ {
10281
+ className: "grid gap-4",
10282
+ "data-skeleton-section": section.id,
10283
+ children: [
10284
+ /* @__PURE__ */ jsxRuntime.jsx(
10285
+ WorkbenchGallerySkeletonHeading,
10286
+ {
10287
+ animated,
10288
+ filterCount
10289
+ }
10290
+ ),
10291
+ /* @__PURE__ */ jsxRuntime.jsx(
10292
+ "div",
10293
+ {
10294
+ className: cn(
10295
+ "grid items-start gap-4",
10296
+ layout === "rail" ? "grid-cols-1 sm:grid-cols-2 xl:grid-cols-4" : "grid-cols-2 md:grid-cols-3 xl:grid-cols-5"
10297
+ ),
10298
+ "data-skeleton-layout": layout,
10299
+ children: Array.from({ length: cardCount }, (_, index) => /* @__PURE__ */ jsxRuntime.jsx(
10300
+ "div",
10301
+ {
10302
+ className: "overflow-hidden rounded-[var(--radius)] border border-border bg-card",
10303
+ children: /* @__PURE__ */ jsxRuntime.jsx(
10304
+ BaseSkeleton,
10305
+ {
10306
+ variant: "block",
10307
+ animated,
10308
+ className: "h-auto w-full rounded-none",
10309
+ style: {
10310
+ aspectRatio: String(
10311
+ normalizeAspectRatio(ratios[index % ratios.length])
10312
+ )
10313
+ }
10314
+ }
10315
+ )
10316
+ },
10317
+ index
10318
+ ))
10319
+ }
10320
+ )
10321
+ ]
10322
+ },
10323
+ section.id || sectionIndex
10324
+ );
10325
+ })
10326
+ }
10327
+ );
10328
+ }
10329
+ function WorkbenchPageSkeleton({
10330
+ animated = true,
10331
+ className,
10332
+ ...props
10333
+ }) {
10334
+ const skeleton = (classNames) => /* @__PURE__ */ jsxRuntime.jsx(BaseSkeleton, { variant: "block", animated, className: classNames });
10335
+ return /* @__PURE__ */ jsxRuntime.jsxs(
10336
+ "div",
10337
+ {
10338
+ ...props,
10339
+ role: "status",
10340
+ "aria-busy": "true",
10341
+ "aria-label": "Loading page",
10342
+ "data-monkeys-component": "workbench-page-skeleton",
10343
+ className: cn(
10344
+ "flex size-full min-h-0 flex-col gap-4 overflow-hidden",
10345
+ className
10346
+ ),
10347
+ children: [
10348
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-10 shrink-0 items-center justify-between gap-4 rounded-lg border border-border bg-card px-4", children: [
10349
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
10350
+ skeleton("h-5 w-20"),
10351
+ skeleton("h-5 w-16"),
10352
+ skeleton("h-5 w-24")
10353
+ ] }),
10354
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
10355
+ skeleton("h-7 w-20"),
10356
+ skeleton("size-7")
10357
+ ] })
10358
+ ] }),
10359
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid shrink-0 grid-cols-1 gap-4 md:grid-cols-3", children: [
10360
+ skeleton("h-40 md:col-span-2"),
10361
+ skeleton("h-40")
10362
+ ] }),
10363
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid min-h-0 flex-1 grid-cols-1 gap-4 lg:grid-cols-[minmax(0,2fr)_minmax(14rem,1fr)]", children: [
10364
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-col gap-4 rounded-lg border border-border bg-card p-4", children: [
10365
+ skeleton("h-6 w-40"),
10366
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3", children: Array.from({ length: 6 }, (_, index) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
10367
+ skeleton("h-4 w-20"),
10368
+ skeleton("h-9 w-full")
10369
+ ] }, index)) }),
10370
+ skeleton("min-h-32 flex-1")
10371
+ ] }),
10372
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-40 flex-col gap-4 rounded-lg border border-border bg-card p-4", children: [
10373
+ skeleton("h-6 w-28"),
10374
+ skeleton("min-h-28 flex-1"),
10375
+ skeleton("h-9 w-full")
10376
+ ] })
10377
+ ] })
10378
+ ]
10379
+ }
10380
+ );
10381
+ }
10141
10382
  function toCssSize4(value) {
10142
10383
  if (value === void 0) return void 0;
10143
10384
  return typeof value === "number" ? `${value}px` : value;
@@ -11649,11 +11890,13 @@ exports.WorkbenchEvidenceList = WorkbenchEvidenceList;
11649
11890
  exports.WorkbenchGalleryCard = WorkbenchGalleryCard;
11650
11891
  exports.WorkbenchGallerySettingsButton = WorkbenchGallerySettingsButton;
11651
11892
  exports.WorkbenchGallerySettingsPanel = WorkbenchGallerySettingsPanel;
11893
+ exports.WorkbenchGallerySkeleton = WorkbenchGallerySkeleton;
11652
11894
  exports.WorkbenchGalleryView = WorkbenchGalleryView;
11653
11895
  exports.WorkbenchJourneyNavigation = WorkbenchJourneyNavigation;
11654
11896
  exports.WorkbenchLaneView = WorkbenchLaneView;
11655
11897
  exports.WorkbenchMasonryLayout = WorkbenchMasonryLayout;
11656
11898
  exports.WorkbenchMetricGrid = WorkbenchMetricGrid;
11899
+ exports.WorkbenchPageSkeleton = WorkbenchPageSkeleton;
11657
11900
  exports.WorkbenchRadarFilterChip = WorkbenchRadarFilterChip;
11658
11901
  exports.WorkbenchRadarInspectorSection = WorkbenchRadarInspectorSection;
11659
11902
  exports.WorkbenchRadarMatrix = WorkbenchRadarMatrix;