@questpie/admin 3.4.1 → 3.5.1

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 (101) hide show
  1. package/dist/client/blocks/block-renderer.d.mts +2 -2
  2. package/dist/client/builder/types/action-types.d.mts +2 -2
  3. package/dist/client/components/actions/action-dialog.mjs +7 -7
  4. package/dist/client/components/blocks/block-canvas.mjs +1 -1
  5. package/dist/client/components/blocks/block-editor-layout.mjs +2 -2
  6. package/dist/client/components/blocks/block-insert-button.mjs +3 -3
  7. package/dist/client/components/blocks/block-item-menu.mjs +9 -9
  8. package/dist/client/components/blocks/block-item.mjs +5 -5
  9. package/dist/client/components/blocks/block-library-sidebar.mjs +3 -3
  10. package/dist/client/components/fields/array-field.mjs +2 -2
  11. package/dist/client/components/fields/date-field.mjs +6 -5
  12. package/dist/client/components/fields/datetime-field.mjs +6 -1
  13. package/dist/client/components/fields/object-array-field.mjs +2 -2
  14. package/dist/client/components/fields/relation/displays/cards-display.mjs +1 -1
  15. package/dist/client/components/fields/relation/displays/grid-display.mjs +1 -1
  16. package/dist/client/components/fields/relation/displays/list-display.mjs +3 -3
  17. package/dist/client/components/fields/relation/displays/table-display.mjs +1 -1
  18. package/dist/client/components/fields/relation-picker.mjs +3 -3
  19. package/dist/client/components/fields/relation-select.mjs +2 -2
  20. package/dist/client/components/filter-builder/filter-builder-sheet.mjs +16 -16
  21. package/dist/client/components/history-sidebar.mjs +12 -4
  22. package/dist/client/components/layout/field-layout-renderer.mjs +8 -3
  23. package/dist/client/components/media/media-grid.mjs +2 -2
  24. package/dist/client/components/preview/live-preview-mode.mjs +4 -4
  25. package/dist/client/components/preview/preview-pane.mjs +4 -4
  26. package/dist/client/components/primitives/asset-preview.mjs +5 -5
  27. package/dist/client/components/primitives/dropzone.mjs +1 -1
  28. package/dist/client/components/ui/kbd.mjs +1 -1
  29. package/dist/client/components/ui/scroll-fade.mjs +4 -4
  30. package/dist/client/components/ui/sidebar.mjs +1 -1
  31. package/dist/client/components/ui/skeleton.mjs +1 -1
  32. package/dist/client/components/ui/table.mjs +1 -1
  33. package/dist/client/components/widgets/quick-actions-widget.mjs +6 -6
  34. package/dist/client/components/widgets/timeline-widget.mjs +3 -3
  35. package/dist/client/components/widgets/value-widget.mjs +1 -1
  36. package/dist/client/components/widgets/widget-skeletons.mjs +2 -2
  37. package/dist/client/hooks/typed-hooks.mjs +66 -21
  38. package/dist/client/hooks/use-collection.mjs +48 -7
  39. package/dist/client/hooks/use-server-actions.mjs +1 -0
  40. package/dist/client/i18n/date-locale.mjs +0 -14
  41. package/dist/client/preview/diff.mjs +4 -1
  42. package/dist/client/preview/patch.mjs +1 -1
  43. package/dist/client/preview/paths.mjs +85 -0
  44. package/dist/client/runtime/translations-provider.mjs +1 -1
  45. package/dist/client/scope/picker.d.mts +2 -2
  46. package/dist/client/scope/provider.d.mts +2 -2
  47. package/dist/client/styles/base.css +51 -1
  48. package/dist/client/views/auth/accept-invite-form.d.mts +2 -2
  49. package/dist/client/views/auth/auth-layout.d.mts +3 -3
  50. package/dist/client/views/auth/reset-password-form.d.mts +2 -2
  51. package/dist/client/views/auth/setup-form.d.mts +2 -2
  52. package/dist/client/views/collection/auto-form-fields.mjs +1 -1
  53. package/dist/client/views/collection/cells/primitive-cells.mjs +2 -2
  54. package/dist/client/views/collection/cells/upload-cells.mjs +2 -2
  55. package/dist/client/views/collection/form-view.mjs +45 -26
  56. package/dist/client/views/collection/table-view.mjs +33 -20
  57. package/dist/client/views/collection/view-skeletons.mjs +37 -38
  58. package/dist/client/views/common/global-search.mjs +3 -3
  59. package/dist/client/views/dashboard/widget-card.mjs +7 -7
  60. package/dist/client/views/layout/admin-router.mjs +84 -37
  61. package/dist/client/views/layout/admin-sidebar.mjs +22 -21
  62. package/dist/client/views/pages/accept-invite-page.d.mts +2 -2
  63. package/dist/client/views/pages/dashboard-page.d.mts +2 -2
  64. package/dist/client/views/pages/forgot-password-page.d.mts +2 -2
  65. package/dist/client/views/pages/invite-page.d.mts +2 -2
  66. package/dist/client/views/pages/login-page.d.mts +2 -2
  67. package/dist/components/rich-text/rich-text-renderer.d.mts +2 -2
  68. package/dist/factories.d.mts +0 -2
  69. package/dist/server/adapters/nextjs.d.mts +0 -1
  70. package/dist/server/augmentation/actions.d.mts +2 -0
  71. package/dist/server/i18n/messages/cs.mjs +2 -0
  72. package/dist/server/i18n/messages/de.mjs +2 -0
  73. package/dist/server/i18n/messages/en.mjs +2 -0
  74. package/dist/server/i18n/messages/es.mjs +2 -0
  75. package/dist/server/i18n/messages/fr.mjs +2 -0
  76. package/dist/server/i18n/messages/pl.mjs +2 -0
  77. package/dist/server/i18n/messages/pt.mjs +2 -0
  78. package/dist/server/i18n/messages/sk.mjs +2 -0
  79. package/dist/server/modules/admin/collections/account.d.mts +50 -50
  80. package/dist/server/modules/admin/collections/admin-locks.d.mts +49 -49
  81. package/dist/server/modules/admin/collections/admin-preferences.d.mts +39 -39
  82. package/dist/server/modules/admin/collections/admin-saved-views.d.mts +36 -36
  83. package/dist/server/modules/admin/collections/apikey.d.mts +68 -68
  84. package/dist/server/modules/admin/collections/assets.d.mts +34 -34
  85. package/dist/server/modules/admin/collections/session.d.mts +42 -42
  86. package/dist/server/modules/admin/collections/user.d.mts +14 -14
  87. package/dist/server/modules/admin/collections/user.mjs +9 -9
  88. package/dist/server/modules/admin/routes/admin-config.d.mts +2 -2
  89. package/dist/server/modules/admin/routes/execute-action.d.mts +9 -9
  90. package/dist/server/modules/admin/routes/execute-action.mjs +3 -2
  91. package/dist/server/modules/admin/routes/preview.d.mts +11 -11
  92. package/dist/server/modules/admin/routes/reactive.d.mts +9 -9
  93. package/dist/server/modules/admin/routes/setup.d.mts +7 -7
  94. package/dist/server/modules/admin/routes/widget-data.d.mts +5 -5
  95. package/dist/server/modules/admin-preferences/collections/saved-views.d.mts +23 -23
  96. package/dist/server/modules/audit/.generated/module.d.mts +6 -6
  97. package/dist/server/modules/audit/collections/audit-log.d.mts +37 -37
  98. package/dist/server.d.mts +0 -2
  99. package/package.json +3 -3
  100. package/dist/server/adapters/index.d.mts +0 -2
  101. package/dist/server/auth-helpers.d.mts +0 -1
