xladmin 0.2.7 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.d.ts CHANGED
@@ -2,6 +2,14 @@ import type { AdminChoicesResponse, AdminDeletePreviewResponse, AdminDetailRespo
2
2
  export type XLAdminRequestOptions = {
3
3
  signal?: AbortSignal;
4
4
  };
5
+ export type XLAdminSelectionScope = {
6
+ q?: string;
7
+ filters?: Record<string, string>;
8
+ };
9
+ export type XLAdminSelectionOptions = {
10
+ selectAll?: boolean;
11
+ selectionScope?: XLAdminSelectionScope;
12
+ };
5
13
  export type XLAdminClient = {
6
14
  getModels: () => Promise<AdminModelsResponse>;
7
15
  getModel: (slug: string) => Promise<AdminModelMeta>;
@@ -16,11 +24,11 @@ export type XLAdminClient = {
16
24
  patchItem: (slug: string, id: string | number, payload: Record<string, unknown>) => Promise<AdminDetailResponse>;
17
25
  deleteItem: (slug: string, id: string | number) => Promise<void>;
18
26
  getDeletePreview: (slug: string, id: string | number) => Promise<AdminDeletePreviewResponse>;
19
- bulkDelete: (slug: string, ids: Array<string | number>) => Promise<{
27
+ bulkDelete: (slug: string, ids: Array<string | number>, options?: XLAdminSelectionOptions) => Promise<{
20
28
  deleted: number;
21
29
  }>;
22
- getBulkDeletePreview: (slug: string, ids: Array<string | number>) => Promise<AdminDeletePreviewResponse>;
23
- runBulkAction: (slug: string, actionSlug: string, ids: Array<string | number>, payload?: Record<string, unknown>) => Promise<{
30
+ getBulkDeletePreview: (slug: string, ids: Array<string | number>, options?: XLAdminSelectionOptions) => Promise<AdminDeletePreviewResponse>;
31
+ runBulkAction: (slug: string, actionSlug: string, ids: Array<string | number>, payload?: Record<string, unknown>, options?: XLAdminSelectionOptions) => Promise<{
24
32
  processed: number;
25
33
  } & Record<string, unknown>>;
26
34
  runObjectAction: (slug: string, id: string | number, actionSlug: string, payload?: Record<string, unknown>) => Promise<AdminObjectActionResponse>;
@@ -1,5 +1,5 @@
1
- import type { XLAdminClient } from '../client';
2
- import type { AdminFieldMeta } from '../types';
1
+ import type { XLAdminClient } from '@xladmin-core/client';
2
+ import type { AdminFieldMeta } from '@xladmin-core/types';
3
3
  type AdminFieldEditorProps = {
4
4
  field: AdminFieldMeta;
5
5
  value: unknown;
@@ -1,3 +1,4 @@
1
+ import type { ReactNode } from 'react';
1
2
  import type { XLAdminClient } from '../client';
2
3
  import type { XLAdminRouter } from '../router';
3
4
  type AdminModelPageProps = {
@@ -5,7 +6,24 @@ type AdminModelPageProps = {
5
6
  basePath: string;
6
7
  slug: string;
7
8
  router?: XLAdminRouter;
9
+ renderBeforePagination?: (context: ModelPageToolbarContext) => ReactNode;
8
10
  };
9
11
  export type ModelPageProps = AdminModelPageProps;
10
- export declare function ModelPage({ client, basePath, slug, router }: ModelPageProps): import("react/jsx-runtime").JSX.Element;
12
+ export type ModelPageToolbarContext = {
13
+ client: XLAdminClient;
14
+ slug: string;
15
+ meta: {
16
+ slug: string;
17
+ title: string;
18
+ };
19
+ selectedIds: Array<string | number>;
20
+ isAllMatchingSelected: boolean;
21
+ selectionCount: number;
22
+ total: number;
23
+ appliedQuery: string;
24
+ sortValue: string;
25
+ appliedFilters: Record<string, string>;
26
+ refresh: () => Promise<void>;
27
+ };
28
+ export declare function ModelPage({ client, basePath, slug, router, renderBeforePagination }: ModelPageProps): import("react/jsx-runtime").JSX.Element;
11
29
  export {};
@@ -1,5 +1,5 @@
1
1
  import { type ReactNode } from 'react';
2
- import type { AdminLocale, AdminModelMeta, AdminModelsBlockMeta } from '../../types';
2
+ import type { AdminLocale, AdminModelMeta, AdminModelsBlockMeta } from '@xladmin-core/types';
3
3
  type AdminDataContextValue = {
4
4
  locale: AdminLocale;
5
5
  models: AdminModelMeta[];
@@ -1,4 +1,4 @@
1
- import type { AdminModelMeta, AdminModelsBlockMeta } from '../../types';
1
+ import type { AdminModelMeta, AdminModelsBlockMeta } from '@xladmin-core/types';
2
2
  type SidebarProps = {
3
3
  models: AdminModelMeta[];
4
4
  blocks: AdminModelsBlockMeta[];
@@ -1,5 +1,5 @@
1
- import type { XLAdminClient } from '../../client';
2
- import type { AdminListFilterMeta } from '../../types';
1
+ import type { XLAdminClient } from '@xladmin-core/client';
2
+ import type { AdminListFilterMeta } from '@xladmin-core/types';
3
3
  type ListFiltersSidebarProps = {
4
4
  client: XLAdminClient;
5
5
  slug: string;
@@ -1,5 +1,5 @@
1
1
  import type { MouseEvent } from 'react';
2
- import type { AdminFieldMeta, AdminLocale } from '../../types';
2
+ import type { AdminFieldMeta, AdminLocale } from '@xladmin-core/types';
3
3
  export type ListRowProps = {
4
4
  row: Record<string, unknown>;
5
5
  pkField: string;
@@ -1,7 +1,7 @@
1
- import type { XLAdminClient } from '../../client';
2
- import type { AdminTranslationKey } from '../../i18n';
3
- import type { XLAdminRouter } from '../../router';
4
- import type { AdminDeletePreviewResponse, AdminListResponse } from '../../types';
1
+ import type { XLAdminClient } from '@xladmin-core/client';
2
+ import type { AdminTranslationKey } from '@xladmin-core/i18n';
3
+ import type { XLAdminRouter } from '@xladmin-core/router';
4
+ import type { AdminDeletePreviewResponse, AdminListResponse } from '@xladmin-core/types';
5
5
  type UseModelPageControllerOptions = {
6
6
  client: XLAdminClient;
7
7
  slug: string;
@@ -15,7 +15,8 @@ export declare function useModelPageController({ client, slug, pathname, locatio
15
15
  appliedFilters: Record<string, string>;
16
16
  appliedQuery: string;
17
17
  bulkActionMenuAnchor: HTMLElement | null;
18
- bulkActions: import("../..").AdminBulkActionMeta[];
18
+ bulkActions: import("@xladmin-core/types").AdminBulkActionMeta[];
19
+ clearBulkDeleteState: () => void;
19
20
  createOpen: boolean;
20
21
  currentPage: number;
21
22
  data: AdminListResponse | null;
@@ -23,8 +24,11 @@ export declare function useModelPageController({ client, slug, pathname, locatio
23
24
  deletePreviewError: string | null;
24
25
  deletePreviewOpen: boolean;
25
26
  error: string | null;
26
- fieldMap: Map<string, import("../..").AdminFieldMeta>;
27
+ fieldMap: Map<string, import("@xladmin-core/types").AdminFieldMeta>;
27
28
  filtersOpen: boolean;
29
+ handleClearDeletePreview: () => void;
30
+ handleCloseBulkActionMenu: () => void;
31
+ handleCloseRowMenu: () => void;
28
32
  handleConfirmDelete: () => Promise<void>;
29
33
  handleFilterChange: (filterSlug: string, value: string) => void;
30
34
  handleOpenRowMenu: (event: {
@@ -36,16 +40,19 @@ export declare function useModelPageController({ client, slug, pathname, locatio
36
40
  handleRowDelete: (rowId: string | number) => Promise<void>;
37
41
  handleRunNamedBulkAction: (actionSlug: string) => Promise<void>;
38
42
  handleSearchCommit: (nextQuery: string) => void;
43
+ handleSelectAllMatching: () => void;
39
44
  handleToggleAllVisible: (checked: boolean) => void;
40
45
  handleToggleSelection: (rowId: string | number, checked: boolean) => void;
41
46
  hasListFilters: boolean;
47
+ hasSelection: boolean;
42
48
  hasVisibleSelection: boolean;
49
+ isAllMatchingSelected: boolean;
43
50
  isDeletePreviewLoading: boolean;
44
51
  isDeleteSubmitting: boolean;
45
52
  isLoading: boolean;
46
53
  listFields: string[];
47
- listFilters: import("../..").AdminListFilterMeta[];
48
- meta: import("../..").AdminModelMeta | null;
54
+ listFilters: import("@xladmin-core/types").AdminListFilterMeta[];
55
+ meta: import("@xladmin-core/types").AdminModelMeta | null;
49
56
  pageInput: string;
50
57
  pendingDeleteIds: (string | number)[];
51
58
  pendingDeleteMode: "single" | "bulk";
@@ -53,6 +60,7 @@ export declare function useModelPageController({ client, slug, pathname, locatio
53
60
  rowActionMenuAnchor: HTMLElement | null;
54
61
  rowActionMenuId: string | number | null;
55
62
  rows: Record<string, unknown>[];
63
+ selectionCount: number;
56
64
  selectedIdSet: Set<string>;
57
65
  selectedIds: (string | number)[];
58
66
  setBulkActionMenuAnchor: import("react").Dispatch<import("react").SetStateAction<HTMLElement | null>>;
@@ -62,9 +70,7 @@ export declare function useModelPageController({ client, slug, pathname, locatio
62
70
  setDeletePreviewOpen: import("react").Dispatch<import("react").SetStateAction<boolean>>;
63
71
  setFiltersOpen: import("react").Dispatch<import("react").SetStateAction<boolean>>;
64
72
  setPageInput: import("react").Dispatch<import("react").SetStateAction<string>>;
65
- setPendingDeleteIds: import("react").Dispatch<import("react").SetStateAction<(string | number)[]>>;
66
- setRowActionMenuAnchor: import("react").Dispatch<import("react").SetStateAction<HTMLElement | null>>;
67
- setRowActionMenuId: import("react").Dispatch<import("react").SetStateAction<string | number | null>>;
73
+ sortValue: string;
68
74
  sortFields: string[];
69
75
  toggleSort: (fieldName: string) => void;
70
76
  total: number;
@@ -1,4 +1,4 @@
1
- import type { AdminModelMeta } from '../../types';
1
+ import type { AdminModelMeta } from '@xladmin-core/types';
2
2
  export type NormalizedBlock = {
3
3
  slug: string;
4
4
  title: string;
@@ -1,5 +1,5 @@
1
- import type { XLAdminClient } from '../../client';
2
- import type { AdminFieldMeta } from '../../types';
1
+ import type { XLAdminClient } from '@xladmin-core/client';
2
+ import type { AdminFieldMeta } from '@xladmin-core/types';
3
3
  type ObjectFieldProps = {
4
4
  field: AdminFieldMeta;
5
5
  value: unknown;
@@ -1,4 +1,4 @@
1
- import type { AdminFieldMeta } from '../../types';
1
+ import type { AdminFieldMeta } from '@xladmin-core/types';
2
2
  type ReadonlyObjectFieldProps = {
3
3
  field: AdminFieldMeta;
4
4
  value: string;
@@ -1,7 +1,7 @@
1
- import type { XLAdminClient } from '../../client';
2
- import type { AdminTranslationKey } from '../../i18n';
3
- import type { XLAdminRouter } from '../../router';
4
- import type { AdminDeletePreviewResponse, AdminDetailResponse } from '../../types';
1
+ import type { XLAdminClient } from '@xladmin-core/client';
2
+ import type { AdminTranslationKey } from '@xladmin-core/i18n';
3
+ import type { XLAdminRouter } from '@xladmin-core/router';
4
+ import type { AdminDeletePreviewResponse, AdminDetailResponse } from '@xladmin-core/types';
5
5
  type UseObjectPageControllerOptions = {
6
6
  client: XLAdminClient;
7
7
  slug: string;
@@ -20,7 +20,7 @@ export declare function useObjectPageController({ client, slug, id, listPath, ro
20
20
  detailFields: string[];
21
21
  editableFieldNames: Set<string>;
22
22
  error: string | null;
23
- fieldMap: Map<string, import("../..").AdminFieldMeta>;
23
+ fieldMap: Map<string, import("@xladmin-core/types").AdminFieldMeta>;
24
24
  handleDelete: () => Promise<void>;
25
25
  handleFieldChange: (fieldName: string, nextValue: unknown) => void;
26
26
  handleNavigateBack: () => void;
@@ -34,8 +34,8 @@ export declare function useObjectPageController({ client, slug, id, listPath, ro
34
34
  isDirty: boolean;
35
35
  isLoading: boolean;
36
36
  isSaving: boolean;
37
- meta: import("../..").AdminModelMeta | null;
38
- objectActions: import("../..").AdminObjectActionMeta[];
37
+ meta: import("@xladmin-core/types").AdminModelMeta | null;
38
+ objectActions: import("@xladmin-core/types").AdminObjectActionMeta[];
39
39
  objectTitle: string;
40
40
  setActionsAnchorEl: import("react").Dispatch<import("react").SetStateAction<HTMLElement | null>>;
41
41
  setDeleteConfirmOpen: import("react").Dispatch<import("react").SetStateAction<boolean>>;
package/dist/index.d.mts CHANGED
@@ -2,7 +2,7 @@ export { createAxiosXLAdminClient, createFetchXLAdminClient, createFetchXLAdminT
2
2
  export { XLAdminRouterProvider, buildUrlWithParams, createBrowserXLAdminRouter, handleNavLinkClick, type XLAdminAnchorProps, type XLAdminLocation, type XLAdminRouter, useXLAdminLocation, useXLAdminRouter, } from './router';
3
3
  export { FormDialog, type FormDialogProps } from './components/FormDialog';
4
4
  export { OverviewPage, type OverviewPageProps } from './components/OverviewPage';
5
- export { ModelPage, type ModelPageProps } from './components/ModelPage';
5
+ export { ModelPage, type ModelPageProps, type ModelPageToolbarContext } from './components/ModelPage';
6
6
  export { ObjectPage, type ObjectPageProps } from './components/ObjectPage';
7
7
  export { Shell, type ShellProps } from './components/Shell';
8
8
  export { FieldEditor, type FieldEditorProps } from './components/FieldEditor';
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export { createAxiosXLAdminClient, createFetchXLAdminClient, createFetchXLAdminT
2
2
  export { XLAdminRouterProvider, buildUrlWithParams, createBrowserXLAdminRouter, handleNavLinkClick, type XLAdminAnchorProps, type XLAdminLocation, type XLAdminRouter, useXLAdminLocation, useXLAdminRouter, } from './router';
3
3
  export { FormDialog, type FormDialogProps } from './components/FormDialog';
4
4
  export { OverviewPage, type OverviewPageProps } from './components/OverviewPage';
5
- export { ModelPage, type ModelPageProps } from './components/ModelPage';
5
+ export { ModelPage, type ModelPageProps, type ModelPageToolbarContext } from './components/ModelPage';
6
6
  export { ObjectPage, type ObjectPageProps } from './components/ObjectPage';
7
7
  export { Shell, type ShellProps } from './components/Shell';
8
8
  export { FieldEditor, type FieldEditorProps } from './components/FieldEditor';
package/dist/index.js CHANGED
@@ -25,17 +25,25 @@ function createXLAdminClient(transport) {
25
25
  async getDeletePreview(slug, id) {
26
26
  return await transportGet(transport, `/xladmin/models/${slug}/items/${id}/delete-preview/`);
27
27
  },
28
- async bulkDelete(slug, ids) {
29
- return await transportPost(transport, `/xladmin/models/${slug}/bulk-delete/`, { ids });
28
+ async bulkDelete(slug, ids, options) {
29
+ return await transportPost(
30
+ transport,
31
+ `/xladmin/models/${slug}/bulk-delete/`,
32
+ buildSelectionPayload(ids, void 0, options)
33
+ );
30
34
  },
31
- async getBulkDeletePreview(slug, ids) {
32
- return await transportPost(transport, `/xladmin/models/${slug}/bulk-delete-preview/`, { ids });
35
+ async getBulkDeletePreview(slug, ids, options) {
36
+ return await transportPost(
37
+ transport,
38
+ `/xladmin/models/${slug}/bulk-delete-preview/`,
39
+ buildSelectionPayload(ids, void 0, options)
40
+ );
33
41
  },
34
- async runBulkAction(slug, actionSlug, ids, payload) {
42
+ async runBulkAction(slug, actionSlug, ids, payload, options) {
35
43
  return await transportPost(
36
44
  transport,
37
45
  `/xladmin/models/${slug}/bulk-actions/${actionSlug}/`,
38
- { ids, ...payload != null ? payload : {} }
46
+ buildSelectionPayload(ids, payload, options)
39
47
  );
40
48
  },
41
49
  async runObjectAction(slug, id, actionSlug, payload) {
@@ -181,6 +189,23 @@ function buildSearchParams(params) {
181
189
  }
182
190
  return searchParams.toString();
183
191
  }
192
+ function buildSelectionPayload(ids, payload, options) {
193
+ var _a;
194
+ const result = {
195
+ ids,
196
+ ...payload != null ? payload : {}
197
+ };
198
+ if (options == null ? void 0 : options.selectAll) {
199
+ result.select_all = true;
200
+ }
201
+ if (options == null ? void 0 : options.selectionScope) {
202
+ result.selection_scope = {
203
+ q: options.selectionScope.q,
204
+ filters: (_a = options.selectionScope.filters) != null ? _a : {}
205
+ };
206
+ }
207
+ return result;
208
+ }
184
209
 
185
210
  // src/router.tsx
186
211
  import { createContext, useContext, useMemo, useRef, useSyncExternalStore } from "react";
@@ -762,7 +787,7 @@ var FieldEditor = memo(function FieldEditor2({
762
787
  label: field.label,
763
788
  disabled: readOnly,
764
789
  value: value ? dayjs(String(value)) : null,
765
- onChange: (nextValue) => onChange(nextValue ? nextValue.format("YYYY-MM-DDTHH:mm:ss") : null),
790
+ onChange: (nextValue) => onChange(nextValue ? nextValue.format("YYYY-MM-DD[T]HH:mm:ss") : null),
766
791
  slotProps: {
767
792
  popper: { disablePortal: true },
768
793
  desktopPaper: buildPickerPaperProps(),
@@ -898,17 +923,19 @@ function renderChoiceEditor({
898
923
  label: field.label,
899
924
  placeholder: searchPlaceholder,
900
925
  helperText: (_a2 = field.help_text) != null ? _a2 : void 0,
901
- InputProps: {
902
- ...InputProps,
903
- endAdornment: /* @__PURE__ */ jsxs(Fragment, { children: [
904
- isLoadingChoices ? /* @__PURE__ */ jsx3(CircularProgress, { color: "inherit", size: 16 }) : null,
905
- InputProps.endAdornment
906
- ] })
907
- },
908
- inputProps: {
909
- ...inputProps,
910
- autoComplete: "new-password",
911
- name: `admin-choice-${field.name}`
926
+ slotProps: {
927
+ input: {
928
+ ...InputProps,
929
+ endAdornment: /* @__PURE__ */ jsxs(Fragment, { children: [
930
+ isLoadingChoices ? /* @__PURE__ */ jsx3(CircularProgress, { color: "inherit", size: 16 }) : null,
931
+ InputProps.endAdornment
932
+ ] })
933
+ },
934
+ htmlInput: {
935
+ ...inputProps,
936
+ autoComplete: "new-password",
937
+ name: `admin-choice-${field.name}`
938
+ }
912
939
  }
913
940
  }
914
941
  );
@@ -954,17 +981,19 @@ function renderChoiceEditor({
954
981
  label: field.label,
955
982
  placeholder: searchPlaceholder,
956
983
  helperText: (_a2 = field.help_text) != null ? _a2 : void 0,
957
- InputProps: {
958
- ...InputProps,
959
- endAdornment: /* @__PURE__ */ jsxs(Fragment, { children: [
960
- isLoadingChoices ? /* @__PURE__ */ jsx3(CircularProgress, { color: "inherit", size: 16 }) : null,
961
- InputProps.endAdornment
962
- ] })
963
- },
964
- inputProps: {
965
- ...inputProps,
966
- autoComplete: "new-password",
967
- name: `admin-choice-${field.name}`
984
+ slotProps: {
985
+ input: {
986
+ ...InputProps,
987
+ endAdornment: /* @__PURE__ */ jsxs(Fragment, { children: [
988
+ isLoadingChoices ? /* @__PURE__ */ jsx3(CircularProgress, { color: "inherit", size: 16 }) : null,
989
+ InputProps.endAdornment
990
+ ] })
991
+ },
992
+ htmlInput: {
993
+ ...inputProps,
994
+ autoComplete: "new-password",
995
+ name: `admin-choice-${field.name}`
996
+ }
968
997
  }
969
998
  }
970
999
  );
@@ -2238,14 +2267,16 @@ function ListFilterField({ client, slug, filter, value, onChange, debounceMs })
2238
2267
  ...textFieldParams,
2239
2268
  label: filter.label,
2240
2269
  placeholder: (_a2 = filter.placeholder) != null ? _a2 : t("search"),
2241
- InputProps: {
2242
- ...InputProps,
2243
- endAdornment: /* @__PURE__ */ jsxs9(Fragment2, { children: [
2244
- isLoadingChoices ? /* @__PURE__ */ jsx15(CircularProgress3, { color: "inherit", size: 16 }) : null,
2245
- InputProps.endAdornment
2246
- ] })
2247
- },
2248
- inputProps
2270
+ slotProps: {
2271
+ input: {
2272
+ ...InputProps,
2273
+ endAdornment: /* @__PURE__ */ jsxs9(Fragment2, { children: [
2274
+ isLoadingChoices ? /* @__PURE__ */ jsx15(CircularProgress3, { color: "inherit", size: 16 }) : null,
2275
+ InputProps.endAdornment
2276
+ ] })
2277
+ },
2278
+ htmlInput: inputProps
2279
+ }
2249
2280
  }
2250
2281
  );
2251
2282
  })()
@@ -2556,6 +2587,7 @@ function useModelPageController({
2556
2587
  })
2557
2588
  )) != null ? _c : null;
2558
2589
  const [selectedIds, setSelectedIds] = useState9([]);
2590
+ const [isAllMatchingSelected, setIsAllMatchingSelected] = useState9(false);
2559
2591
  const [createOpen, setCreateOpen] = useState9(false);
2560
2592
  const [data, setData] = useState9(() => initialCachedResponse);
2561
2593
  const [error, setError] = useState9(null);
@@ -2574,6 +2606,8 @@ function useModelPageController({
2574
2606
  const [isDeletePreviewLoading, setIsDeletePreviewLoading] = useState9(false);
2575
2607
  const [isDeleteSubmitting, setIsDeleteSubmitting] = useState9(false);
2576
2608
  const [pendingDeleteIds, setPendingDeleteIds] = useState9([]);
2609
+ const [pendingDeleteSelectAll, setPendingDeleteSelectAll] = useState9(false);
2610
+ const [pendingDeleteScope, setPendingDeleteScope] = useState9(null);
2577
2611
  const [pendingDeleteMode, setPendingDeleteMode] = useState9("single");
2578
2612
  const [filtersOpen, setFiltersOpen] = useState9(false);
2579
2613
  const requestIdRef = useRef3(0);
@@ -2596,15 +2630,32 @@ function useModelPageController({
2596
2630
  const listFilters = (_j = meta == null ? void 0 : meta.list_filters) != null ? _j : [];
2597
2631
  const hasListFilters = listFilters.length > 0;
2598
2632
  const bulkActions = (_k = meta == null ? void 0 : meta.bulk_actions) != null ? _k : [];
2599
- const selectedIdSet = useMemo8(() => new Set(selectedIds.map((item) => String(item))), [selectedIds]);
2600
- const allVisibleSelected = rows.length > 0 && rows.every((row) => {
2633
+ const selectionScope = useMemo8(
2634
+ () => ({
2635
+ q: appliedQuery || void 0,
2636
+ filters: { ...appliedFilters }
2637
+ }),
2638
+ [appliedFilters, appliedQuery]
2639
+ );
2640
+ const selectedIdSet = useMemo8(() => {
2641
+ if (isAllMatchingSelected) {
2642
+ return new Set(rows.map((row) => {
2643
+ var _a2;
2644
+ return String(row[(_a2 = meta == null ? void 0 : meta.pk_field) != null ? _a2 : "id"]);
2645
+ }));
2646
+ }
2647
+ return new Set(selectedIds.map((item) => String(item)));
2648
+ }, [isAllMatchingSelected, meta == null ? void 0 : meta.pk_field, rows, selectedIds]);
2649
+ const allVisibleSelected = rows.length > 0 && (isAllMatchingSelected || rows.every((row) => {
2601
2650
  var _a2;
2602
2651
  return selectedIdSet.has(String(row[(_a2 = meta == null ? void 0 : meta.pk_field) != null ? _a2 : "id"]));
2603
- });
2604
- const hasVisibleSelection = rows.some((row) => {
2652
+ }));
2653
+ const hasVisibleSelection = isAllMatchingSelected || rows.some((row) => {
2605
2654
  var _a2;
2606
2655
  return selectedIdSet.has(String(row[(_a2 = meta == null ? void 0 : meta.pk_field) != null ? _a2 : "id"]));
2607
2656
  });
2657
+ const hasSelection = isAllMatchingSelected || selectedIds.length > 0;
2658
+ const selectionCount = isAllMatchingSelected ? total : selectedIds.length;
2608
2659
  useEffect9(() => {
2609
2660
  dataRef.current = data;
2610
2661
  }, [data]);
@@ -2624,6 +2675,10 @@ function useModelPageController({
2624
2675
  setPageInput(String(nextPage));
2625
2676
  setAppliedFilters(nextFilters);
2626
2677
  }, [locationSearch]);
2678
+ const clearSelection = useCallback4(() => {
2679
+ setSelectedIds([]);
2680
+ setIsAllMatchingSelected(false);
2681
+ }, []);
2627
2682
  const loadItems = useCallback4(async () => {
2628
2683
  var _a2;
2629
2684
  const activeRequestId = requestIdRef.current + 1;
@@ -2640,7 +2695,9 @@ function useModelPageController({
2640
2695
  const cachedResponse = (_a2 = getClientCacheBucket(client).listResponseCache.get(requestKey)) != null ? _a2 : null;
2641
2696
  if (cachedResponse) {
2642
2697
  setData(cachedResponse);
2643
- setSelectedIds([]);
2698
+ if (!isAllMatchingSelected) {
2699
+ setSelectedIds([]);
2700
+ }
2644
2701
  setIsLoading(false);
2645
2702
  return;
2646
2703
  }
@@ -2655,7 +2712,9 @@ function useModelPageController({
2655
2712
  }
2656
2713
  pageSizeRef.current = response.meta.page_size;
2657
2714
  setData(response);
2658
- setSelectedIds([]);
2715
+ if (!isAllMatchingSelected) {
2716
+ setSelectedIds([]);
2717
+ }
2659
2718
  } catch (reason) {
2660
2719
  setError(reason instanceof Error ? reason.message : t("model_load_error"));
2661
2720
  } finally {
@@ -2663,7 +2722,7 @@ function useModelPageController({
2663
2722
  setIsLoading(false);
2664
2723
  }
2665
2724
  }
2666
- }, [appliedFilters, appliedQuery, client, currentPage, slug, sortValue, t]);
2725
+ }, [appliedFilters, appliedQuery, client, currentPage, isAllMatchingSelected, slug, sortValue, t]);
2667
2726
  useEffect9(() => {
2668
2727
  void loadItems();
2669
2728
  }, [loadItems]);
@@ -2678,11 +2737,12 @@ function useModelPageController({
2678
2737
  if (nextQuery === appliedQuery) {
2679
2738
  return;
2680
2739
  }
2740
+ clearSelection();
2681
2741
  setAppliedQuery(nextQuery);
2682
2742
  setCurrentPage(1);
2683
2743
  setPageInput("1");
2684
2744
  replaceLocation(nextQuery, sortValue, 1, appliedFilters);
2685
- }, [appliedFilters, appliedQuery, replaceLocation, sortValue]);
2745
+ }, [appliedFilters, appliedQuery, clearSelection, replaceLocation, sortValue]);
2686
2746
  const handlePageChange = useCallback4((nextPage) => {
2687
2747
  const safePage = Math.min(Math.max(nextPage, 1), totalPages);
2688
2748
  setCurrentPage(safePage);
@@ -2717,22 +2777,26 @@ function useModelPageController({
2717
2777
  if (!value) {
2718
2778
  delete nextFilters[filterSlug];
2719
2779
  }
2780
+ clearSelection();
2720
2781
  setAppliedFilters(nextFilters);
2721
2782
  setCurrentPage(1);
2722
2783
  setPageInput("1");
2723
2784
  replaceLocation(appliedQuery, sortValue, 1, nextFilters);
2724
- }, [appliedFilters, appliedQuery, replaceLocation, sortValue]);
2785
+ }, [appliedFilters, appliedQuery, clearSelection, replaceLocation, sortValue]);
2725
2786
  const handleResetFilters = useCallback4(() => {
2726
2787
  if (Object.keys(appliedFilters).length === 0) {
2727
2788
  return;
2728
2789
  }
2790
+ clearSelection();
2729
2791
  setAppliedFilters({});
2730
2792
  setCurrentPage(1);
2731
2793
  setPageInput("1");
2732
2794
  replaceLocation(appliedQuery, sortValue, 1, {});
2733
- }, [appliedFilters, appliedQuery, replaceLocation, sortValue]);
2795
+ }, [appliedFilters, appliedQuery, clearSelection, replaceLocation, sortValue]);
2734
2796
  const openSingleDeletePreview = useCallback4(async (rowId) => {
2735
2797
  setPendingDeleteIds([rowId]);
2798
+ setPendingDeleteSelectAll(false);
2799
+ setPendingDeleteScope(null);
2736
2800
  setPendingDeleteMode("single");
2737
2801
  setDeletePreviewOpen(true);
2738
2802
  setDeletePreview(null);
@@ -2748,17 +2812,23 @@ function useModelPageController({
2748
2812
  }
2749
2813
  }, [client, slug, t]);
2750
2814
  const openBulkDeletePreview = useCallback4(async () => {
2751
- if (selectedIds.length === 0) {
2815
+ if (!hasSelection) {
2752
2816
  return;
2753
2817
  }
2754
- setPendingDeleteIds(selectedIds);
2818
+ setPendingDeleteIds(isAllMatchingSelected ? [] : selectedIds);
2819
+ setPendingDeleteSelectAll(isAllMatchingSelected);
2820
+ setPendingDeleteScope(isAllMatchingSelected ? selectionScope : null);
2755
2821
  setPendingDeleteMode("bulk");
2756
2822
  setDeletePreviewOpen(true);
2757
2823
  setDeletePreview(null);
2758
2824
  setDeletePreviewError(null);
2759
2825
  setIsDeletePreviewLoading(true);
2760
2826
  try {
2761
- const preview = await client.getBulkDeletePreview(slug, selectedIds);
2827
+ const preview = await client.getBulkDeletePreview(
2828
+ slug,
2829
+ isAllMatchingSelected ? [] : selectedIds,
2830
+ isAllMatchingSelected ? { selectAll: true, selectionScope } : void 0
2831
+ );
2762
2832
  setDeletePreview(preview);
2763
2833
  } catch (reason) {
2764
2834
  setDeletePreviewError(reason instanceof Error ? reason.message : t("delete_preview_error"));
@@ -2766,9 +2836,12 @@ function useModelPageController({
2766
2836
  setIsDeletePreviewLoading(false);
2767
2837
  setBulkActionMenuAnchor(null);
2768
2838
  }
2769
- }, [client, selectedIds, slug, t]);
2839
+ }, [client, hasSelection, isAllMatchingSelected, selectedIds, selectionScope, slug, t]);
2770
2840
  const handleConfirmDelete = useCallback4(async () => {
2771
- if (pendingDeleteIds.length === 0) {
2841
+ if (pendingDeleteMode === "single" && pendingDeleteIds.length === 0) {
2842
+ return;
2843
+ }
2844
+ if (pendingDeleteMode === "bulk" && pendingDeleteIds.length === 0 && !pendingDeleteSelectAll) {
2772
2845
  return;
2773
2846
  }
2774
2847
  setIsDeleteSubmitting(true);
@@ -2777,21 +2850,28 @@ function useModelPageController({
2777
2850
  if (pendingDeleteMode === "single") {
2778
2851
  await client.deleteItem(slug, pendingDeleteIds[0]);
2779
2852
  } else {
2780
- await client.bulkDelete(slug, pendingDeleteIds);
2853
+ await client.bulkDelete(
2854
+ slug,
2855
+ pendingDeleteIds,
2856
+ pendingDeleteSelectAll && pendingDeleteScope ? { selectAll: true, selectionScope: pendingDeleteScope } : void 0
2857
+ );
2781
2858
  }
2782
2859
  setDeletePreviewOpen(false);
2783
2860
  setDeletePreview(null);
2784
2861
  setDeletePreviewError(null);
2785
2862
  setPendingDeleteIds([]);
2863
+ setPendingDeleteSelectAll(false);
2864
+ setPendingDeleteScope(null);
2865
+ clearSelection();
2786
2866
  await refresh();
2787
2867
  } catch (reason) {
2788
2868
  setDeletePreviewError(reason instanceof Error ? reason.message : t("object_delete_error"));
2789
2869
  } finally {
2790
2870
  setIsDeleteSubmitting(false);
2791
2871
  }
2792
- }, [client, pendingDeleteIds, pendingDeleteMode, refresh, slug, t]);
2872
+ }, [clearSelection, client, pendingDeleteIds, pendingDeleteMode, pendingDeleteScope, pendingDeleteSelectAll, refresh, slug, t]);
2793
2873
  const handleRunNamedBulkAction = useCallback4(async (actionSlug) => {
2794
- if (!actionSlug || selectedIds.length === 0) {
2874
+ if (!actionSlug || !hasSelection) {
2795
2875
  return;
2796
2876
  }
2797
2877
  if (actionSlug === "delete") {
@@ -2799,19 +2879,40 @@ function useModelPageController({
2799
2879
  return;
2800
2880
  }
2801
2881
  try {
2802
- await client.runBulkAction(slug, actionSlug, selectedIds);
2882
+ await client.runBulkAction(
2883
+ slug,
2884
+ actionSlug,
2885
+ isAllMatchingSelected ? [] : selectedIds,
2886
+ void 0,
2887
+ isAllMatchingSelected ? { selectAll: true, selectionScope } : void 0
2888
+ );
2803
2889
  setBulkActionMenuAnchor(null);
2890
+ clearSelection();
2804
2891
  await refresh();
2805
2892
  } catch (reason) {
2806
2893
  setError(reason instanceof Error ? reason.message : t("object_action_error"));
2807
2894
  }
2808
- }, [client, openBulkDeletePreview, refresh, selectedIds, slug, t]);
2895
+ }, [clearSelection, client, hasSelection, isAllMatchingSelected, openBulkDeletePreview, refresh, selectedIds, selectionScope, slug, t]);
2809
2896
  const handleRowDelete = useCallback4(async (rowId) => {
2810
2897
  setRowActionMenuAnchor(null);
2811
2898
  setRowActionMenuId(null);
2812
2899
  await openSingleDeletePreview(rowId);
2813
2900
  }, [openSingleDeletePreview]);
2814
2901
  const handleToggleSelection = useCallback4((rowId, checked) => {
2902
+ if (isAllMatchingSelected) {
2903
+ if (checked) {
2904
+ return;
2905
+ }
2906
+ setIsAllMatchingSelected(false);
2907
+ if (!meta) {
2908
+ setSelectedIds([]);
2909
+ return;
2910
+ }
2911
+ setSelectedIds(
2912
+ rows.map((row) => row[meta.pk_field]).filter((item) => String(item) !== String(rowId))
2913
+ );
2914
+ return;
2915
+ }
2815
2916
  setSelectedIds((current) => {
2816
2917
  const rowKey = String(rowId);
2817
2918
  if (checked) {
@@ -2822,27 +2923,54 @@ function useModelPageController({
2822
2923
  }
2823
2924
  return current.filter((item) => String(item) !== rowKey);
2824
2925
  });
2825
- }, []);
2926
+ }, [isAllMatchingSelected, meta, rows]);
2826
2927
  const handleToggleAllVisible = useCallback4((checked) => {
2827
2928
  if (!meta) {
2828
2929
  return;
2829
2930
  }
2830
2931
  if (checked) {
2932
+ setIsAllMatchingSelected(false);
2831
2933
  setSelectedIds(rows.map((row) => row[meta.pk_field]));
2832
2934
  return;
2833
2935
  }
2936
+ clearSelection();
2937
+ }, [clearSelection, meta, rows]);
2938
+ const handleSelectAllMatching = useCallback4(() => {
2939
+ if (!hasSelection || total === 0) {
2940
+ return;
2941
+ }
2834
2942
  setSelectedIds([]);
2835
- }, [meta, rows]);
2943
+ setIsAllMatchingSelected(true);
2944
+ }, [hasSelection, total]);
2945
+ const clearBulkDeleteState = useCallback4(() => {
2946
+ setPendingDeleteIds([]);
2947
+ setPendingDeleteSelectAll(false);
2948
+ setPendingDeleteScope(null);
2949
+ }, []);
2950
+ const handleClearDeletePreview = useCallback4(() => {
2951
+ setDeletePreviewOpen(false);
2952
+ setDeletePreview(null);
2953
+ setDeletePreviewError(null);
2954
+ clearBulkDeleteState();
2955
+ }, [clearBulkDeleteState]);
2836
2956
  const handleOpenRowMenu = useCallback4((event, rowId) => {
2837
2957
  setRowActionMenuAnchor(event.currentTarget);
2838
2958
  setRowActionMenuId(rowId);
2839
2959
  }, []);
2960
+ const handleCloseRowMenu = useCallback4(() => {
2961
+ setRowActionMenuAnchor(null);
2962
+ setRowActionMenuId(null);
2963
+ }, []);
2964
+ const handleCloseBulkActionMenu = useCallback4(() => {
2965
+ setBulkActionMenuAnchor(null);
2966
+ }, []);
2840
2967
  return {
2841
2968
  allVisibleSelected,
2842
2969
  appliedFilters,
2843
2970
  appliedQuery,
2844
2971
  bulkActionMenuAnchor,
2845
2972
  bulkActions,
2973
+ clearBulkDeleteState,
2846
2974
  createOpen,
2847
2975
  currentPage,
2848
2976
  data,
@@ -2852,6 +2980,9 @@ function useModelPageController({
2852
2980
  error,
2853
2981
  fieldMap,
2854
2982
  filtersOpen,
2983
+ handleClearDeletePreview,
2984
+ handleCloseBulkActionMenu,
2985
+ handleCloseRowMenu,
2855
2986
  handleConfirmDelete,
2856
2987
  handleFilterChange,
2857
2988
  handleOpenRowMenu,
@@ -2861,10 +2992,13 @@ function useModelPageController({
2861
2992
  handleRowDelete,
2862
2993
  handleRunNamedBulkAction,
2863
2994
  handleSearchCommit,
2995
+ handleSelectAllMatching,
2864
2996
  handleToggleAllVisible,
2865
2997
  handleToggleSelection,
2866
2998
  hasListFilters,
2999
+ hasSelection,
2867
3000
  hasVisibleSelection,
3001
+ isAllMatchingSelected,
2868
3002
  isDeletePreviewLoading,
2869
3003
  isDeleteSubmitting,
2870
3004
  isLoading,
@@ -2878,6 +3012,7 @@ function useModelPageController({
2878
3012
  rowActionMenuAnchor,
2879
3013
  rowActionMenuId,
2880
3014
  rows,
3015
+ selectionCount,
2881
3016
  selectedIdSet,
2882
3017
  selectedIds,
2883
3018
  setBulkActionMenuAnchor,
@@ -2887,9 +3022,7 @@ function useModelPageController({
2887
3022
  setDeletePreviewOpen,
2888
3023
  setFiltersOpen,
2889
3024
  setPageInput,
2890
- setPendingDeleteIds,
2891
- setRowActionMenuAnchor,
2892
- setRowActionMenuId,
3025
+ sortValue,
2893
3026
  sortFields,
2894
3027
  toggleSort,
2895
3028
  total,
@@ -2965,10 +3098,11 @@ import { jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
2965
3098
  var SEARCH_DEBOUNCE_MS = 300;
2966
3099
  var CHECKBOX_COLUMN_WIDTH = 56;
2967
3100
  var ACTIONS_COLUMN_WIDTH = 56;
2968
- function ModelPage({ client, basePath, slug, router }) {
3101
+ function ModelPage({ client, basePath, slug, router, renderBeforePagination }) {
2969
3102
  var _a, _b;
2970
3103
  const locale = useAdminLocale();
2971
3104
  const t = useAdminTranslation();
3105
+ const selectAllLabel = locale === "ru" ? "\u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0432\u0441\u0435" : "Select All";
2972
3106
  const resolvedRouter = useXLAdminRouter(router);
2973
3107
  const location = useXLAdminLocation(resolvedRouter);
2974
3108
  const theme = useTheme2();
@@ -2989,6 +3123,22 @@ function ModelPage({ client, basePath, slug, router }) {
2989
3123
  return /* @__PURE__ */ jsx19(ModelPageSkeleton, {});
2990
3124
  }
2991
3125
  const meta = controller.meta;
3126
+ const beforePagination = renderBeforePagination == null ? void 0 : renderBeforePagination({
3127
+ client,
3128
+ slug,
3129
+ meta: {
3130
+ slug: meta.slug,
3131
+ title: meta.title
3132
+ },
3133
+ selectedIds: controller.selectedIds,
3134
+ isAllMatchingSelected: controller.isAllMatchingSelected,
3135
+ selectionCount: controller.selectionCount,
3136
+ total: controller.total,
3137
+ appliedQuery: controller.appliedQuery,
3138
+ sortValue: controller.sortValue,
3139
+ appliedFilters: controller.appliedFilters,
3140
+ refresh: controller.refresh
3141
+ });
2992
3142
  return /* @__PURE__ */ jsx19(XLAdminRouterProvider, { router: resolvedRouter, children: /* @__PURE__ */ jsxs12(Stack9, { spacing: 1.5, sx: { height: "100%", minHeight: 0 }, children: [
2993
3143
  /* @__PURE__ */ jsx19(
2994
3144
  MainHeader,
@@ -3000,8 +3150,8 @@ function ModelPage({ client, basePath, slug, router }) {
3000
3150
  "aria-label": t("create"),
3001
3151
  onClick: () => controller.setCreateOpen(true),
3002
3152
  sx: {
3003
- width: 30,
3004
- height: 30,
3153
+ width: 29,
3154
+ height: 29,
3005
3155
  p: 0.5,
3006
3156
  backgroundColor: "primary.main",
3007
3157
  color: "primary.contrastText",
@@ -3025,28 +3175,53 @@ function ModelPage({ client, basePath, slug, router }) {
3025
3175
  flexShrink: 0
3026
3176
  },
3027
3177
  children: /* @__PURE__ */ jsxs12(Stack9, { direction: { xs: "column", lg: "row" }, spacing: 1.5, alignItems: { lg: "center" }, children: [
3028
- /* @__PURE__ */ jsx19(Stack9, { direction: { xs: "column", xl: "row" }, spacing: 1.5, sx: { flex: 1, minWidth: 0 }, children: /* @__PURE__ */ jsx19(
3029
- SearchField,
3178
+ /* @__PURE__ */ jsxs12(
3179
+ Stack9,
3030
3180
  {
3031
- value: controller.appliedQuery,
3032
- onCommit: controller.handleSearchCommit,
3033
- debounceMs: SEARCH_DEBOUNCE_MS,
3034
- placeholder: t("search")
3181
+ direction: { xs: "column", xl: "row" },
3182
+ spacing: 1.5,
3183
+ alignItems: { xl: "center" },
3184
+ sx: { flex: 1, minWidth: 0 },
3185
+ children: [
3186
+ /* @__PURE__ */ jsx19(
3187
+ SearchField,
3188
+ {
3189
+ value: controller.appliedQuery,
3190
+ onCommit: controller.handleSearchCommit,
3191
+ debounceMs: SEARCH_DEBOUNCE_MS,
3192
+ placeholder: t("search")
3193
+ }
3194
+ ),
3195
+ controller.hasSelection ? /* @__PURE__ */ jsxs12(Stack9, { direction: "row", spacing: 1, alignItems: "center", sx: { minWidth: 0, flexWrap: "wrap" }, children: [
3196
+ /* @__PURE__ */ jsx19(
3197
+ Button4,
3198
+ {
3199
+ variant: "outlined",
3200
+ endIcon: /* @__PURE__ */ jsx19(ExpandMoreIcon4, {}),
3201
+ onClick: (event) => controller.setBulkActionMenuAnchor(event.currentTarget),
3202
+ children: t("actions")
3203
+ }
3204
+ ),
3205
+ !controller.isAllMatchingSelected && controller.selectionCount < controller.total ? /* @__PURE__ */ jsx19(
3206
+ Button4,
3207
+ {
3208
+ variant: "text",
3209
+ onClick: controller.handleSelectAllMatching,
3210
+ sx: { px: 0.5, minWidth: "auto" },
3211
+ children: selectAllLabel
3212
+ }
3213
+ ) : null,
3214
+ /* @__PURE__ */ jsxs12(Typography5, { color: "text.secondary", sx: { alignSelf: "center", whiteSpace: "nowrap" }, children: [
3215
+ t("selected_count", { count: controller.selectionCount }),
3216
+ " / ",
3217
+ controller.total
3218
+ ] })
3219
+ ] }) : null
3220
+ ]
3035
3221
  }
3036
- ) }),
3037
- controller.selectedIds.length > 0 ? /* @__PURE__ */ jsxs12(Stack9, { direction: "row", spacing: 1, sx: { flex: { lg: 1 }, minWidth: 0 }, children: [
3038
- /* @__PURE__ */ jsx19(
3039
- Button4,
3040
- {
3041
- variant: "outlined",
3042
- endIcon: /* @__PURE__ */ jsx19(ExpandMoreIcon4, {}),
3043
- onClick: (event) => controller.setBulkActionMenuAnchor(event.currentTarget),
3044
- children: t("actions")
3045
- }
3046
- ),
3047
- /* @__PURE__ */ jsx19(Typography5, { color: "text.secondary", sx: { alignSelf: "center" }, children: t("selected_count", { count: controller.selectedIds.length }) })
3048
- ] }) : null,
3222
+ ),
3049
3223
  /* @__PURE__ */ jsxs12(Stack9, { direction: "row", spacing: 1, alignItems: "center", sx: { marginLeft: "auto" }, children: [
3224
+ beforePagination ? /* @__PURE__ */ jsx19(Box9, { sx: { display: "flex", alignItems: "center", flexShrink: 0 }, children: beforePagination }) : null,
3050
3225
  isMobile && controller.hasListFilters ? /* @__PURE__ */ jsx19(Tooltip, { title: t("filters"), children: /* @__PURE__ */ jsx19(
3051
3226
  IconButton3,
3052
3227
  {
@@ -3251,7 +3426,7 @@ function ModelPage({ client, basePath, slug, router }) {
3251
3426
  {
3252
3427
  anchorEl: controller.bulkActionMenuAnchor,
3253
3428
  open: Boolean(controller.bulkActionMenuAnchor),
3254
- onClose: () => controller.setBulkActionMenuAnchor(null),
3429
+ onClose: controller.handleCloseBulkActionMenu,
3255
3430
  children: controller.bulkActions.map((action) => /* @__PURE__ */ jsx19(
3256
3431
  MenuItem2,
3257
3432
  {
@@ -3267,10 +3442,7 @@ function ModelPage({ client, basePath, slug, router }) {
3267
3442
  {
3268
3443
  anchorEl: controller.rowActionMenuAnchor,
3269
3444
  open: Boolean(controller.rowActionMenuAnchor),
3270
- onClose: () => {
3271
- controller.setRowActionMenuAnchor(null);
3272
- controller.setRowActionMenuId(null);
3273
- },
3445
+ onClose: controller.handleCloseRowMenu,
3274
3446
  children: /* @__PURE__ */ jsx19(
3275
3447
  MenuItem2,
3276
3448
  {
@@ -3347,10 +3519,7 @@ function ModelPage({ client, basePath, slug, router }) {
3347
3519
  if (controller.isDeleteSubmitting) {
3348
3520
  return;
3349
3521
  }
3350
- controller.setDeletePreviewOpen(false);
3351
- controller.setDeletePreview(null);
3352
- controller.setDeletePreviewError(null);
3353
- controller.setPendingDeleteIds([]);
3522
+ controller.handleClearDeletePreview();
3354
3523
  },
3355
3524
  onConfirm: () => void controller.handleConfirmDelete()
3356
3525
  }
@@ -1,4 +1,4 @@
1
- import type { AdminFieldMeta, AdminLocale } from '../types';
1
+ import type { AdminFieldMeta, AdminLocale } from '@xladmin-core/types';
2
2
  /**
3
3
  * Готовит payload для create/patch так, чтобы nullable-поля можно было очищать,
4
4
  * а служебные пустые поля вроде пароля не улетали в API без необходимости.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xladmin",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "description": "Framework-agnostic React + MUI admin frontend for xladmin.",
5
5
  "license": "MIT",
6
6
  "type": "module",