@@ -1,5 +1,5 @@
1
1
  import { ScopePickerProps } from "./types.mjs";
2
- import * as react_jsx_runtime26 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime27 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/client/scope/picker.d.ts
5
5
 
@@ -48,6 +48,6 @@ declare function ScopePicker({
48
48
  clearText,
49
49
  className,
50
50
  compact
51
- }: ScopePickerProps): react_jsx_runtime26.JSX.Element;
51
+ }: ScopePickerProps): react_jsx_runtime27.JSX.Element;
52
52
  //#endregion
53
53
  export { ScopePicker };
@@ -1,5 +1,5 @@
1
1
  import { ScopeContextValue, ScopeProviderProps } from "./types.mjs";
2
- import * as react_jsx_runtime27 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime26 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/client/scope/provider.d.ts
5
5
 
@@ -29,7 +29,7 @@ declare function ScopeProvider({
29
29
  headerName,
30
30
  storageKey,
31
31
  defaultScope
32
- }: ScopeProviderProps): react_jsx_runtime27.JSX.Element;
32
+ }: ScopeProviderProps): react_jsx_runtime26.JSX.Element;
33
33
  /**
34
34
  * Hook to access the current scope context.
35
35
  *
@@ -718,6 +718,51 @@
718
718
  }
719
719
  }
720
720
 
721
+ /* =============================================================================
722
+ Loading States
723
+ ============================================================================= */
724
+
725
+ .qa-skeleton {
726
+ animation:
727
+ qa-skeleton-fade-in var(--motion-duration-base) var(--motion-ease-enter)
728
+ 120ms both,
729
+ qa-skeleton-breathe 1.6s ease-in-out 120ms infinite;
730
+ background-color: color-mix(in srgb, var(--muted) 86%, var(--foreground) 14%);
731
+ }
732
+
733
+ .qa-table-view-skeleton,
734
+ .qa-router-skeleton,
735
+ .qa-form-view-skeleton {
736
+ min-height: min(36rem, calc(100dvh - 14rem));
737
+ }
738
+
739
+ @keyframes qa-skeleton-fade-in {
740
+ from {
741
+ opacity: 0;
742
+ }
743
+ to {
744
+ opacity: 1;
745
+ }
746
+ }
747
+
748
+ @keyframes qa-skeleton-breathe {
749
+ 0%,
750
+ 100% {
751
+ background-color: color-mix(
752
+ in srgb,
753
+ var(--muted) 86%,
754
+ var(--foreground) 14%
755
+ );
756
+ }
757
+ 50% {
758
+ background-color: color-mix(
759
+ in srgb,
760
+ var(--muted) 68%,
761
+ var(--foreground) 32%
762
+ );
763
+ }
764
+ }
765
+
721
766
  /* =============================================================================
722
767
  Base Styles
723
768
  ============================================================================= */
@@ -936,9 +981,14 @@
936
981
  .animate-field-glow,
937
982
  .animate-realtime-pulse,
938
983
  .animate-realtime-delete,
939
- .animate-spin {
984
+ .animate-spin,
985
+ .qa-skeleton {
940
986
  animation: none !important;
941
987
  }
988
+
989
+ .qa-skeleton {
990
+ opacity: 1;
991
+ }
942
992
  }
943
993
 
944
994
  /* =============================================================================
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime2 from "react/jsx-runtime";
1
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
2
2
 
3
3
  //#region src/client/views/auth/accept-invite-form.d.ts
4
4
  /**
@@ -67,6 +67,6 @@ declare function AcceptInviteForm({
67
67
  className,
68
68
  error,
69
69
  minPasswordLength
70
- }: AcceptInviteFormProps): react_jsx_runtime2.JSX.Element;
70
+ }: AcceptInviteFormProps): react_jsx_runtime0.JSX.Element;
71
71
  //#endregion
72
72
  export { AcceptInviteForm };
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import * as react_jsx_runtime0 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime1 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/client/views/auth/auth-layout.d.ts
5
5
 
@@ -26,7 +26,7 @@ declare function AuthDefaultLogo({
26
26
  brandName
27
27
  }: {
28
28
  brandName: string;
29
- }): react_jsx_runtime0.JSX.Element;
29
+ }): react_jsx_runtime1.JSX.Element;
30
30
  /**
31
31
  * Minimal split layout for authentication pages (login, register, forgot password, etc.)
32
32
  *
@@ -50,6 +50,6 @@ declare function AuthDefaultLogo({
50
50
  * </AuthLayout>
51
51
  * ```
52
52
  */
53
- declare function AuthLayout(props: AuthLayoutProps): react_jsx_runtime0.JSX.Element;
53
+ declare function AuthLayout(props: AuthLayoutProps): react_jsx_runtime1.JSX.Element;
54
54
  //#endregion
55
55
  export { AuthDefaultLogo, AuthLayout, AuthLayoutProps };
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime6 from "react/jsx-runtime";
1
+ import * as react_jsx_runtime5 from "react/jsx-runtime";
2
2
 
3
3
  //#region src/client/views/auth/reset-password-form.d.ts
4
4
  /**
@@ -60,6 +60,6 @@ declare function ResetPasswordForm({
60
60
  minPasswordLength,
61
61
  className,
62
62
  error
63
- }: ResetPasswordFormProps): react_jsx_runtime6.JSX.Element;
63
+ }: ResetPasswordFormProps): react_jsx_runtime5.JSX.Element;
64
64
  //#endregion
65
65
  export { ResetPasswordForm };
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime7 from "react/jsx-runtime";
1
+ import * as react_jsx_runtime6 from "react/jsx-runtime";
2
2
 
3
3
  //#region src/client/views/auth/setup-form.d.ts
4
4
  /**
@@ -55,6 +55,6 @@ declare function SetupForm({
55
55
  className,
56
56
  error,
57
57
  minPasswordLength
58
- }: SetupFormProps): react_jsx_runtime7.JSX.Element;
58
+ }: SetupFormProps): react_jsx_runtime6.JSX.Element;
59
59
  //#endregion
60
60
  export { SetupForm, SetupFormValues };
@@ -481,7 +481,7 @@ function AutoFormFields({ app: _cms, collection, mode = "collection", config, re
481
481
  children: /* @__PURE__ */ jsx("div", {
482
482
  className: "bg-surface-low/45 flex min-h-0 flex-col rounded-md @2xl:h-full",
483
483
  children: /* @__PURE__ */ jsx("div", {
484
- className: "scrollbar-thin min-h-0 space-y-4 overflow-y-auto px-3 py-3 @2xl:flex-1",
484
+ className: "min-h-0 scrollbar-thin space-y-4 overflow-y-auto p-3 @2xl:flex-1",
485
485
  children: /* @__PURE__ */ jsx(SidebarRenderer, {
486
486
  sidebar: formConfig.sidebar,
487
487
  fields,
@@ -183,7 +183,7 @@ function SelectCell({ value, fieldDef }) {
183
183
  const options = getSelectOptions(fieldDef);
184
184
  return /* @__PURE__ */ jsx("span", {
185
185
  className: "inline-flex max-w-[300px] flex-wrap gap-1",
186
- children: values.map((item, index) => {
186
+ children: values.map((item) => {
187
187
  const option = findSelectOption(options, item);
188
188
  const icon = resolveIconElement(option?.icon);
189
189
  return /* @__PURE__ */ jsxs(Badge, {
@@ -196,7 +196,7 @@ function SelectCell({ value, fieldDef }) {
196
196
  t,
197
197
  locale
198
198
  })]
199
- }, `${String(item)}-${index}`);
199
+ }, String(item));
200
200
  })
201
201
  });
202
202
  }
@@ -79,11 +79,11 @@ function UploadManyCell({ value }) {
79
79
  return /* @__PURE__ */ jsxs("div", {
80
80
  className: "flex items-center gap-1",
81
81
  children: [/* @__PURE__ */ jsx("div", {
82
- className: "flex -space-x-2",
82
+ className: "flex",
83
83
  children: imageAssets.map((asset, index) => /* @__PURE__ */ jsx("img", {
84
84
  src: asset.url,
85
85
  alt: asset.filename || "Asset",
86
- className: "image-outline bg-background size-6 rounded object-cover"
86
+ className: `image-outline bg-background size-6 rounded object-cover${index > 0 ? " -ms-2" : ""}`
87
87
  }, asset.id || index))
88
88
  }), remaining > 0 && /* @__PURE__ */ jsxs("span", {
89
89
  className: "text-muted-foreground ml-1 text-xs tabular-nums",
@@ -9,6 +9,7 @@ import { LocaleSwitcher } from "../../components/locale-switcher.mjs";
9
9
  import { Label } from "../../components/ui/label.mjs";
10
10
  import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "../../components/ui/dialog.mjs";
11
11
  import { Badge } from "../../components/ui/badge.mjs";
12
+ import { useAdminConfig } from "../../hooks/use-admin-config.mjs";
12
13
  import { RenderProfiler } from "../../lib/render-profiler.mjs";
13
14
  import { scrollFieldIntoView } from "../../contexts/focus-context.mjs";
14
15
  import { Checkbox } from "../../components/ui/checkbox.mjs";
@@ -33,6 +34,7 @@ import { useServerActions } from "../../hooks/use-server-actions.mjs";
33
34
  import { useTransitionStage } from "../../hooks/use-transition-stage.mjs";
34
35
  import { applyPatchBatchImmutable, cloneSnapshot } from "../../preview/patch.mjs";
35
36
  import { diffSnapshot, diffSnapshotAtPath } from "../../preview/diff.mjs";
37
+ import { resolveKnownPreviewPath } from "../../preview/paths.mjs";
36
38
  import { detectManyToManyRelations, hasManyToManyRelations } from "../../utils/detect-relations.mjs";
37
39
  import { shouldHandleAdminShortcut } from "../../utils/keyboard-shortcuts.mjs";
38
40
  import { AdminViewHeader } from "../layout/admin-view-layout.mjs";
@@ -168,11 +170,12 @@ const FormStateRefBridge = React.memo(function FormStateRefBridge$1({ control, o
168
170
  }, [isSubmitting, onSubmittingChange]);
169
171
  return null;
170
172
  });
171
- const PreviewPatchBridge = React.memo(function PreviewPatchBridge$1({ form, previewRef, enabled }) {
173
+ const PreviewPatchBridge = React.memo(function PreviewPatchBridge$1({ form, previewRef, enabled, schema, blocks }) {
172
174
  const previousSnapshotRef = React.useRef(null);
173
175
  const snapshotVersionRef = React.useRef(void 0);
176
+ const subscribedFieldNames = React.useMemo(() => Object.keys(schema?.fields ?? {}).filter(isSafePreviewEditPath), [schema]);
174
177
  React.useEffect(() => {
175
- if (!enabled) {
178
+ if (!enabled || subscribedFieldNames.length === 0) {
176
179
  previousSnapshotRef.current = null;
177
180
  snapshotVersionRef.current = void 0;
178
181
  return;
@@ -194,37 +197,50 @@ const PreviewPatchBridge = React.memo(function PreviewPatchBridge$1({ form, prev
194
197
  if (pendingOps.size === 0 || animationFrame !== null) return;
195
198
  animationFrame = window.requestAnimationFrame(flushPendingOps);
196
199
  };
197
- const subscription = form.watch((values, info) => {
198
- const previousSnapshot = previousSnapshotRef.current;
199
- const nextValues = values;
200
- if (!previousSnapshot) {
201
- const nextSnapshot = cloneSnapshot(nextValues);
202
- previousSnapshotRef.current = nextSnapshot;
203
- snapshotVersionRef.current = previewRef.current?.sendInitSnapshot(nextSnapshot);
204
- return;
205
- }
206
- const changedPath = info?.name;
207
- let ops = changedPath && isSafePreviewEditPath(changedPath) ? diffSnapshotAtPath(previousSnapshot, nextValues, changedPath) : diffSnapshot(previousSnapshot, nextValues);
208
- if (ops.some((op) => !op.path)) {
209
- const nextSnapshot = cloneSnapshot(nextValues);
210
- previousSnapshotRef.current = nextSnapshot;
211
- snapshotVersionRef.current = previewRef.current?.sendInitSnapshot(nextSnapshot);
212
- pendingOps.clear();
213
- return;
200
+ const unsubscribe = form.subscribe({
201
+ name: subscribedFieldNames,
202
+ exact: false,
203
+ formState: { values: true },
204
+ callback: ({ values, name }) => {
205
+ const previousSnapshot = previousSnapshotRef.current;
206
+ const nextValues = values;
207
+ if (!previousSnapshot) {
208
+ const nextSnapshot = cloneSnapshot(nextValues);
209
+ previousSnapshotRef.current = nextSnapshot;
210
+ snapshotVersionRef.current = previewRef.current?.sendInitSnapshot(nextSnapshot);
211
+ return;
212
+ }
213
+ const changedPath = resolveKnownPreviewPath(name, {
214
+ schema,
215
+ blocks,
216
+ values: nextValues,
217
+ previousValues: previousSnapshot
218
+ });
219
+ let ops = changedPath ? diffSnapshotAtPath(previousSnapshot, nextValues, changedPath) : diffSnapshot(previousSnapshot, nextValues);
220
+ if (ops.some((op) => !op.path)) {
221
+ const nextSnapshot = cloneSnapshot(nextValues);
222
+ previousSnapshotRef.current = nextSnapshot;
223
+ snapshotVersionRef.current = previewRef.current?.sendInitSnapshot(nextSnapshot);
224
+ pendingOps.clear();
225
+ return;
226
+ }
227
+ ops = ops.filter((op) => op.path);
228
+ if (ops.length === 0) return;
229
+ previousSnapshotRef.current = applyPatchBatchImmutable(previousSnapshot, ops);
230
+ queuePatchOps(ops);
214
231
  }
215
- ops = ops.filter((op) => op.path);
216
- if (ops.length === 0) return;
217
- previousSnapshotRef.current = applyPatchBatchImmutable(previousSnapshot, ops);
218
- queuePatchOps(ops);
219
232
  });
220
233
  return () => {
221
234
  if (animationFrame !== null) window.cancelAnimationFrame(animationFrame);
222
- subscription.unsubscribe();
235
+ unsubscribe();
223
236
  };
224
237
  }, [
238
+ blocks,
225
239
  enabled,
226
240
  form,
227
- previewRef
241
+ previewRef,
242
+ schema,
243
+ subscribedFieldNames
228
244
  ]);
229
245
  return null;
230
246
  });
@@ -379,6 +395,7 @@ function FormView({ collection, id, config, viewConfig, navigate, basePath = "/a
379
395
  const resolveText = useResolveText();
380
396
  const isEditMode = !!id;
381
397
  const { fields: resolvedFields, schema, isLoading: isFieldsLoading } = useCollectionFields(collection, { fallbackFields: config?.fields });
398
+ const { data: adminConfig } = useAdminConfig();
382
399
  const resolvedFormConfig = React.useMemo(() => viewConfig ?? (config?.form)?.["~config"] ?? config?.form ?? schema?.admin?.form, [
383
400
  viewConfig,
384
401
  config?.form,
@@ -1214,7 +1231,9 @@ function FormView({ collection, id, config, viewConfig, navigate, basePath = "/a
1214
1231
  /* @__PURE__ */ jsx(PreviewPatchBridge, {
1215
1232
  form,
1216
1233
  previewRef,
1217
- enabled: isLivePreviewOpen && !!previewUrl && !isBlocked
1234
+ enabled: isLivePreviewOpen && !!previewUrl && !isBlocked && !!schema,
1235
+ schema,
1236
+ blocks: adminConfig?.blocks
1218
1237
  }),
1219
1238
  /* @__PURE__ */ jsx(ReactiveFieldsManager, {
1220
1239
  collection,
@@ -203,6 +203,13 @@ function getStickyLeftOffset(columns, index) {
203
203
  return left + getColumnSize(column, columnIndex === 0 ? 40 : 360);
204
204
  }, 0);
205
205
  }
206
+ function reconcileOrderIds(orderIds, itemIds) {
207
+ const knownIds = new Set(itemIds);
208
+ const next = orderIds.filter((id) => knownIds.has(id));
209
+ const nextIds = new Set(next);
210
+ for (const id of itemIds) if (!nextIds.has(id)) next.push(id);
211
+ return next;
212
+ }
206
213
  function getActionReferenceType(reference) {
207
214
  if (typeof reference === "string") return reference;
208
215
  if (typeof reference === "function") {
@@ -473,7 +480,7 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
473
480
  groupBy: defaultGroupBy
474
481
  }, collection, user?.id);
475
482
  const effectiveRealtime = viewState.config.realtime ?? resolvedRealtime;
476
- const visibleColumnsForExpansion = useMemo(() => viewState.config.visibleColumns.length > 0 ? viewState.config.visibleColumns : defaultColumns, [viewState.config.visibleColumns, defaultColumns]);
483
+ const visibleColumnsForExpansion = viewState.config.visibleColumns.length > 0 ? viewState.config.visibleColumns : defaultColumns;
477
484
  const expandedFields = useMemo(() => autoExpandFields({
478
485
  fields: resolvedFields,
479
486
  list: resolvedListConfig,
@@ -802,31 +809,19 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
802
809
  searchData?.docs,
803
810
  listData?.docs
804
811
  ]);
812
+ const itemIds = useMemo(() => items.map((item) => String(item.id)), [items]);
805
813
  const { isHighlighted } = useRealtimeHighlight(items, { enabled: effectiveRealtime && !isSearching });
806
814
  const { getLock, isLocked: isDocLocked } = useLocks({
807
815
  resourceType: "collection",
808
816
  resource: collection,
809
817
  realtime: effectiveRealtime
810
818
  });
811
- React.useEffect(() => {
812
- if (!isReorderMode) {
813
- setOptimisticOrderIds(null);
814
- return;
815
- }
816
- setOptimisticOrderIds((current) => {
817
- const itemIds = items.map((item) => String(item.id));
818
- if (!current) return itemIds;
819
- const knownIds = new Set(itemIds);
820
- const next = current.filter((id) => knownIds.has(id));
821
- for (const id of itemIds) if (!next.includes(id)) next.push(id);
822
- return next;
823
- });
824
- }, [isReorderMode, items]);
825
819
  const filteredItems = useMemo(() => {
826
820
  if (!isReorderMode || !optimisticOrderIds) return items;
821
+ const orderedIds = reconcileOrderIds(optimisticOrderIds, itemIds);
827
822
  const itemsById = new Map(items.map((item) => [String(item.id), item]));
828
823
  const seen = /* @__PURE__ */ new Set();
829
- const ordered = optimisticOrderIds.map((id) => {
824
+ const ordered = orderedIds.map((id) => {
830
825
  const item = itemsById.get(id);
831
826
  if (item) seen.add(id);
832
827
  return item;
@@ -838,6 +833,7 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
838
833
  return ordered;
839
834
  }, [
840
835
  isReorderMode,
836
+ itemIds,
841
837
  items,
842
838
  optimisticOrderIds
843
839
  ]);
@@ -860,13 +856,22 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
860
856
  desc: nextSort.direction === "desc"
861
857
  }]);
862
858
  viewState.setSort(nextSort);
859
+ setOptimisticOrderIds(itemIds);
863
860
  setIsReorderMode(true);
864
861
  return;
865
862
  }
866
- setIsReorderMode((active) => !active);
863
+ if (isReorderMode) {
864
+ setOptimisticOrderIds(null);
865
+ setIsReorderMode(false);
866
+ return;
867
+ }
868
+ setOptimisticOrderIds(itemIds);
869
+ setIsReorderMode(true);
867
870
  }, [
868
871
  canReorder,
869
872
  isOrderSortActive,
873
+ isReorderMode,
874
+ itemIds,
870
875
  orderDirection,
871
876
  viewState
872
877
  ]);
@@ -877,9 +882,17 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
877
882
  filters: []
878
883
  });
879
884
  };
885
+ const exitReorderMode = React.useCallback(() => {
886
+ setOptimisticOrderIds(null);
887
+ setIsReorderMode(false);
888
+ }, []);
880
889
  React.useEffect(() => {
881
- if (isReorderMode && !canReorder) setIsReorderMode(false);
882
- }, [isReorderMode, canReorder]);
890
+ if (isReorderMode && !canReorder) exitReorderMode();
891
+ }, [
892
+ canReorder,
893
+ exitReorderMode,
894
+ isReorderMode
895
+ ]);
883
896
  const table = useReactTable({
884
897
  data: filteredItems,
885
898
  columns: visibleColumnDefs,
@@ -1226,7 +1239,7 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
1226
1239
  }), /* @__PURE__ */ jsx(Button, {
1227
1240
  variant: "ghost",
1228
1241
  size: "xs",
1229
- onClick: () => setIsReorderMode(false),
1242
+ onClick: exitReorderMode,
1230
1243
  children: t("common.done")
1231
1244
  })]
1232
1245
  }),
@@ -13,8 +13,8 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
13
13
  const TABLE_SKELETON_COLUMNS = [
14
14
  {
15
15
  width: 40,
16
- header: "mx-auto h-4 w-4",
17
- cells: ["mx-auto h-4 w-4"]
16
+ header: "mx-auto size-4",
17
+ cells: ["mx-auto size-4"]
18
18
  },
19
19
  {
20
20
  width: 360,
@@ -66,7 +66,7 @@ function getColumnSizeStyle(width) {
66
66
  function getSkeletonWidth(widths, rowIndex) {
67
67
  return widths[rowIndex % widths.length] ?? widths[0] ?? "h-4 w-20";
68
68
  }
69
- function TableSkeletonRows({ rows = 8 }) {
69
+ function TableSkeletonRows({ rows = 12 }) {
70
70
  return /* @__PURE__ */ jsx(Fragment, { children: Array.from({ length: rows }, (_, rowIndex) => /* @__PURE__ */ jsx(TableRow, {
71
71
  className: "hover:bg-transparent",
72
72
  children: TABLE_SKELETON_COLUMNS.map((column, columnIndex) => {
@@ -88,7 +88,7 @@ function TableSkeletonShell() {
88
88
  className: "table-fixed",
89
89
  style: { width: TABLE_SKELETON_COLUMNS.reduce((total, column) => total + column.width, 0) },
90
90
  children: [
91
- /* @__PURE__ */ jsx("colgroup", { children: TABLE_SKELETON_COLUMNS.map((column, index) => /* @__PURE__ */ jsx("col", { style: { width: column.width } }, index)) }),
91
+ /* @__PURE__ */ jsx("colgroup", { children: TABLE_SKELETON_COLUMNS.map((column) => /* @__PURE__ */ jsx("col", { style: { width: column.width } }, `${column.width}-${column.header}`)) }),
92
92
  /* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsx(TableRow, {
93
93
  className: "hover:bg-transparent",
94
94
  children: TABLE_SKELETON_COLUMNS.map((column, index) => {
@@ -101,7 +101,7 @@ function TableSkeletonShell() {
101
101
  variant: "text",
102
102
  className: column.header
103
103
  })
104
- }, index);
104
+ }, `${column.width}-${column.header}`);
105
105
  })
106
106
  }) }),
107
107
  /* @__PURE__ */ jsx(TableBody, { children: /* @__PURE__ */ jsx(TableSkeletonRows, {}) })
@@ -187,44 +187,43 @@ function TableViewSkeleton() {
187
187
  */
188
188
  function FormViewSkeleton() {
189
189
  return /* @__PURE__ */ jsx("div", {
190
- className: "qa-form-view-skeleton container max-w-4xl py-6",
190
+ className: "qa-form-view-skeleton qa-form-view w-full",
191
+ "aria-busy": "true",
191
192
  children: /* @__PURE__ */ jsxs("div", {
192
193
  className: "space-y-6",
193
- children: [/* @__PURE__ */ jsxs("div", {
194
- className: "flex items-center justify-between",
195
- children: [/* @__PURE__ */ jsxs("div", {
196
- className: "flex items-center gap-3",
194
+ children: [
195
+ /* @__PURE__ */ jsx("span", {
196
+ className: "sr-only",
197
+ children: "Loading form view"
198
+ }),
199
+ /* @__PURE__ */ jsx(AdminViewHeader, {
200
+ title: /* @__PURE__ */ jsx(Skeleton, {
201
+ variant: "text",
202
+ className: "h-7 w-48 max-w-full"
203
+ }),
204
+ meta: /* @__PURE__ */ jsx(Skeleton, {
205
+ variant: "text",
206
+ className: "h-3 w-36 max-w-full"
207
+ }),
208
+ actions: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-20" }), /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-24" })] })
209
+ }),
210
+ /* @__PURE__ */ jsxs("div", {
211
+ className: "space-y-6",
197
212
  children: [
198
- /* @__PURE__ */ jsx(Skeleton, {
199
- variant: "text",
200
- className: "h-8 w-8"
201
- }),
202
- " ",
203
- /* @__PURE__ */ jsx(Skeleton, {
204
- variant: "text",
205
- className: "h-8 w-48"
213
+ /* @__PURE__ */ jsx(FormFieldSkeleton, {}),
214
+ /* @__PURE__ */ jsx(FormFieldSkeleton, {}),
215
+ /* @__PURE__ */ jsx(FormFieldSkeleton, {}),
216
+ /* @__PURE__ */ jsx(FormFieldSkeleton, {}),
217
+ /* @__PURE__ */ jsxs("div", {
218
+ className: "space-y-2",
219
+ children: [/* @__PURE__ */ jsx(Skeleton, {
220
+ variant: "text",
221
+ className: "h-4 w-32"
222
+ }), /* @__PURE__ */ jsx(Skeleton, { className: "h-32 w-full" })]
206
223
  })
207
224
  ]
208
- }), /* @__PURE__ */ jsxs("div", {
209
- className: "flex items-center gap-2",
210
- children: [/* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-20" }), /* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-24" })]
211
- })]
212
- }), /* @__PURE__ */ jsxs("div", {
213
- className: "space-y-6",
214
- children: [
215
- /* @__PURE__ */ jsx(FormFieldSkeleton, {}),
216
- /* @__PURE__ */ jsx(FormFieldSkeleton, {}),
217
- /* @__PURE__ */ jsx(FormFieldSkeleton, {}),
218
- /* @__PURE__ */ jsx(FormFieldSkeleton, {}),
219
- /* @__PURE__ */ jsxs("div", {
220
- className: "space-y-2",
221
- children: [/* @__PURE__ */ jsx(Skeleton, {
222
- variant: "text",
223
- className: "h-4 w-32"
224
- }), /* @__PURE__ */ jsx(Skeleton, { className: "h-32 w-full" })]
225
- })
226
- ]
227
- })]
225
+ })
226
+ ]
228
227
  })
229
228
  });
230
229
  }
@@ -94,8 +94,8 @@ const SearchGroup = React.memo(function SearchGroup$1({ title, items, selectedIn
94
94
  "data-selected": isSelected,
95
95
  className: cn("item-surface flex w-full items-center gap-3 border-transparent px-3 py-2.5 text-sm transition-colors outline-none", isSelected ? "border-border bg-accent text-accent-foreground" : "hover:border-border hover:bg-accent hover:text-accent-foreground"),
96
96
  children: [item.icon && /* @__PURE__ */ jsx("span", {
97
- className: "text-muted-foreground flex h-4 w-4 shrink-0 items-center justify-center",
98
- children: resolveIconElement(item.icon, { className: "h-4 w-4" })
97
+ className: "text-muted-foreground flex size-4 shrink-0 items-center justify-center",
98
+ children: resolveIconElement(item.icon, { className: "size-4" })
99
99
  }), /* @__PURE__ */ jsxs("div", {
100
100
  className: "flex min-w-0 flex-col items-start",
101
101
  children: [item.highlights?.title ? /* @__PURE__ */ jsx("span", {
@@ -372,7 +372,7 @@ function GlobalSearch({ isOpen, onClose, navigate, basePath: basePathProp }) {
372
372
  className: "flex items-center justify-center gap-2",
373
373
  children: [/* @__PURE__ */ jsx(Icon, {
374
374
  icon: "ph:spinner",
375
- className: "h-4 w-4 animate-spin"
375
+ className: "size-4 animate-spin"
376
376
  }), /* @__PURE__ */ jsx("span", { children: t("globalSearch.searching") })]
377
377
  }) : t("globalSearch.noResults")
378
378
  })
@@ -52,7 +52,7 @@ function WidgetCardError({ error, variant = "default", onRetry }) {
52
52
  onClick: onRetry,
53
53
  children: /* @__PURE__ */ jsx(Icon, {
54
54
  icon: "ph:arrow-clockwise",
55
- className: "h-3.5 w-3.5"
55
+ className: "size-3.5"
56
56
  })
57
57
  }) })]
58
58
  }), /* @__PURE__ */ jsx(CardContent, {
@@ -106,7 +106,7 @@ function WidgetCard({ title, description, icon, variant = "default", isLoading,
106
106
  className: "qa-widget-card__header shrink-0",
107
107
  children: [/* @__PURE__ */ jsxs("div", {
108
108
  className: "flex min-w-0 items-center gap-2",
109
- children: [resolveIconElement(icon, { className: "h-4 w-4 text-muted-foreground" }), /* @__PURE__ */ jsxs("div", {
109
+ children: [resolveIconElement(icon, { className: "size-4 text-muted-foreground" }), /* @__PURE__ */ jsxs("div", {
110
110
  className: "min-w-0 flex-1",
111
111
  children: [title && /* @__PURE__ */ jsx(CardTitle, {
112
112
  className: "truncate text-sm font-medium",
@@ -124,19 +124,19 @@ function WidgetCard({ title, description, icon, variant = "default", isLoading,
124
124
  size: "icon-xs",
125
125
  onClick: actions[0].onClick,
126
126
  title: actions[0].label,
127
- children: resolveIconElement(actions[0].icon, { className: "h-3.5 w-3.5" })
127
+ children: resolveIconElement(actions[0].icon, { className: "size-3.5" })
128
128
  }, actions[0].id), actions.length > 1 && /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, { render: /* @__PURE__ */ jsx(Button, {
129
129
  variant: "ghost",
130
130
  size: "icon-xs",
131
131
  children: /* @__PURE__ */ jsx(Icon, {
132
132
  icon: "ph:dots-three-vertical",
133
- className: "h-3.5 w-3.5"
133
+ className: "size-3.5"
134
134
  })
135
135
  }) }), /* @__PURE__ */ jsx(DropdownMenuContent, {
136
136
  align: "end",
137
137
  children: actions.slice(1).map((action) => /* @__PURE__ */ jsxs(DropdownMenuItem, {
138
138
  onClick: action.onClick,
139
- children: [resolveIconElement(action.icon, { className: "h-3.5 w-3.5 mr-2" }), action.label]
139
+ children: [resolveIconElement(action.icon, { className: "size-3.5 mr-2" }), action.label]
140
140
  }, action.id))
141
141
  })] })] }),
142
142
  onRefresh && /* @__PURE__ */ jsx(Button, {
@@ -147,7 +147,7 @@ function WidgetCard({ title, description, icon, variant = "default", isLoading,
147
147
  disabled: isRefreshing,
148
148
  children: /* @__PURE__ */ jsx(Icon, {
149
149
  icon: "ph:arrow-clockwise",
150
- className: cn("h-3.5 w-3.5", isRefreshing && "animate-spin")
150
+ className: cn("size-3.5", isRefreshing && "animate-spin")
151
151
  })
152
152
  }),
153
153
  onExpand && /* @__PURE__ */ jsx(Button, {
@@ -157,7 +157,7 @@ function WidgetCard({ title, description, icon, variant = "default", isLoading,
157
157
  title: t("ui.expand"),
158
158
  children: /* @__PURE__ */ jsx(Icon, {
159
159
  icon: "ph:arrows-out-simple",
160
- className: "h-3.5 w-3.5"
160
+ className: "size-3.5"
161
161
  })
162
162
  })
163
163
  ]