@rebasepro/admin 0.13.1-canary.gf57a27e → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/{CollectionEditorDialog-BnfOcqM_.js → CollectionEditorDialog-CSheud_E.js} +19 -9
  2. package/dist/CollectionEditorDialog-CSheud_E.js.map +1 -0
  3. package/dist/{PropertyEditView-vLXglzIy.js → PropertyEditView-CWZlC1gq.js} +5 -5
  4. package/dist/PropertyEditView-CWZlC1gq.js.map +1 -0
  5. package/dist/{RouterCollectionsStudioView-DF3aiOuv.js → RouterCollectionsStudioView-CwTpdzWF.js} +4 -4
  6. package/dist/{RouterCollectionsStudioView-DF3aiOuv.js.map → RouterCollectionsStudioView-CwTpdzWF.js.map} +1 -1
  7. package/dist/collection_editor_ui.js +4 -4
  8. package/dist/components/ClearFilterSortButton.d.ts +7 -1
  9. package/dist/components/CollectionViewBinding/SortButton.d.ts +7 -1
  10. package/dist/components/RelationSelector.d.ts +13 -0
  11. package/dist/data_export/export/export.d.ts +7 -0
  12. package/dist/data_export/export/fetch_export_data.d.ts +48 -0
  13. package/dist/data_export/export/index.d.ts +1 -0
  14. package/dist/data_import/utils/csv.d.ts +30 -0
  15. package/dist/data_import/utils/index.d.ts +2 -0
  16. package/dist/data_import/utils/save_entities.d.ts +38 -0
  17. package/dist/data_import/utils/transforms.d.ts +9 -0
  18. package/dist/editor.js +3 -0
  19. package/dist/editor.js.map +1 -1
  20. package/dist/{export-C26xQN-g.js → export-L53WektO.js} +175 -39
  21. package/dist/export-L53WektO.js.map +1 -0
  22. package/dist/{history-DSS-Ebwh.js → history-D5SKygpJ.js} +2 -2
  23. package/dist/{history-DSS-Ebwh.js.map → history-D5SKygpJ.js.map} +1 -1
  24. package/dist/{import-Dhx96B6x.js → import-C_4edkoD.js} +5 -4
  25. package/dist/import-C_4edkoD.js.map +1 -0
  26. package/dist/index.js +93 -74
  27. package/dist/index.js.map +1 -1
  28. package/dist/preview/components/date_fns_locales.d.ts +9 -0
  29. package/dist/{util-Bt6VadeZ.js → util-C-D5yD2n.js} +567 -113
  30. package/dist/util-C-D5yD2n.js.map +1 -0
  31. package/package.json +10 -9
  32. package/src/collection_editor/ui/collection_editor/AICollectionGeneratorPopover.tsx +8 -3
  33. package/src/collection_editor/ui/collection_editor/CollectionDetailsForm.tsx +5 -2
  34. package/src/collection_editor/ui/collection_editor/CollectionJsonImportDialog.tsx +1 -0
  35. package/src/collection_editor/ui/collection_editor/CollectionRLSTab.tsx +9 -8
  36. package/src/collection_editor/ui/collection_editor/GeneralSettingsForm.tsx +5 -2
  37. package/src/collection_editor/ui/collection_editor/properties/MapPropertyField.tsx +1 -1
  38. package/src/collection_editor/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +5 -2
  39. package/src/collection_editor/ui/collection_editor/properties/conditions/ConditionsEditor.tsx +1 -0
  40. package/src/collection_editor/useJsonCollectionsConfigController.tsx +92 -84
  41. package/src/collection_editor/useLocalCollectionsConfigController.tsx +10 -3
  42. package/src/components/ClearFilterSortButton.tsx +16 -7
  43. package/src/components/CollectionTableBinding/table_bindings.tsx +7 -1
  44. package/src/components/CollectionViewBinding/CollectionBoardViewBinding.tsx +13 -7
  45. package/src/components/CollectionViewBinding/CollectionViewBinding.tsx +1 -0
  46. package/src/components/CollectionViewBinding/CollectionViewStartActions.tsx +9 -0
  47. package/src/components/CollectionViewBinding/SortButton.tsx +16 -4
  48. package/src/components/CollectionViewBinding/ViewModeToggle.tsx +10 -5
  49. package/src/components/CollectionViewBinding/useBoardDataController.tsx +11 -3
  50. package/src/components/DefaultDrawer.tsx +7 -0
  51. package/src/components/DrawerNavigationItem.tsx +12 -0
  52. package/src/components/RelationSelector.tsx +52 -5
  53. package/src/components/SearchIconsView.tsx +14 -33
  54. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +1 -0
  55. package/src/components/UserSelector.tsx +47 -5
  56. package/src/data_export/export/ExportCollectionAction.tsx +101 -48
  57. package/src/data_export/export/export.ts +63 -16
  58. package/src/data_export/export/fetch_export_data.ts +90 -0
  59. package/src/data_export/export/index.ts +1 -0
  60. package/src/data_import/components/DataNewPropertiesMapping.tsx +20 -3
  61. package/src/data_import/components/ImportNewPropertyFieldPreview.tsx +1 -0
  62. package/src/data_import/components/ImportSaveInProgress.tsx +35 -36
  63. package/src/data_import/import/ImportCollectionAction.tsx +13 -2
  64. package/src/data_import/utils/csv.ts +153 -0
  65. package/src/data_import/utils/data.ts +7 -1
  66. package/src/data_import/utils/file_to_json.ts +77 -6
  67. package/src/data_import/utils/index.ts +2 -0
  68. package/src/data_import/utils/save_entities.ts +138 -0
  69. package/src/data_import/utils/transforms.ts +23 -0
  70. package/src/editor/components/image-bubble.tsx +2 -0
  71. package/src/editor/selectors/link-selector.tsx +1 -0
  72. package/src/form/field_bindings/BinaryFieldBinding.tsx +1 -0
  73. package/src/form/field_bindings/DateTimeFieldBinding.tsx +4 -0
  74. package/src/form/field_bindings/KeyValueFieldBinding.tsx +3 -0
  75. package/src/form/field_bindings/TextFieldBinding.tsx +18 -0
  76. package/src/form/field_bindings/VectorFieldBinding.tsx +1 -0
  77. package/src/preview/components/DatePreview.tsx +15 -2
  78. package/src/preview/components/ReferencePreview.tsx +5 -8
  79. package/src/preview/components/RelationPreview.tsx +8 -7
  80. package/src/preview/components/date_fns_locales.ts +125 -0
  81. package/dist/CollectionEditorDialog-BnfOcqM_.js.map +0 -1
  82. package/dist/PropertyEditView-vLXglzIy.js.map +0 -1
  83. package/dist/export-C26xQN-g.js.map +0 -1
  84. package/dist/import-Dhx96B6x.js.map +0 -1
  85. package/dist/util-Bt6VadeZ.js.map +0 -1
@@ -1,18 +1,17 @@
1
1
  import * as React$1 from "react";
2
2
  import React, { Suspense, createContext, createElement, forwardRef, lazy, memo, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
3
3
  import { deepEqual } from "fast-equals";
4
- import { Alert, AlertCircleIcon, AlertTriangleIcon, AlignLeftIcon, ArrowDownIcon, ArrowLeftIcon, ArrowRightToLineIcon, ArrowUpDownIcon, ArrowUpIcon, Badge, BooleanSwitchWithLabel, Button, CHIP_COLORS, CHIP_SEED_KEYS, CalendarIcon, Card, CardView, CenteredView, CheckCircleIcon, CheckIcon, Checkbox, ChevronDownIcon, ChevronRightIcon, ChevronUpIcon, ChevronsUpDownIcon, Chip, CircleIcon, CircularProgress, CircularProgressCenter, CodeIcon, Collapse, ColumnsIcon, CopyIcon, DateTimeField, Dialog, DialogActions, DialogContent, DialogTitle, ErrorBoundary, ExpandablePanel, ExternalLinkIcon, EyeIcon, EyeOffIcon, FileTextIcon, FileUpload, FilterChip, FilterIcon, FilterXIcon, FlagIcon, FolderUpIcon, FunctionSquareIcon, GlobeIcon, HandleIcon, HashIcon, HistoryIcon, IconButton, ImageOffIcon, KanbanIcon, KanbanView, KeyRoundIcon, Label, LayoutGridIcon, Link2Icon, LinkIcon, ListIcon, ListOrderedIcon, ListView, LoadingButton, MailIcon, Markdown, Maximize2Icon, Menu, MenuItem, MinusIcon, MoreVerticalIcon, MultiSelect, MultiSelectItem, PanelLeftCloseIcon, PanelLeftOpenIcon, PencilIcon, PlusIcon, Popover, PopoverPrimitive, Portal, QuoteIcon, RadioGroup, RadioGroupItem, RefreshCwIcon, RepeatIcon, ResizablePanels, Rows3Icon, SearchBar, SearchIcon, Select, SelectItem, Separator, SettingsIcon, Sheet, Skeleton, Tab, Table, TableBody, TableCell, TableHeader, TableIcon, TableRow, Tabs, TextField, TextIcon, TextareaAutosize, ToggleButtonGroup, Tooltip, Trash2Icon, Typography, Unlink2Icon, UploadIcon, UserIcon, VirtualTable, VirtualTableDateField, VirtualTableInput, VirtualTableInput as VirtualTableInput$1, VirtualTableNumberInput, VirtualTableSwitch, VoteIcon, XCircleIcon, XIcon, cls, coolIconKeys, createVirtualTableSelectionStore, debounce, defaultBorderMixin, fieldBackgroundDisabledMixin, fieldBackgroundHoverMixin, fieldBackgroundInvisibleMixin, fieldBackgroundMixin, focusedDisabled, getColorSchemeForSeed, iconSize, lucideIcons, paperMixin, useDebouncedCallback, useOutsideAlerter, usePortalContainer } from "@rebasepro/ui";
4
+ import { Alert, AlertCircleIcon, AlertTriangleIcon, AlignLeftIcon, ArrowDownIcon, ArrowLeftIcon, ArrowRightToLineIcon, ArrowUpDownIcon, ArrowUpIcon, Badge, BooleanSwitchWithLabel, Button, CHIP_COLORS, CHIP_SEED_KEYS, CalendarIcon, Card, CardView, CenteredView, CheckCircleIcon, CheckIcon, Checkbox, ChevronDownIcon, ChevronRightIcon, ChevronUpIcon, ChevronsUpDownIcon, Chip, CircleIcon, CircularProgress, CircularProgressCenter, CodeIcon, Collapse, ColumnsIcon, CopyIcon, DateTimeField, Dialog, DialogActions, DialogContent, DialogTitle, ErrorBoundary, ExpandablePanel, ExternalLinkIcon, EyeIcon, EyeOffIcon, FileTextIcon, FileUpload, FilterChip, FilterIcon, FilterXIcon, FlagIcon, FolderUpIcon, FunctionSquareIcon, GlobeIcon, HandleIcon, HashIcon, HistoryIcon, IconButton, ImageOffIcon, KanbanIcon, KanbanView, KeyRoundIcon, Label, LayoutGridIcon, Link2Icon, LinkIcon, ListIcon, ListOrderedIcon, ListView, LoadingButton, LucideIconByName, MailIcon, Markdown, Maximize2Icon, Menu, MenuItem, MinusIcon, MoreVerticalIcon, MultiSelect, MultiSelectItem, PanelLeftCloseIcon, PanelLeftOpenIcon, PencilIcon, PlusIcon, Popover, PopoverPrimitive, Portal, QuoteIcon, RadioGroup, RadioGroupItem, RefreshCwIcon, RepeatIcon, ResizablePanels, Rows3Icon, SearchBar, SearchIcon, Select, SelectItem, Separator, SettingsIcon, Sheet, Skeleton, Tab, Table, TableBody, TableCell, TableHeader, TableIcon, TableRow, Tabs, TextField, TextIcon, TextareaAutosize, ToggleButtonGroup, Tooltip, Trash2Icon, Typography, Unlink2Icon, UploadIcon, UserIcon, VirtualTable, VirtualTableDateField, VirtualTableInput, VirtualTableInput as VirtualTableInput$1, VirtualTableNumberInput, VirtualTableSwitch, VoteIcon, XCircleIcon, XIcon, cls, coolIconKeys, createVirtualTableSelectionStore, debounce, defaultBorderMixin, fieldBackgroundDisabledMixin, fieldBackgroundHoverMixin, fieldBackgroundInvisibleMixin, fieldBackgroundMixin, focusedDisabled, getColorSchemeForSeed, iconSize, paperMixin, useDebouncedCallback, useOutsideAlerter, usePortalContainer } from "@rebasepro/ui";
5
5
  import { CollectionRegistry, DEFAULT_ONE_OF_TYPE, DEFAULT_ONE_OF_VALUE, canReadCollection, enumToObjectEntries, getArrayResolvedProperties, getDefaultValueFor, getDefaultValueFortype, getDefaultValuesFor, getEntityChildViews, getLabelOrConfigFrom, getReferenceFrom, getRelationFrom, getRelationTargetPath, getSubcollections, getTableName, isPropertyBuilder, normalizeToEntityRelation, resolveArrayProperties, resolveEnumValues, resolveProperty, resolveRelationProperty, resolveStorageFilenameString, resolveStoragePathString, resolveStorageSource } from "@rebasepro/common";
6
6
  import { CONTAINER_FULL_WIDTH, CollectionScopeProvider, DEFAULT_PAGE_SIZE, EntityDisplayCache, ErrorTooltip, ErrorView, IconForView, PluginProviderStack, SIDE_PANEL_DEFAULT_WIDTH, StorageSourceContext, UnsavedChangesDialog, UserDisplay, addInitialSlash, apiBaseOf, buildEnumLabel, createFormexStub, deleteEntityWithCallbacks, entityDisplayKey, flattenKeys, getAdminEntityChildViews, getCollectionBySlugWithin, getCollectionPathsCombinations, getColorScheme, getColumnKeysForProperty, getDisplayPropertyKey, getDisplayResolver, getEntityFromCache, getEntityFromMemoryCache, getEntityImagePreviewPropertyKey, getEntityPreviewKeys, getFormFieldKeys, getIcon, getLastSegment as getLastSegment$1, getLeadingRelationTitleKey, getLocalChangesBackup, getNavigationEntriesFromPath, getParentReferencesFromPath, getPropertyInPath, getRedundantChildViewColumnIds, getRelationIncludeParams, getRowHeight, getTitlePropertyCandidates, getTitlePropertyKey, getTitlePropertyKeyForValues, hasDeclaredDisplay, iconsSearch, isDisabled, isFilterableRelation, isHidden, isReadOnly, looksLikeIdentifierValue, removeEntityFromCache, removeEntityFromMemoryCache, removeInitialAndTrailingSlashes, removeInitialAndTrailingSlashes as removeInitialAndTrailingSlashes$1, removeInitialSlash, removeTrailingSlash, removeTrailingSlash as removeTrailingSlash$1, resolveCollectionPathIds, resolveCollectionPathIds as resolveCollectionPathIds$1, resolveComponentRef, resolveDefaultSelectedView, resolveFilterOperators, resolveFormLayout, saveEntityToCache, saveEntityToMemoryCache, saveEntityWithCallbacks, useAnalyticsController, useApiBase, useApiConfig, useAuthController, useCollectionScope, useColumnIds, useComponentOverride, useCustomizationController, useData, useDataTableController, useFetch, useLargeLayout, useModeController, useNavigationBlocker, usePermissions, useRebaseClient, useRebaseContext, useRelationSelector, useResolvedComponent, useScrollRestoration, useSlot, useSnackbarController, useStorageSource, useStorageSources, useStorageUploadController, useTranslation, useUnsavedChangesDialog, useUserConfigurationPersistence } from "@rebasepro/app";
7
7
  import { Field, Formex, getIn, setIn, useCreateFormex, useFormex } from "@rebasepro/forms";
8
8
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
- import { defaultDateFormat, formatRelativeTime, getHashValue, getValueInPath, hydrateRegExp, isArrayValue, isObject, mergeDeep, randomString, readStoredJson, toArray, writeStoredJson } from "@rebasepro/utils";
9
+ import { defaultDateFormat, formatRelativeTime, getHashValue, getValueInPath, hydrateRegExp, isArrayValue, isObject, isPrototypePollutingKey, mergeDeep, pathTraversesPrototype, randomString, readStoredJson, toArray, writeStoredJson } from "@rebasepro/utils";
10
10
  import { resolveAdminCollection } from "@rebasepro/admin-types";
11
11
  import { format } from "date-fns";
12
- import * as locales from "date-fns/locale";
13
12
  import { Command } from "cmdk";
14
13
  import { useDropzone } from "react-dropzone";
15
- import { EntityReference, EntityRelation, GeoPoint, Vector, getCollectionDataPath, getDataSourceCapabilities, isRelationalCollectionConfig } from "@rebasepro/types";
14
+ import { EntityReference, EntityRelation, GeoPoint, MAX_LIST_LIMIT, Vector, getCollectionDataPath, getDataSourceCapabilities, isRelationalCollectionConfig } from "@rebasepro/types";
16
15
  import { z } from "zod";
17
16
  import useMeasure from "react-use-measure";
18
17
  import { Link, useLocation, useNavigate, useSearchParams } from "react-router";
@@ -20,7 +19,6 @@ import { DndContext, KeyboardSensor, PointerSensor, closestCenter, useSensor, us
20
19
  import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
21
20
  import { SortableContext, horizontalListSortingStrategy, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
22
21
  import { CSS } from "@dnd-kit/utilities";
23
- import ExcelJS from "exceljs";
24
22
  import { inferTypeFromValue } from "@rebasepro/inference";
25
23
  import { Highlight, themes } from "prism-react-renderer";
26
24
  import { generateKeyBetween, generateNKeysBetween } from "fractional-indexing";
@@ -2244,8 +2242,7 @@ function ReferencePreviewExisting({ reference, collection, previewProperties, si
2244
2242
  collection,
2245
2243
  useCache: true
2246
2244
  });
2247
- if (entity) referencesCache.set(reference.pathWithId, entity);
2248
- const usedEntity = entity ?? referencesCache.get(reference.pathWithId);
2245
+ const usedEntity = entity;
2249
2246
  let body;
2250
2247
  if (!reference) body = /* @__PURE__ */ jsx(ErrorView, { error: "Reference not set" });
2251
2248
  else if (usedEntity && !usedEntity.values) body = /* @__PURE__ */ jsx(ErrorView, {
@@ -2304,7 +2301,6 @@ function ReferencePreviewExisting({ reference, collection, previewProperties, si
2304
2301
  hover
2305
2302
  });
2306
2303
  }
2307
- var referencesCache = /* @__PURE__ */ new Map();
2308
2304
  //#endregion
2309
2305
  //#region src/preview/components/InlineEntityListPreview.tsx
2310
2306
  /**
@@ -2487,8 +2483,7 @@ function RelationPreviewExisting({ relation, collection, previewProperties, size
2487
2483
  collection,
2488
2484
  useCache: true
2489
2485
  });
2490
- if (entity) relationsCache.set(relation.pathWithId, entity);
2491
- const usedEntity = passedEntity ?? entity ?? relationsCache.get(relation.pathWithId);
2486
+ const usedEntity = passedEntity ?? entity;
2492
2487
  let body;
2493
2488
  if (!relation) body = /* @__PURE__ */ jsx(ErrorView, { error: "Relation not set" });
2494
2489
  else if (usedEntity && !usedEntity.values) body = /* @__PURE__ */ jsx(ErrorView, {
@@ -2547,7 +2542,6 @@ function RelationPreviewExisting({ relation, collection, previewProperties, size
2547
2542
  hover
2548
2543
  });
2549
2544
  }
2550
- var relationsCache = /* @__PURE__ */ new Map();
2551
2545
  //#endregion
2552
2546
  //#region src/preview/property_previews/ArrayOfRelationsPreview.tsx
2553
2547
  /**
@@ -2851,13 +2845,144 @@ function KeyValuePreview({ value }) {
2851
2845
  });
2852
2846
  }
2853
2847
  //#endregion
2848
+ //#region src/preview/components/date_fns_locales.ts
2849
+ /**
2850
+ * One date-fns locale, fetched when the admin is actually configured to use it.
2851
+ *
2852
+ * `DatePreview` used to open with:
2853
+ *
2854
+ * import * as locales from "date-fns/locale";
2855
+ *
2856
+ * and then index that namespace with the configured locale. A namespace import
2857
+ * of a barrel is a reference to every member of it, so all ~77 locales were
2858
+ * bundled — 640 kB, in the entry chunk's static graph, to format dates in the
2859
+ * one locale a given deployment has configured (and `undefined`, meaning
2860
+ * en-US, for nearly all of them).
2861
+ *
2862
+ * A map of thunks is written out rather than derived from the locale key,
2863
+ * because `import(`date-fns/locale/${name}`)` is not statically analysable:
2864
+ * the bundler cannot see which modules it must emit, and the import fails at
2865
+ * runtime in a build. Each entry below is its own async chunk, and exactly one
2866
+ * of them is ever fetched.
2867
+ *
2868
+ * `fil` is in the `Locale` union and has no date-fns locale to import; it
2869
+ * resolved to `undefined` through the namespace too, so it still does.
2870
+ */
2871
+ var dateFnsLocaleLoaders = {
2872
+ af: () => import("date-fns/locale/af").then((m) => m.af),
2873
+ ar: () => import("date-fns/locale/ar").then((m) => m.ar),
2874
+ arDZ: () => import("date-fns/locale/ar-DZ").then((m) => m.arDZ),
2875
+ arMA: () => import("date-fns/locale/ar-MA").then((m) => m.arMA),
2876
+ arSA: () => import("date-fns/locale/ar-SA").then((m) => m.arSA),
2877
+ az: () => import("date-fns/locale/az").then((m) => m.az),
2878
+ be: () => import("date-fns/locale/be").then((m) => m.be),
2879
+ bg: () => import("date-fns/locale/bg").then((m) => m.bg),
2880
+ bn: () => import("date-fns/locale/bn").then((m) => m.bn),
2881
+ ca: () => import("date-fns/locale/ca").then((m) => m.ca),
2882
+ cs: () => import("date-fns/locale/cs").then((m) => m.cs),
2883
+ cy: () => import("date-fns/locale/cy").then((m) => m.cy),
2884
+ da: () => import("date-fns/locale/da").then((m) => m.da),
2885
+ de: () => import("date-fns/locale/de").then((m) => m.de),
2886
+ el: () => import("date-fns/locale/el").then((m) => m.el),
2887
+ enAU: () => import("date-fns/locale/en-AU").then((m) => m.enAU),
2888
+ enCA: () => import("date-fns/locale/en-CA").then((m) => m.enCA),
2889
+ enGB: () => import("date-fns/locale/en-GB").then((m) => m.enGB),
2890
+ enIN: () => import("date-fns/locale/en-IN").then((m) => m.enIN),
2891
+ enNZ: () => import("date-fns/locale/en-NZ").then((m) => m.enNZ),
2892
+ enUS: () => import("date-fns/locale/en-US").then((m) => m.enUS),
2893
+ eo: () => import("date-fns/locale/eo").then((m) => m.eo),
2894
+ es: () => import("date-fns/locale/es").then((m) => m.es),
2895
+ et: () => import("date-fns/locale/et").then((m) => m.et),
2896
+ eu: () => import("date-fns/locale/eu").then((m) => m.eu),
2897
+ faIR: () => import("date-fns/locale/fa-IR").then((m) => m.faIR),
2898
+ fi: () => import("date-fns/locale/fi").then((m) => m.fi),
2899
+ fr: () => import("date-fns/locale/fr").then((m) => m.fr),
2900
+ frCA: () => import("date-fns/locale/fr-CA").then((m) => m.frCA),
2901
+ frCH: () => import("date-fns/locale/fr-CH").then((m) => m.frCH),
2902
+ gd: () => import("date-fns/locale/gd").then((m) => m.gd),
2903
+ gl: () => import("date-fns/locale/gl").then((m) => m.gl),
2904
+ gu: () => import("date-fns/locale/gu").then((m) => m.gu),
2905
+ he: () => import("date-fns/locale/he").then((m) => m.he),
2906
+ hi: () => import("date-fns/locale/hi").then((m) => m.hi),
2907
+ hr: () => import("date-fns/locale/hr").then((m) => m.hr),
2908
+ hu: () => import("date-fns/locale/hu").then((m) => m.hu),
2909
+ hy: () => import("date-fns/locale/hy").then((m) => m.hy),
2910
+ id: () => import("date-fns/locale/id").then((m) => m.id),
2911
+ is: () => import("date-fns/locale/is").then((m) => m.is),
2912
+ it: () => import("date-fns/locale/it").then((m) => m.it),
2913
+ ja: () => import("date-fns/locale/ja").then((m) => m.ja),
2914
+ ka: () => import("date-fns/locale/ka").then((m) => m.ka),
2915
+ kk: () => import("date-fns/locale/kk").then((m) => m.kk),
2916
+ kn: () => import("date-fns/locale/kn").then((m) => m.kn),
2917
+ ko: () => import("date-fns/locale/ko").then((m) => m.ko),
2918
+ lb: () => import("date-fns/locale/lb").then((m) => m.lb),
2919
+ lt: () => import("date-fns/locale/lt").then((m) => m.lt),
2920
+ lv: () => import("date-fns/locale/lv").then((m) => m.lv),
2921
+ mk: () => import("date-fns/locale/mk").then((m) => m.mk),
2922
+ ms: () => import("date-fns/locale/ms").then((m) => m.ms),
2923
+ mt: () => import("date-fns/locale/mt").then((m) => m.mt),
2924
+ nb: () => import("date-fns/locale/nb").then((m) => m.nb),
2925
+ nl: () => import("date-fns/locale/nl").then((m) => m.nl),
2926
+ nlBE: () => import("date-fns/locale/nl-BE").then((m) => m.nlBE),
2927
+ nn: () => import("date-fns/locale/nn").then((m) => m.nn),
2928
+ pl: () => import("date-fns/locale/pl").then((m) => m.pl),
2929
+ pt: () => import("date-fns/locale/pt").then((m) => m.pt),
2930
+ ptBR: () => import("date-fns/locale/pt-BR").then((m) => m.ptBR),
2931
+ ro: () => import("date-fns/locale/ro").then((m) => m.ro),
2932
+ ru: () => import("date-fns/locale/ru").then((m) => m.ru),
2933
+ sk: () => import("date-fns/locale/sk").then((m) => m.sk),
2934
+ sl: () => import("date-fns/locale/sl").then((m) => m.sl),
2935
+ sr: () => import("date-fns/locale/sr").then((m) => m.sr),
2936
+ srLatn: () => import("date-fns/locale/sr-Latn").then((m) => m.srLatn),
2937
+ sv: () => import("date-fns/locale/sv").then((m) => m.sv),
2938
+ ta: () => import("date-fns/locale/ta").then((m) => m.ta),
2939
+ te: () => import("date-fns/locale/te").then((m) => m.te),
2940
+ th: () => import("date-fns/locale/th").then((m) => m.th),
2941
+ tr: () => import("date-fns/locale/tr").then((m) => m.tr),
2942
+ ug: () => import("date-fns/locale/ug").then((m) => m.ug),
2943
+ uk: () => import("date-fns/locale/uk").then((m) => m.uk),
2944
+ uz: () => import("date-fns/locale/uz").then((m) => m.uz),
2945
+ vi: () => import("date-fns/locale/vi").then((m) => m.vi),
2946
+ zhCN: () => import("date-fns/locale/zh-CN").then((m) => m.zhCN),
2947
+ zhTW: () => import("date-fns/locale/zh-TW").then((m) => m.zhTW)
2948
+ };
2949
+ var cache = /* @__PURE__ */ new Map();
2950
+ /** The locale if it has already been fetched, otherwise `undefined`. Never fetches. */
2951
+ function getLoadedDateFnsLocale(locale) {
2952
+ return locale ? cache.get(locale) : void 0;
2953
+ }
2954
+ /**
2955
+ * Fetch a date-fns locale. Resolves to `undefined` for a locale date-fns does
2956
+ * not ship, which is what indexing the namespace used to yield.
2957
+ */
2958
+ async function loadDateFnsLocale(locale) {
2959
+ if (!locale) return void 0;
2960
+ const cached = cache.get(locale);
2961
+ if (cached) return cached;
2962
+ const loader = dateFnsLocaleLoaders[locale];
2963
+ if (!loader) return void 0;
2964
+ const loaded = await loader();
2965
+ cache.set(locale, loaded);
2966
+ return loaded;
2967
+ }
2968
+ //#endregion
2854
2969
  //#region src/preview/components/DatePreview.tsx
2855
2970
  /**
2856
2971
  * @group Preview components
2857
2972
  */
2858
2973
  function DatePreview({ date, mode = "date_time", timezone }) {
2859
2974
  const customizationController = useCustomizationController();
2860
- const dateUtilsLocale = customizationController?.locale ? locales[customizationController.locale] : void 0;
2975
+ const configuredLocale = customizationController?.locale;
2976
+ const [dateUtilsLocale, setDateUtilsLocale] = React.useState(() => getLoadedDateFnsLocale(configuredLocale));
2977
+ React.useEffect(() => {
2978
+ let cancelled = false;
2979
+ loadDateFnsLocale(configuredLocale).then((loaded) => {
2980
+ if (!cancelled) setDateUtilsLocale(loaded);
2981
+ }).catch((error) => console.error(`[rebase] could not load the "${configuredLocale}" date locale`, error));
2982
+ return () => {
2983
+ cancelled = true;
2984
+ };
2985
+ }, [configuredLocale]);
2861
2986
  if (!date || !(date instanceof Date) || isNaN(date.getTime())) return /* @__PURE__ */ jsx(Fragment, {});
2862
2987
  if (timezone) {
2863
2988
  const options = {
@@ -3585,9 +3710,13 @@ function useUserSelector({ pageSize = DEFAULT_PAGE_SIZE$2 } = {}) {
3585
3710
  const [currentSearch, setCurrentSearch] = useState("");
3586
3711
  const offsetRef = useRef(0);
3587
3712
  const userCacheRef = useRef(/* @__PURE__ */ new Map());
3713
+ const requestIdRef = useRef(0);
3714
+ const searchTimeoutRef = useRef(null);
3588
3715
  const fetchUsers = useCallback(async (searchStr, offset, append) => {
3589
3716
  const apiBase = apiBaseOf(client);
3590
3717
  if (!apiBase) return;
3718
+ const requestId = ++requestIdRef.current;
3719
+ const superseded = () => requestId !== requestIdRef.current;
3591
3720
  setIsLoading(true);
3592
3721
  try {
3593
3722
  const token = await getAuthToken?.();
@@ -3599,6 +3728,7 @@ function useUserSelector({ pageSize = DEFAULT_PAGE_SIZE$2 } = {}) {
3599
3728
  const response = await fetch(`${apiBase}/admin/users?${params}`, { headers: token ? { Authorization: `Bearer ${token}` } : {} });
3600
3729
  if (!response.ok) throw new Error("Failed to fetch users");
3601
3730
  const data = await response.json();
3731
+ if (superseded()) return;
3602
3732
  const newItems = (data.data ?? data.users ?? []).map((r) => {
3603
3733
  const user = {
3604
3734
  uid: r.uid ?? r.id ?? "",
@@ -3620,9 +3750,10 @@ function useUserSelector({ pageSize = DEFAULT_PAGE_SIZE$2 } = {}) {
3620
3750
  setHasMore(newItems.length >= pageSize);
3621
3751
  offsetRef.current = offset + newItems.length;
3622
3752
  } catch {
3753
+ if (superseded()) return;
3623
3754
  setHasMore(false);
3624
3755
  } finally {
3625
- setIsLoading(false);
3756
+ if (!superseded()) setIsLoading(false);
3626
3757
  }
3627
3758
  }, [
3628
3759
  client?.baseUrl,
@@ -3634,15 +3765,24 @@ function useUserSelector({ pageSize = DEFAULT_PAGE_SIZE$2 } = {}) {
3634
3765
  offsetRef.current = 0;
3635
3766
  fetchUsers("", 0, false);
3636
3767
  }, [fetchUsers]);
3768
+ const search = useCallback((searchStr) => {
3769
+ if (searchTimeoutRef.current) clearTimeout(searchTimeoutRef.current);
3770
+ searchTimeoutRef.current = setTimeout(() => {
3771
+ setCurrentSearch(searchStr);
3772
+ offsetRef.current = 0;
3773
+ fetchUsers(searchStr, 0, false);
3774
+ }, searchStr.trim() ? 300 : 0);
3775
+ }, [fetchUsers]);
3776
+ useEffect(() => {
3777
+ return () => {
3778
+ if (searchTimeoutRef.current) clearTimeout(searchTimeoutRef.current);
3779
+ };
3780
+ }, []);
3637
3781
  return {
3638
3782
  items,
3639
3783
  isLoading,
3640
3784
  hasMore,
3641
- search: useCallback((searchStr) => {
3642
- setCurrentSearch(searchStr);
3643
- offsetRef.current = 0;
3644
- fetchUsers(searchStr, 0, false);
3645
- }, [fetchUsers]),
3785
+ search,
3646
3786
  loadMore: useCallback(() => {
3647
3787
  fetchUsers(currentSearch, offsetRef.current, true);
3648
3788
  }, [fetchUsers, currentSearch]),
@@ -3667,7 +3807,9 @@ var UserSelector = React$1.forwardRef(({ value, size = "medium", onValueChange,
3667
3807
  }, [ref]);
3668
3808
  const contentRef = useRef(null);
3669
3809
  const searchInputRef = useRef(null);
3670
- const resolvedUser = (value ? getUser(value) ?? null : null) ?? (value ? availableItems.find((i) => i.uid === value)?.user ?? null : null);
3810
+ const listedUser = (value ? getUser(value) ?? null : null) ?? (value ? availableItems.find((i) => i.uid === value)?.user ?? null : null);
3811
+ const fetchedUser = useResolvedUser(value && !listedUser ? value : void 0);
3812
+ const resolvedUser = listedUser ?? fetchedUser;
3671
3813
  const sentinelCallbackRef = useCallback((node) => {
3672
3814
  if (observerRef.current) {
3673
3815
  observerRef.current.disconnect();
@@ -3776,6 +3918,9 @@ var UserSelector = React$1.forwardRef(({ value, size = "medium", onValueChange,
3776
3918
  children: [resolvedUser ? /* @__PURE__ */ jsx("div", {
3777
3919
  className: "flex flex-row items-center gap-1 truncate flex-1 min-w-0 mr-2",
3778
3920
  children: /* @__PURE__ */ jsx(UserDisplay, { user: resolvedUser })
3921
+ }) : value ? /* @__PURE__ */ jsx("span", {
3922
+ className: "text-sm truncate text-text-primary dark:text-text-primary-dark",
3923
+ children: value
3779
3924
  }) : /* @__PURE__ */ jsx("span", {
3780
3925
  className: "text-sm text-text-secondary dark:text-text-secondary-dark",
3781
3926
  children: resolvedPlaceholder
@@ -4453,7 +4598,7 @@ function relationCardinality$2(relation) {
4453
4598
  if (relation.kind === "belongsTo" || relation.kind === "hasOne") return "one";
4454
4599
  return relation.cardinality;
4455
4600
  }
4456
- var RelationSelector = React$1.forwardRef(({ value, size = "medium", onValueChange, invisible, disabled, placeholder, useChips = true, className, relation, multiple: multipleOverride, fixedFilter, pageSize, emptyPlaceholder, searchPlaceholder = "Search...", noResultsText = "No relations found.", loadingText = "Loading..." }, ref) => {
4601
+ var RelationSelector = React$1.forwardRef(({ value, size = "medium", onValueChange, invisible, disabled, placeholder, useChips = true, className, relation, multiple: multipleOverride, fixedFilter, pageSize, emptyPlaceholder, searchPlaceholder = "Search...", noResultsText = "No matches.", emptyText = "Select…", emptyCollectionText, loadingText = "Loading..." }, ref) => {
4457
4602
  const collection = relation.target();
4458
4603
  const dataClient = useData();
4459
4604
  const sidePanelController = useSidePanel();
@@ -4461,6 +4606,7 @@ var RelationSelector = React$1.forwardRef(({ value, size = "medium", onValueChan
4461
4606
  const multiple = multipleOverride ?? relationCardinality$2(relation) === "many";
4462
4607
  const [isPopoverOpen, setIsPopoverOpen] = useState(false);
4463
4608
  const isPopoverOpenRef = useRef(false);
4609
+ const [listRequested, setListRequested] = useState(false);
4464
4610
  const [selectedItems, setSelectedItems] = useState([]);
4465
4611
  const [isLoadingSelectedItems, setIsLoadingSelectedItems] = useState(false);
4466
4612
  const [searchString, setSearchString] = useState("");
@@ -4470,7 +4616,8 @@ var RelationSelector = React$1.forwardRef(({ value, size = "medium", onValueChan
4470
4616
  path: getCollectionDataPath(collection),
4471
4617
  collection,
4472
4618
  fixedFilter,
4473
- pageSize
4619
+ pageSize,
4620
+ enabled: listRequested
4474
4621
  });
4475
4622
  const scrollContainerRef = useRef(null);
4476
4623
  const sentinelRef = useRef(null);
@@ -4663,6 +4810,7 @@ var RelationSelector = React$1.forwardRef(({ value, size = "medium", onValueChan
4663
4810
  if (disabled) return;
4664
4811
  if (next) {
4665
4812
  pinnedIdsRef.current = new Set(selectedItems.map((i) => String(i.id)));
4813
+ setListRequested(true);
4666
4814
  setIsPopoverOpen(true);
4667
4815
  isPopoverOpenRef.current = true;
4668
4816
  }
@@ -4698,7 +4846,7 @@ var RelationSelector = React$1.forwardRef(({ value, size = "medium", onValueChan
4698
4846
  isPopoverOpenRef.current = false;
4699
4847
  pinnedIdsRef.current = null;
4700
4848
  }, []);
4701
- const resolvedPlaceholder = placeholder || emptyPlaceholder || /* @__PURE__ */ jsx(EmptyValue, { className: "ml-2" });
4849
+ const resolvedPlaceholder = placeholder || emptyPlaceholder || emptyText;
4702
4850
  const portalContainer = contextPortalContainer ?? (typeof document !== "undefined" ? document.body : void 0) ?? void 0;
4703
4851
  return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(PopoverPrimitive.Root, {
4704
4852
  open: isPopoverOpen,
@@ -4715,6 +4863,7 @@ var RelationSelector = React$1.forwardRef(({ value, size = "medium", onValueChan
4715
4863
  disabled,
4716
4864
  onClick: () => {
4717
4865
  if (disabled) return;
4866
+ setListRequested(true);
4718
4867
  setIsPopoverOpen((o) => {
4719
4868
  const next = !o;
4720
4869
  isPopoverOpenRef.current = next;
@@ -4871,7 +5020,7 @@ var RelationSelector = React$1.forwardRef(({ value, size = "medium", onValueChan
4871
5020
  }),
4872
5021
  !isLoading && availableItems.length === 0 && /* @__PURE__ */ jsx(Command.Empty, {
4873
5022
  className: "px-4 py-6 text-center text-text-secondary dark:text-text-secondary-dark",
4874
- children: noResultsText
5023
+ children: searchString ? noResultsText : emptyCollectionText ?? `No ${collection.name ?? "entries"} yet.`
4875
5024
  }),
4876
5025
  /* @__PURE__ */ jsxs(Command.Group, { children: [
4877
5026
  (() => {
@@ -5102,14 +5251,16 @@ function getTableBindingForProperty(property, selected) {
5102
5251
  allowScroll: false
5103
5252
  };
5104
5253
  } else if (property.type === "relation") {
5105
- if (property.relation) if (property.admin?.widget === "dialog") return {
5106
- Component: RelationDialogBindingComponent,
5107
- allowScroll: false
5108
- };
5109
- else return {
5110
- Component: RelationSelectorBindingComponent,
5111
- allowScroll: false
5112
- };
5254
+ if (property.relation) {
5255
+ if (property.admin?.widget === "dialog") return {
5256
+ Component: RelationDialogBindingComponent,
5257
+ allowScroll: false
5258
+ };
5259
+ else if (selected) return {
5260
+ Component: RelationSelectorBindingComponent,
5261
+ allowScroll: false
5262
+ };
5263
+ }
5113
5264
  } else if (property.type === "array") {
5114
5265
  const arrayProperty = property;
5115
5266
  if (!arrayProperty.of && !arrayProperty.oneOf) throw Error("You need to specify an 'of' or 'oneOf' prop (or specify a custom field) in your array property");
@@ -6268,6 +6419,7 @@ function StringNumberFilterField({ name, value, setValue, type, isArray, enumVal
6268
6419
  children: [
6269
6420
  !enumValues && /* @__PURE__ */ jsx(TextField, {
6270
6421
  size: "medium",
6422
+ "aria-label": `${title ?? name} filter value`,
6271
6423
  type: type === "number" ? "number" : void 0,
6272
6424
  value: internalValue !== void 0 && internalValue != null ? String(internalValue) : "",
6273
6425
  disabled: isNullOperation,
@@ -7630,15 +7782,6 @@ function FieldCaption({ error, children }) {
7630
7782
  //#endregion
7631
7783
  //#region src/components/SearchIconsView.tsx
7632
7784
  var UPDATE_SEARCH_INDEX_WAIT_MS = 220;
7633
- function toPascalCase(str) {
7634
- return str.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
7635
- }
7636
- function resolveIcon(iconKey) {
7637
- const allIcons = lucideIcons;
7638
- let icon = allIcons[iconKey];
7639
- if (!icon) icon = allIcons[toPascalCase(iconKey)];
7640
- return icon ?? null;
7641
- }
7642
7785
  function SearchIconsView({ selectedIcon = "", onIconSelected }) {
7643
7786
  const { t } = useTranslation();
7644
7787
  const [keys, setKeys] = React.useState(null);
@@ -7664,21 +7807,20 @@ function SearchIconsView({ selectedIcon = "", onIconSelected }) {
7664
7807
  placeholder: t("search_for_more_icons")
7665
7808
  }), /* @__PURE__ */ jsx("div", {
7666
7809
  className: "flex max-w-full flex-wrap mt-4",
7667
- children: icons.map((icon) => {
7668
- const LucideIcon = resolveIcon(icon);
7669
- if (!LucideIcon) return null;
7670
- return /* @__PURE__ */ jsx(Tooltip, {
7671
- title: icon,
7672
- asChild: true,
7673
- children: /* @__PURE__ */ jsx(IconButton, {
7674
- shape: "square",
7675
- toggled: selectedIcon === icon,
7676
- onClick: onIconSelected ? () => onIconSelected(icon) : void 0,
7677
- className: "box-content m-1",
7678
- children: /* @__PURE__ */ jsx(LucideIcon, { size: iconSize.medium })
7810
+ children: icons.map((icon) => /* @__PURE__ */ jsx(Tooltip, {
7811
+ title: icon,
7812
+ asChild: true,
7813
+ children: /* @__PURE__ */ jsx(IconButton, {
7814
+ shape: "square",
7815
+ toggled: selectedIcon === icon,
7816
+ onClick: onIconSelected ? () => onIconSelected(icon) : void 0,
7817
+ className: "box-content m-1",
7818
+ children: /* @__PURE__ */ jsx(LucideIconByName, {
7819
+ name: icon,
7820
+ size: iconSize.medium
7679
7821
  })
7680
- }, icon);
7681
- })
7822
+ })
7823
+ }, icon))
7682
7824
  })] });
7683
7825
  }
7684
7826
  //#endregion
@@ -8022,10 +8164,23 @@ function getWorksheetHeaders(worksheet) {
8022
8164
  /**
8023
8165
  * Take an object with keys of type `address.street`, `address.city` and
8024
8166
  * convert it to an object with nested objects like `{ address: { street: ..., city: ... } }`
8167
+ *
8168
+ * Keys here are the **header row of an uploaded file**, so they are attacker
8169
+ * data. `res["__proto__"] = …` is the prototype setter rather than an own
8170
+ * property, so a column named `__proto__.polluted` walked out of the accumulator
8171
+ * and wrote onto `Object.prototype` for the life of the tab — and
8172
+ * `constructor.prototype.x` threw a `TypeError` that surfaced as an unreadable
8173
+ * file. Such a column is refused, not sanitised, the way `setIn` settled it
8174
+ * (`docs/bug-classes.md` class 22).
8175
+ *
8025
8176
  * @param flatObj
8026
8177
  */
8027
8178
  function unflattenObject(flatObj) {
8028
8179
  return Object.keys(flatObj).reduce((nestedObj, key) => {
8180
+ if (pathTraversesPrototype(key)) {
8181
+ console.warn(`Skipping column "${key}": a header may not reach the prototype chain`);
8182
+ return nestedObj;
8183
+ }
8029
8184
  let currentObj = nestedObj;
8030
8185
  const keyParts = key.split(".");
8031
8186
  keyParts.forEach((keyPart, i) => {
@@ -8047,6 +8202,10 @@ function unflattenObject(flatObj) {
8047
8202
  }
8048
8203
  function mapJsonParse(obj) {
8049
8204
  return Object.keys(obj).reduce((acc, key) => {
8205
+ if (isPrototypePollutingKey(key)) {
8206
+ console.warn(`Skipping column "${key}": a header may not reach the prototype chain`);
8207
+ return acc;
8208
+ }
8050
8209
  try {
8051
8210
  acc[key] = JSON.parse(obj[key]);
8052
8211
  } catch (e) {
@@ -8056,10 +8215,183 @@ function mapJsonParse(obj) {
8056
8215
  }, {});
8057
8216
  }
8058
8217
  //#endregion
8218
+ //#region src/data_import/utils/csv.ts
8219
+ /**
8220
+ * Delimiters sniffed from the header row, most common first.
8221
+ *
8222
+ * A spreadsheet saved as "CSV" in a locale that uses the comma as a decimal
8223
+ * separator is semicolon-delimited, and a tab-separated file saved as `.csv`
8224
+ * happens often enough to be worth reading.
8225
+ */
8226
+ var CANDIDATE_DELIMITERS = [
8227
+ ",",
8228
+ ";",
8229
+ " "
8230
+ ];
8231
+ /**
8232
+ * Split CSV text into rows of raw cells, RFC 4180 style.
8233
+ *
8234
+ * Quoted fields may contain the delimiter, newlines and doubled quotes; this is
8235
+ * the exact inverse of what the export's `entryToCSVRow` writes, so a file this
8236
+ * admin produced reads back into the cells it was built from.
8237
+ */
8238
+ function parseCsvRows(text, delimiter = ",") {
8239
+ const rows = [];
8240
+ let row = [];
8241
+ let field = "";
8242
+ let quoted = false;
8243
+ let inQuotes = false;
8244
+ let i = 0;
8245
+ const endField = () => {
8246
+ row.push(field);
8247
+ field = "";
8248
+ quoted = false;
8249
+ };
8250
+ while (i < text.length) {
8251
+ const char = text[i];
8252
+ if (inQuotes) {
8253
+ if (char === "\"") {
8254
+ if (text[i + 1] === "\"") {
8255
+ field += "\"";
8256
+ i += 2;
8257
+ continue;
8258
+ }
8259
+ inQuotes = false;
8260
+ i++;
8261
+ continue;
8262
+ }
8263
+ field += char;
8264
+ i++;
8265
+ continue;
8266
+ }
8267
+ if (char === "\"" && field === "") {
8268
+ inQuotes = true;
8269
+ quoted = true;
8270
+ i++;
8271
+ continue;
8272
+ }
8273
+ if (char === delimiter) {
8274
+ endField();
8275
+ i++;
8276
+ continue;
8277
+ }
8278
+ if (char === "\r") {
8279
+ i++;
8280
+ continue;
8281
+ }
8282
+ if (char === "\n") {
8283
+ endField();
8284
+ rows.push(row);
8285
+ row = [];
8286
+ i++;
8287
+ continue;
8288
+ }
8289
+ field += char;
8290
+ i++;
8291
+ }
8292
+ if (field !== "" || quoted || row.length > 0) {
8293
+ endField();
8294
+ rows.push(row);
8295
+ }
8296
+ return rows;
8297
+ }
8298
+ /**
8299
+ * Pick the delimiter that splits the header row into the most cells. A
8300
+ * single-column file matches nothing and stays on the comma.
8301
+ */
8302
+ function detectCsvDelimiter(text) {
8303
+ const sample = text.slice(0, 64 * 1024);
8304
+ let best = CANDIDATE_DELIMITERS[0];
8305
+ let bestCount = 1;
8306
+ for (const candidate of CANDIDATE_DELIMITERS) {
8307
+ const count = parseCsvRows(sample, candidate)[0]?.length ?? 0;
8308
+ if (count > bestCount) {
8309
+ best = candidate;
8310
+ bestCount = count;
8311
+ }
8312
+ }
8313
+ return best;
8314
+ }
8315
+ /**
8316
+ * Parse CSV text into one object per row, keyed by the header row.
8317
+ *
8318
+ * The picker accepts `.csv` and the dialog promises "Upload a CSV, Excel or JSON
8319
+ * file", but every non-JSON file used to be handed to ExcelJS'
8320
+ * `workbook.xlsx.load`, which is a zip reader — so every CSV failed with
8321
+ * "Can't find end of central directory : is this a zip file ?". The export
8322
+ * writes CSV by default, so the round trip could not close.
8323
+ *
8324
+ * A blank header keeps its column rather than collapsing it, so the cells to its
8325
+ * right stay under their own names.
8326
+ */
8327
+ function parseCsvToObjects(text) {
8328
+ const cleaned = text.charCodeAt(0) === 65279 ? text.slice(1) : text;
8329
+ const rows = parseCsvRows(cleaned, detectCsvDelimiter(cleaned)).filter((row) => row.some((cell) => cell.trim() !== ""));
8330
+ if (rows.length === 0) return {
8331
+ headers: [],
8332
+ data: []
8333
+ };
8334
+ const headers = rows[0].map((header, index) => header.trim() || `Column${index + 1}`);
8335
+ return {
8336
+ headers,
8337
+ data: rows.slice(1).map((cells) => {
8338
+ const obj = {};
8339
+ headers.forEach((header, index) => {
8340
+ if (isPrototypePollutingKey(header)) return;
8341
+ const cell = cells[index];
8342
+ if (cell === void 0) return;
8343
+ obj[header] = cell;
8344
+ });
8345
+ return obj;
8346
+ })
8347
+ };
8348
+ }
8349
+ //#endregion
8059
8350
  //#region src/data_import/utils/file_to_json.ts
8351
+ var excelJsModule;
8352
+ function loadExcelJs() {
8353
+ excelJsModule ??= import("exceljs").then((mod) => mod.default ?? mod);
8354
+ return excelJsModule;
8355
+ }
8356
+ /**
8357
+ * Whether this file is delimited text rather than a workbook.
8358
+ *
8359
+ * Browsers report `text/csv`, `application/csv` or nothing at all for the same
8360
+ * `.csv`, so the extension is the reliable half of the test.
8361
+ */
8362
+ function isCsvFile(file) {
8363
+ const name = (file.name ?? "").toLowerCase();
8364
+ if (name.endsWith(".csv") || name.endsWith(".tsv")) return true;
8365
+ return file.type === "text/csv" || file.type === "application/csv";
8366
+ }
8367
+ /** Turn the parsed cells of one sheet/file into the entity-shaped rows the import maps. */
8368
+ function toImportRows(rows) {
8369
+ return rows.map(mapJsonParse).map(unflattenObject);
8370
+ }
8060
8371
  function convertFileToJson(file) {
8061
8372
  return new Promise((resolve, reject) => {
8062
- if (file.type === "application/json") {
8373
+ if (isCsvFile(file)) {
8374
+ console.debug("Converting CSV file to JSON", file.name);
8375
+ const reader = new FileReader();
8376
+ reader.onload = function(e) {
8377
+ try {
8378
+ const { headers, data } = parseCsvToObjects(e.target?.result);
8379
+ if (headers.length === 0) {
8380
+ reject(/* @__PURE__ */ new Error("The CSV file is empty"));
8381
+ return;
8382
+ }
8383
+ resolve({
8384
+ data: toImportRows(data),
8385
+ propertiesOrder: headers
8386
+ });
8387
+ } catch (err) {
8388
+ console.error("Error parsing CSV file", err);
8389
+ reject(err);
8390
+ }
8391
+ };
8392
+ reader.onerror = () => reject(reader.error ?? /* @__PURE__ */ new Error("Could not read the file"));
8393
+ reader.readAsText(file, "utf-8");
8394
+ } else if (file.type === "application/json") {
8063
8395
  console.debug("Converting JSON file to JSON", file.name);
8064
8396
  const reader = new FileReader();
8065
8397
  reader.onload = function(e) {
@@ -8083,7 +8415,7 @@ function convertFileToJson(file) {
8083
8415
  reader.onload = async function(e) {
8084
8416
  try {
8085
8417
  const buffer = e.target?.result;
8086
- const workbook = new ExcelJS.Workbook();
8418
+ const workbook = new (await (loadExcelJs())).Workbook();
8087
8419
  await workbook.xlsx.load(buffer);
8088
8420
  const worksheet = workbook.worksheets[0];
8089
8421
  if (!worksheet) {
@@ -8097,12 +8429,12 @@ function convertFileToJson(file) {
8097
8429
  const obj = {};
8098
8430
  row.eachCell({ includeEmpty: false }, (cell, colNumber) => {
8099
8431
  const header = headers[colNumber - 1];
8100
- if (header) obj[header] = cell.value;
8432
+ if (header && !isPrototypePollutingKey(header)) obj[header] = cell.value;
8101
8433
  });
8102
8434
  parsedData.push(obj);
8103
8435
  });
8104
8436
  resolve({
8105
- data: parsedData.map(mapJsonParse).map(unflattenObject),
8437
+ data: toImportRows(parsedData),
8106
8438
  propertiesOrder: headers
8107
8439
  });
8108
8440
  } catch (err) {
@@ -8151,44 +8483,153 @@ function ImportFileUpload({ onDataAdded }) {
8151
8483
  });
8152
8484
  }
8153
8485
  //#endregion
8486
+ //#region src/data_import/utils/save_entities.ts
8487
+ /** Rows written per request, and per transaction where the data source has one. */
8488
+ var IMPORT_BATCH_SIZE = 25;
8489
+ /** The row a bulk write takes: the values, with the chosen id as a column. */
8490
+ function toBulkRow(entity) {
8491
+ const values = { ...entity.values ?? {} };
8492
+ if (entity.id !== void 0 && entity.id !== null && entity.id !== "") values.id = entity.id;
8493
+ return values;
8494
+ }
8495
+ function isBulkUnsupported(error) {
8496
+ if (error?.code === "BULK_UNSUPPORTED") return true;
8497
+ const message = error?.message ?? "";
8498
+ return /bulk writes are not supported|does not support bulk/i.test(message);
8499
+ }
8500
+ function isConflict(error) {
8501
+ if ((error?.status ?? error?.statusCode) === 409) return true;
8502
+ const code = error?.code;
8503
+ return code === "CONFLICT" || code === "23505";
8504
+ }
8505
+ /**
8506
+ * Write one batch, all-or-nothing wherever the data source can.
8507
+ *
8508
+ * `POST /<collection>/bulk` validates every row before the transaction opens,
8509
+ * names a failure by row index, and takes the `upsert` flag the preview screen
8510
+ * promises — none of which one `create` per row inside a `Promise.all` could
8511
+ * offer, since `Promise.all` does not cancel its siblings and every request was
8512
+ * its own transaction.
8513
+ */
8514
+ async function writeBatch(accessor, batch, bulkUnsupported, written) {
8515
+ if (accessor.createMany && !bulkUnsupported.current) try {
8516
+ await accessor.createMany(batch.map(toBulkRow), { upsert: true });
8517
+ written.count = batch.length;
8518
+ return;
8519
+ } catch (e) {
8520
+ if (!isBulkUnsupported(e)) throw e;
8521
+ bulkUnsupported.current = true;
8522
+ }
8523
+ for (const entity of batch) {
8524
+ try {
8525
+ await accessor.create(entity.values ?? {}, entity.id);
8526
+ } catch (e) {
8527
+ if (entity.id === void 0 || !isConflict(e)) throw e;
8528
+ await accessor.update(entity.id, entity.values ?? {});
8529
+ }
8530
+ written.count++;
8531
+ }
8532
+ }
8533
+ /**
8534
+ * Write imported entities in batches, reporting what was committed.
8535
+ *
8536
+ * Resumable on purpose: a failure at row 4 000 leaves rows 1–3 999 in the table
8537
+ * whatever this does, so the only useful thing to report is where to carry on
8538
+ * from. `offset` is that point — the Retry button passes back the `committed`
8539
+ * count of the error it is retrying, instead of starting from zero and
8540
+ * conflicting on every row that already exists.
8541
+ *
8542
+ * @throws {ImportSaveError} naming the rows that were not written.
8543
+ */
8544
+ async function saveImportedEntities(dataClient, path, data, { offset = 0, batchSize = 25, onBatchCommitted = () => void 0, bulkUnsupported = { current: false } } = {}) {
8545
+ console.debug("Saving imported data", offset, batchSize);
8546
+ const accessor = dataClient.collection(path);
8547
+ for (let start = offset; start < data.length; start += batchSize) {
8548
+ const batch = data.slice(start, start + batchSize);
8549
+ const written = { count: 0 };
8550
+ try {
8551
+ await writeBatch(accessor, batch, bulkUnsupported, written);
8552
+ } catch (e) {
8553
+ throw Object.assign(e, {
8554
+ committed: start + written.count,
8555
+ failedFrom: start + written.count,
8556
+ failedTo: start + batch.length
8557
+ });
8558
+ }
8559
+ onBatchCommitted(start + batch.length);
8560
+ }
8561
+ onBatchCommitted(data.length);
8562
+ }
8563
+ //#endregion
8154
8564
  //#region src/data_import/components/ImportSaveInProgress.tsx
8155
8565
  function ImportSaveInProgress({ path, importConfig, collection, onImportSuccess }) {
8156
8566
  const [errorSaving, setErrorSaving] = useState(void 0);
8157
8567
  const dataClient = useData();
8158
8568
  const savingRef = useRef(false);
8569
+ const committedRef = useRef(0);
8570
+ const bulkUnsupportedRef = useRef({ current: false });
8159
8571
  const [processedEntities, setProcessedEntities] = useState(0);
8160
8572
  function save() {
8161
8573
  if (savingRef.current) return;
8162
8574
  savingRef.current = true;
8163
- saveDataBatch(dataClient, collection, path, importConfig.entities, 0, 25, setProcessedEntities).then(() => {
8575
+ setErrorSaving(void 0);
8576
+ saveImportedEntities(dataClient, path, importConfig.entities, {
8577
+ offset: committedRef.current,
8578
+ batchSize: 25,
8579
+ onBatchCommitted: (written) => {
8580
+ committedRef.current = written;
8581
+ setProcessedEntities(written);
8582
+ },
8583
+ bulkUnsupported: bulkUnsupportedRef.current
8584
+ }).then(() => {
8164
8585
  onImportSuccess(collection);
8165
8586
  savingRef.current = false;
8166
8587
  }).catch((e) => {
8167
- setErrorSaving(e);
8588
+ const error = e;
8589
+ committedRef.current = error.committed ?? committedRef.current;
8590
+ setProcessedEntities(committedRef.current);
8591
+ setErrorSaving(error);
8168
8592
  savingRef.current = false;
8169
8593
  });
8170
8594
  }
8171
8595
  useEffect(() => {
8172
8596
  save();
8173
8597
  }, []);
8174
- if (errorSaving) return /* @__PURE__ */ jsxs(CenteredView, {
8175
- className: "flex flex-col gap-4 items-center",
8176
- children: [
8177
- /* @__PURE__ */ jsx(Typography, {
8178
- variant: "h6",
8179
- children: "Error saving data"
8180
- }),
8181
- /* @__PURE__ */ jsx(Typography, {
8182
- variant: "body2",
8183
- color: "error",
8184
- children: errorSaving.message
8185
- }),
8186
- /* @__PURE__ */ jsx(Button, {
8187
- onClick: save,
8188
- children: "Retry"
8189
- })
8190
- ]
8191
- });
8598
+ if (errorSaving) {
8599
+ const total = importConfig.entities.length;
8600
+ const failedRows = errorSaving.failedTo - errorSaving.failedFrom > 1 ? `rows ${errorSaving.failedFrom + 1}–${errorSaving.failedTo}` : `row ${errorSaving.failedFrom + 1}`;
8601
+ return /* @__PURE__ */ jsxs(CenteredView, {
8602
+ className: "flex flex-col gap-4 items-center",
8603
+ children: [
8604
+ /* @__PURE__ */ jsx(Typography, {
8605
+ variant: "h6",
8606
+ children: "Error saving data"
8607
+ }),
8608
+ /* @__PURE__ */ jsx(Typography, {
8609
+ variant: "body2",
8610
+ color: "error",
8611
+ children: errorSaving.message
8612
+ }),
8613
+ /* @__PURE__ */ jsxs(Typography, {
8614
+ variant: "body2",
8615
+ children: [
8616
+ errorSaving.committed,
8617
+ " of ",
8618
+ total,
8619
+ " rows were imported. The import stopped on ",
8620
+ failedRows,
8621
+ ", which ",
8622
+ errorSaving.failedTo - errorSaving.failedFrom > 1 ? "were" : "was",
8623
+ " not written."
8624
+ ]
8625
+ }),
8626
+ /* @__PURE__ */ jsx(Button, {
8627
+ onClick: save,
8628
+ children: errorSaving.committed > 0 ? `Retry from row ${errorSaving.committed + 1}` : "Retry"
8629
+ })
8630
+ ]
8631
+ });
8632
+ }
8192
8633
  return /* @__PURE__ */ jsxs("div", {
8193
8634
  className: "flex flex-col gap-4 items-center",
8194
8635
  children: [
@@ -8213,18 +8654,6 @@ function ImportSaveInProgress({ path, importConfig, collection, onImportSuccess
8213
8654
  ]
8214
8655
  });
8215
8656
  }
8216
- function saveDataBatch(dataClient, collection, path, data, offset = 0, batchSize = 25, onProgressUpdate) {
8217
- console.debug("Saving imported data", offset, batchSize);
8218
- const batch = data.slice(offset, offset + batchSize);
8219
- return Promise.all(batch.map((d) => dataClient.collection(path).create(d.values, d.id))).then(() => {
8220
- if (offset + batchSize < data.length) {
8221
- onProgressUpdate(offset + batchSize);
8222
- return saveDataBatch(dataClient, collection, path, data, offset + batchSize, batchSize, onProgressUpdate);
8223
- }
8224
- onProgressUpdate(data.length);
8225
- return Promise.resolve();
8226
- });
8227
- }
8228
8657
  //#endregion
8229
8658
  //#region src/data_import/hooks/useImportConfig.tsx
8230
8659
  var useImportConfig = () => {
@@ -8296,6 +8725,10 @@ function convertDataToEntity(authController, navigation, data, idColumn, headers
8296
8725
  }
8297
8726
  function flattenEntry(obj, parent = "") {
8298
8727
  return Object.keys(obj).reduce((acc, key) => {
8728
+ if (isPrototypePollutingKey(key)) {
8729
+ console.warn(`Skipping column "${key}": a header may not reach the prototype chain`);
8730
+ return acc;
8731
+ }
8299
8732
  const prefixedKey = parent ? `${parent}.${key}` : key;
8300
8733
  if (typeof obj[key] === "object" && !(obj[key] instanceof Date) && obj[key] !== null && !Array.isArray(obj[key])) Object.assign(acc, flattenEntry(obj[key], prefixedKey));
8301
8734
  else acc[prefixedKey] = obj[key];
@@ -8370,6 +8803,7 @@ function processValueMapping(authController, value, navigation, property) {
8370
8803
  //#endregion
8371
8804
  //#region src/data_import/components/DataNewPropertiesMapping.tsx
8372
8805
  function DataNewPropertiesMapping({ importConfig, destinationProperties, buildPropertyView }) {
8806
+ const { t } = useTranslation();
8373
8807
  const headersMapping = importConfig.headersMapping;
8374
8808
  const headingsOrder = importConfig.headingsOrder;
8375
8809
  const idColumn = importConfig.idColumn;
@@ -8449,7 +8883,7 @@ function DataNewPropertiesMapping({ importConfig, destinationProperties, buildPr
8449
8883
  /* @__PURE__ */ jsx(TableCell, {
8450
8884
  header: true,
8451
8885
  style: { width: "65%" },
8452
- children: "Default value"
8886
+ children: t("default_value")
8453
8887
  })
8454
8888
  ] }), /* @__PURE__ */ jsx(TableBody, { children: destinationProperties && getAllPropertyKeys(destinationProperties).map((key) => {
8455
8889
  const property = getPropertyInPath$1(destinationProperties, key);
@@ -8529,17 +8963,22 @@ function IdSelectField({ idColumn, headersMapping, onChange }) {
8529
8963
  }) });
8530
8964
  }
8531
8965
  function DefaultValuesField({ property, onValueChange, defaultValue }) {
8966
+ const { t } = useTranslation();
8967
+ const defaultValueLabel = t("default_value");
8968
+ const ariaLabel = property.name ? `${defaultValueLabel}: ${property.name}` : defaultValueLabel;
8532
8969
  if (property.type === "string") return /* @__PURE__ */ jsx(TextField, {
8533
8970
  size: "medium",
8534
- placeholder: "Default value",
8971
+ "aria-label": ariaLabel,
8972
+ placeholder: defaultValueLabel,
8535
8973
  value: defaultValue ?? "",
8536
8974
  onChange: (event) => onValueChange(event.target.value)
8537
8975
  });
8538
8976
  else if (property.type === "number") return /* @__PURE__ */ jsx(TextField, {
8539
8977
  size: "medium",
8540
8978
  type: "number",
8979
+ "aria-label": ariaLabel,
8541
8980
  value: defaultValue ?? "",
8542
- placeholder: "Default value",
8981
+ placeholder: defaultValueLabel,
8543
8982
  onChange: (event) => onValueChange(event.target.value)
8544
8983
  });
8545
8984
  else if (property.type === "boolean") return /* @__PURE__ */ jsx(BooleanSwitchWithLabel, {
@@ -8576,6 +9015,7 @@ function ImportNewPropertyFieldPreview({ propertyKey, property, onEditClick, inc
8576
9015
  className: "flex flex-row items-center gap-2",
8577
9016
  children: [includeName && /* @__PURE__ */ jsx(TextField, {
8578
9017
  size: "medium",
9018
+ "aria-label": propertyKey ? `Name of property ${propertyKey}` : "Property name",
8579
9019
  className: "text-base grow",
8580
9020
  value: property?.name ?? "",
8581
9021
  onChange: (e) => {
@@ -8878,8 +9318,8 @@ function EditorCollectionAction({ path, parentCollectionSlugs, parentEntityIds,
8878
9318
  }
8879
9319
  //#endregion
8880
9320
  //#region src/components/CollectionViewBinding/CollectionViewActions.tsx
8881
- var ImportCollectionAction = lazy(() => import("./import-Dhx96B6x.js").then((n) => n.t).then((m) => ({ default: m.ImportCollectionAction })));
8882
- var ExportCollectionAction = lazy(() => import("./export-C26xQN-g.js").then((n) => n.t).then((m) => ({ default: m.ExportCollectionAction })));
9321
+ var ImportCollectionAction = lazy(() => import("./import-C_4edkoD.js").then((n) => n.t).then((m) => ({ default: m.ImportCollectionAction })));
9322
+ var ExportCollectionAction = lazy(() => import("./export-L53WektO.js").then((n) => n.t).then((m) => ({ default: m.ExportCollectionAction })));
8883
9323
  function CollectionViewActions({ collection, relativePath, parentCollectionSlugs, parentEntityIds, onNewClick, onAddExistingClick, onMultipleDeleteClick, selectionEnabled, path, selectionController, tableController, collectionEntitiesCount, compact, children, openNewDocument }) {
8884
9324
  const context = useAdminContext();
8885
9325
  const { canCreate, canDelete } = usePermissions();
@@ -11204,7 +11644,7 @@ function JsonPreviewBinding({ values }) {
11204
11644
  }
11205
11645
  //#endregion
11206
11646
  //#region src/components/EntityInspector.tsx
11207
- var EntityHistoryView = lazy(() => import("./history-DSS-Ebwh.js").then((m) => ({ default: m.EntityHistoryView })));
11647
+ var EntityHistoryView = lazy(() => import("./history-D5SKygpJ.js").then((m) => ({ default: m.EntityHistoryView })));
11208
11648
  /**
11209
11649
  * Raw values and revision history, as an inspector rather than as tabs.
11210
11650
  *
@@ -12326,6 +12766,7 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
12326
12766
  }, []);
12327
12767
  const subscribeToColumn = useCallback((column, itemCount) => {
12328
12768
  if (isCleaningUpRef.current) return;
12769
+ const requestedCount = Math.min(itemCount, MAX_LIST_LIMIT);
12329
12770
  const currentDataClient = dataClientRef.current;
12330
12771
  const currentCollection = collectionRef.current;
12331
12772
  const currentContext = contextRef.current;
@@ -12455,7 +12896,7 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
12455
12896
  const includeParams = getRelationIncludeParams(currentCollection);
12456
12897
  const fetchOnce = () => accessor.find({
12457
12898
  where: whereFilter,
12458
- limit: itemCount,
12899
+ limit: requestedCount,
12459
12900
  orderBy: orderByParam,
12460
12901
  include: includeParams,
12461
12902
  searchString: currentSearchString
@@ -12481,7 +12922,7 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
12481
12922
  let liveDataReceived = false;
12482
12923
  const unsubscribe = accessor.listen({
12483
12924
  where: whereFilter,
12484
- limit: itemCount,
12925
+ limit: requestedCount,
12485
12926
  orderBy: orderByParam,
12486
12927
  include: includeParams,
12487
12928
  searchString: currentSearchString
@@ -13064,7 +13505,7 @@ function CollectionBoardViewBinding({ collection, tableController, fullPath, par
13064
13505
  analyticsController.onAnalyticsEvent?.("kanban_backfill_order", { path: fullPath });
13065
13506
  setBackfillLoading(true);
13066
13507
  try {
13067
- const entitiesToUpdate = (await dataClient.collection(fullPath).find({ limit: 1e4 })).data.filter((entity) => !isValidOrderKey(entity.values?.[orderProperty]));
13508
+ const entitiesToUpdate = (await dataClient.collection(fullPath).find({ limit: MAX_LIST_LIMIT })).data.filter((entity) => !isValidOrderKey(entity.values?.[orderProperty]));
13068
13509
  const keys = generateNKeysBetween(null, null, entitiesToUpdate.length, ORDER_KEY_DIGITS);
13069
13510
  const updates = [];
13070
13511
  entitiesToUpdate.forEach((entity, index) => {
@@ -13304,10 +13745,10 @@ var ALL_VIEW_MODES = [
13304
13745
  function ViewModeToggle({ viewMode = "table", onViewModeChange, enabledViews = ALL_VIEW_MODES, size, onSizeChanged, open, onOpenChange, kanbanPropertyOptions, selectedKanbanProperty, onKanbanPropertyChange }) {
13305
13746
  const { t } = useTranslation();
13306
13747
  const getViewModeIcon = () => {
13307
- if (viewMode === "kanban") return /* @__PURE__ */ jsx(KanbanIcon, { size: iconSize.smallest });
13308
- if (viewMode === "cards") return /* @__PURE__ */ jsx(LayoutGridIcon, { size: iconSize.smallest });
13309
- if (viewMode === "table") return /* @__PURE__ */ jsx(TableIcon, { size: iconSize.smallest });
13310
- return /* @__PURE__ */ jsx(ListIcon, { size: iconSize.smallest });
13748
+ if (viewMode === "kanban") return /* @__PURE__ */ jsx(KanbanIcon, { size: iconSize.small });
13749
+ if (viewMode === "cards") return /* @__PURE__ */ jsx(LayoutGridIcon, { size: iconSize.small });
13750
+ if (viewMode === "table") return /* @__PURE__ */ jsx(TableIcon, { size: iconSize.small });
13751
+ return /* @__PURE__ */ jsx(ListIcon, { size: iconSize.small });
13311
13752
  };
13312
13753
  const getViewModeName = () => {
13313
13754
  if (viewMode === "kanban") return t("board");
@@ -14346,7 +14787,7 @@ function PopupFormFieldInternal({ tableKey, entityId, customFieldValidator, prop
14346
14787
  }
14347
14788
  //#endregion
14348
14789
  //#region src/components/ClearFilterSortButton.tsx
14349
- function ClearFilterSortButton({ tableController, enabled }) {
14790
+ function ClearFilterSortButton({ tableController, enabled, compact }) {
14350
14791
  const { t } = useTranslation();
14351
14792
  if (!enabled) return null;
14352
14793
  const filterIsSet = !!tableController.filterValues && Object.keys(tableController.filterValues).length > 0;
@@ -14358,15 +14799,14 @@ function ClearFilterSortButton({ tableController, enabled }) {
14358
14799
  else label = t("clear_sort");
14359
14800
  return /* @__PURE__ */ jsx(Tooltip, {
14360
14801
  title: label,
14361
- children: /* @__PURE__ */ jsx(Button, {
14802
+ children: /* @__PURE__ */ jsx(IconButton, {
14362
14803
  size: "small",
14363
- variant: "text",
14364
14804
  "aria-label": label,
14365
14805
  onClick: () => {
14366
14806
  tableController.clearFilter?.();
14367
14807
  tableController.setSortBy?.(void 0);
14368
14808
  },
14369
- children: /* @__PURE__ */ jsx(FilterXIcon, { size: iconSize.small })
14809
+ children: /* @__PURE__ */ jsx(FilterXIcon, { size: compact ? iconSize.smallest : iconSize.small })
14370
14810
  })
14371
14811
  });
14372
14812
  }
@@ -14724,7 +15164,7 @@ function FilterPresetsButton({ filterPresets, tableController, compact }) {
14724
15164
  * is one click rather than a trip through a direction selector; clicking a
14725
15165
  * different one starts it ascending. The popover stays open through both.
14726
15166
  */
14727
- function SortButton({ tableController, properties }) {
15167
+ function SortButton({ tableController, properties, compact }) {
14728
15168
  const { t } = useTranslation();
14729
15169
  const [open, setOpen] = useState(false);
14730
15170
  const options = useMemo(() => getSortablePropertyOptions(properties), [properties]);
@@ -14750,6 +15190,7 @@ function SortButton({ tableController, properties }) {
14750
15190
  if (!setSortBy || options.length === 0) return null;
14751
15191
  const activeOption = options.find((option) => option.key === activeKey);
14752
15192
  const label = activeOption ? `${t("sort_by")}: ${activeOption.title}` : t("sort");
15193
+ const arrowSize = compact ? iconSize.smallest : iconSize.small;
14753
15194
  return /* @__PURE__ */ jsx(Popover, {
14754
15195
  open,
14755
15196
  onOpenChange: setOpen,
@@ -14761,7 +15202,7 @@ function SortButton({ tableController, properties }) {
14761
15202
  "aria-label": label,
14762
15203
  title: label,
14763
15204
  className: cls(activeOption && "text-primary"),
14764
- children: activeDirection === "desc" ? /* @__PURE__ */ jsx(ArrowDownIcon, { size: iconSize.smallest }) : activeDirection === "asc" ? /* @__PURE__ */ jsx(ArrowUpIcon, { size: iconSize.smallest }) : /* @__PURE__ */ jsx(ArrowUpDownIcon, { size: iconSize.smallest })
15205
+ children: activeDirection === "desc" ? /* @__PURE__ */ jsx(ArrowDownIcon, { size: arrowSize }) : activeDirection === "asc" ? /* @__PURE__ */ jsx(ArrowUpIcon, { size: arrowSize }) : /* @__PURE__ */ jsx(ArrowUpDownIcon, { size: arrowSize })
14765
15206
  }),
14766
15207
  children: /* @__PURE__ */ jsxs("div", {
14767
15208
  className: "py-2 min-w-[240px] flex flex-col",
@@ -14858,6 +15299,7 @@ function CollectionViewStartActions({ collection, relativePath, parentCollection
14858
15299
  children: /* @__PURE__ */ jsx(XIcon, { size: iconSize.smallest })
14859
15300
  })
14860
15301
  }, "close_tooltip");
15302
+ const iconOnlyToolbar = !largeLayout || Boolean(compact);
14861
15303
  const filtersButton = resolvedProperties && tableController.setFilterValues && /* @__PURE__ */ jsx(Tooltip, {
14862
15304
  title: t("filters"),
14863
15305
  children: /* @__PURE__ */ jsx(Badge, {
@@ -14880,7 +15322,8 @@ function CollectionViewStartActions({ collection, relativePath, parentCollection
14880
15322
  }, "filters_tooltip");
14881
15323
  const sortButton = resolvedProperties && (viewMode === "list" || viewMode === "cards") && /* @__PURE__ */ jsx(SortButton, {
14882
15324
  tableController,
14883
- properties: resolvedProperties
15325
+ properties: resolvedProperties,
15326
+ compact: iconOnlyToolbar
14884
15327
  }, "sort_button");
14885
15328
  const filterPresetsButton = !compact && collection.filterPresets?.length ? /* @__PURE__ */ jsx(FilterPresetsButton, {
14886
15329
  filterPresets: collection.filterPresets,
@@ -14894,6 +15337,7 @@ function CollectionViewStartActions({ collection, relativePath, parentCollection
14894
15337
  sortButton,
14895
15338
  /* @__PURE__ */ jsx(ClearFilterSortButton, {
14896
15339
  tableController,
15340
+ compact: iconOnlyToolbar,
14897
15341
  enabled: !collection.fixedFilter
14898
15342
  }, "clear_filter"),
14899
15343
  filterPresetsButton
@@ -15909,6 +16353,7 @@ function EntityIdHeaderWidget({ collection, path, idPath, openEntityMode }) {
15909
16353
  className: "flex p-2 w-full gap-2",
15910
16354
  children: [/* @__PURE__ */ jsx(TextField, {
15911
16355
  autoFocus: openPopup,
16356
+ "aria-label": t("find_entity_by_id"),
15912
16357
  placeholder: t("find_entity_by_id"),
15913
16358
  size: "small",
15914
16359
  onChange: (e) => {
@@ -19199,7 +19644,8 @@ function DateTimeFieldBinding({ propertyKey, value, setValue, autoFocus, error,
19199
19644
  required: property.validation?.required,
19200
19645
  className: showError ? "text-red-500 dark:text-red-500" : "text-text-secondary dark:text-text-secondary-dark",
19201
19646
  title: property.name ?? propertyKey
19202
- })
19647
+ }),
19648
+ "aria-label": hideLabel ? property.name ?? propertyKey : void 0
19203
19649
  })
19204
19650
  }), /* @__PURE__ */ jsx(FieldHelperText, {
19205
19651
  includeDescription,
@@ -19356,6 +19802,7 @@ function MapKeyValueRow({ id, fieldKey, value, onFieldKeyChange, onDeleteClick,
19356
19802
  const { t } = useTranslation();
19357
19803
  function buildInput(entryValue, fieldKey, type) {
19358
19804
  if (type === "string" || type === "number") return /* @__PURE__ */ jsx(TextField, {
19805
+ "aria-label": fieldKey ? `${t("value")}: ${fieldKey}` : t("value"),
19359
19806
  placeholder: "value",
19360
19807
  value: entryValue,
19361
19808
  type: type === "number" ? "number" : "text",
@@ -19471,6 +19918,7 @@ function MapKeyValueRow({ id, fieldKey, value, onFieldKeyChange, onDeleteClick,
19471
19918
  className: "w-[300px] max-w-[30%]",
19472
19919
  children: /* @__PURE__ */ jsx(TextField, {
19473
19920
  value: fieldKey,
19921
+ "aria-label": t("key"),
19474
19922
  placeholder: "key",
19475
19923
  disabled: disabled || entryValue !== void 0 && entryValue !== null && entryValue !== "",
19476
19924
  size: "medium",
@@ -19541,6 +19989,7 @@ function ArrayKeyValueRow({ id, index, value, setValue }) {
19541
19989
  function buildInput(entryValue, type) {
19542
19990
  if (type === "string" || type === "number") return /* @__PURE__ */ jsx(TextField, {
19543
19991
  value: entryValue,
19992
+ "aria-label": `Value ${index + 1}`,
19544
19993
  type: type === "number" ? "number" : "text",
19545
19994
  size: "medium",
19546
19995
  onChange: (event) => {
@@ -20590,6 +21039,7 @@ function TextFieldBinding({ propertyKey, value, setValue, error, showError, disa
20590
21039
  required: property.validation?.required || property.isId === true,
20591
21040
  title: property.name ?? propertyKey
20592
21041
  });
21042
+ const accessibleName = property.name ?? propertyKey;
20593
21043
  return /* @__PURE__ */ jsxs(Fragment, { children: [
20594
21044
  /* @__PURE__ */ jsx(PropertyIdCopyTooltip, {
20595
21045
  propertyKey,
@@ -20605,6 +21055,7 @@ function TextFieldBinding({ propertyKey, value, setValue, error, showError, disa
20605
21055
  onChange,
20606
21056
  autoFocus,
20607
21057
  disabled,
21058
+ "aria-label": accessibleName,
20608
21059
  className: cls("rounded-md resize-none w-full outline-none text-base bg-transparent min-h-[64px] px-3", label ? "pt-8 pb-2" : "py-2", disabled && "outline-none opacity-50 text-surface-accent-600 dark:text-surface-accent-500", showError && error ? "text-red-500 dark:text-red-600" : "")
20609
21060
  }),
20610
21061
  property.admin?.clearable && /* @__PURE__ */ jsx("div", {
@@ -20621,6 +21072,7 @@ function TextFieldBinding({ propertyKey, value, setValue, error, showError, disa
20621
21072
  onChange,
20622
21073
  autoFocus,
20623
21074
  label,
21075
+ "aria-label": label ? void 0 : accessibleName,
20624
21076
  type: inputType,
20625
21077
  disabled,
20626
21078
  endAdornment: property.admin?.clearable && /* @__PURE__ */ jsx(IconButton, {
@@ -20756,6 +21208,7 @@ function VectorFieldBinding({ propertyKey, value, setValue, error, showError, di
20756
21208
  className: cls("flex flex-col gap-2 p-4 rounded-xl border bg-surface-50/20 dark:bg-surface-800/10", defaultBorderMixin),
20757
21209
  children: [/* @__PURE__ */ jsx(TextField, {
20758
21210
  size,
21211
+ "aria-label": `${property.name ?? propertyKey} vector values`,
20759
21212
  value: textValue,
20760
21213
  onChange,
20761
21214
  autoFocus: true,
@@ -21039,6 +21492,7 @@ function BinaryFieldBinding({ propertyKey, value, setValue, error, showError, di
21039
21492
  className: cls("flex flex-col gap-2 p-4 rounded-xl border", defaultBorderMixin),
21040
21493
  children: [/* @__PURE__ */ jsx(TextField, {
21041
21494
  size,
21495
+ "aria-label": `${property.name ?? propertyKey} base64 value`,
21042
21496
  value: text,
21043
21497
  onChange,
21044
21498
  autoFocus: autoFocus ?? true,
@@ -23985,6 +24439,6 @@ function getFullIdPath(propertyKey, propertyNamespace) {
23985
24439
  return idToPropertiesPath(propertyNamespace ? `${propertyNamespace}.${propertyKey}` : propertyKey);
23986
24440
  }
23987
24441
  //#endregion
23988
- export { getEntityViewWidth as $, UrlComponentPreview as $n, CollectionTableBinding as $t, SelectFieldBinding as A, ArrayOfReferencesPreview as An, convertFileToJson as At, useSelectionDialog as B, getEntityPreviewKeys as Bn, mergeEntityActions as Bt, getDefaultFieldId as C, MapPropertyPreview as Cn, convertDataToEntity as Ct, TextFieldBinding as D, ArrayEnumPreview as Dn, useImportConfig as Dt, VectorFieldBinding as E, ArrayPropertyEnumPreview as En, getInferenceType as Et, MapFieldBinding as F, useSidePanel as Fn, FieldCaption as Ft, useTopLevelNavigation as G, EnumValuesChip as Gn, removeInitialAndTrailingSlashes$1 as Gt, SideDialogs as H, getEntityTitlePropertyKeyForEntity as Hn, getCollectionBySlugWithin as Ht, KeyValueFieldBinding as I, CollectionRegistryContext as In, useBreadcrumbsController as It, useBuildCollectionRegistryController as J, SkeletonPropertyComponent as Jn, resolveCollectionPathIds$1 as Jt, useResolvedViews as K, StorageThumbnail as Kn, removeInitialSlash as Kt, DateTimeFieldBinding as L, useCollectionRegistryController as Ln, BreadcrumbsProvider as Lt, ReferenceFieldBinding as M, ReferencePreview as Mn, ArrayContainer as Mt, MultiSelectFieldBinding as N, EntityPreviewBinding as Nn, PropertyConfigBadge as Nt, SwitchFieldBinding as O, ArrayOfStorageComponentsPreview as On, ImportSaveInProgress as Ot, MarkdownEditorFieldBinding as P, SidePanelControllerContext as Pn, SearchIconsView as Pt, buildSidePanelsFromUrl as Q, renderSkeletonText as Qn, VirtualTableInput$1 as Qt, BlockFieldBinding as R, getUserLabel as Rn, resolveEntityAction as Rt, getDefaultFieldConfig as S, KeyValuePreview as Sn, DataNewPropertiesMapping as St, getFieldId as T, ArrayOfStringsPreview as Tn, processValueMapping as Tt, useBuildUrlController as U, ArrayPropertyPreview as Un, getCollectionPathsCombinations as Ut, SelectionTableBinding as V, getEntityTitlePropertyKey as Vn, addInitialSlash as Vt, useBuildNavigationStateController as W, StringPropertyPreview as Wn, getLastSegment$1 as Wt, resolveNavigationFrom as X, renderSkeletonIcon as Xn, resolveViewMode as Xt, useHistory as Y, renderSkeletonCaptionText as Yn, resolveOpenEntityMode as Yt, useResolvedNavigationFrom as Z, renderSkeletonImageThumbnail as Zn, useSelectionController as Zt, useCollectionsConfigController as _, PropertyPreview as _n, getResolvedPropertyInPath as _r, CollectionViewActions as _t, namespaceToPropertiesPath as a, NavigationStateContext as an, RecordMeta as ar, removeEmptyContainers as at, PropertyFieldBinding as b, BooleanPreview as bn, useCollectionEditorDialogsState as bt, buildCollectionGenerationCallback as c, SideDialogsControllerContext as cn, isSelfLabellingProperty as cr, copyEntityAction as ct, fromSerializableCollectionConfigs as d, useClearRestoreValue as dn, getBracketNotation as dr, resetPasswordAction as dt, SelectableTable as en, ImagePreview as er, useBuildSidePanel as et, fromSerializableProperties as f, ReadOnlyFieldBinding as fn, getDefaultPropertiesOrder as fr, CreationResultDialog as ft, toSerializableProperty as g, ArrayOfMapsPreview as gn, getPropertyInPath$1 as gr, EntityCardBinding as gt, toSerializableProperties as h, FieldHelperText as hn, getPropertiesWithPropertiesOrder as hr, CollectionCardViewBinding as ht, namespaceToPropertiesOrderPath as i, useUrlController as in, FormRail as ir, getInitialEntityValues as it, RepeatFieldBinding as j, InlineEntityListPreview as jn, unflattenObject as jt, StorageUploadFieldBinding as k, RelationPreview as kn, ImportFileUpload as kt, validateCollectionJson as l, SelectableTableContext as ln, spanClass as lr, deleteEntityAction as lt, toSerializableCollectionConfig as m, LabelWithIcon as mn, getIconForWidget as mr, EntityViewBinding as mt, getFullIdPath as n, useAdminContext as nn, EmptyValue as nr, extractTouchedValues as nt, CollectionGenerationApiError as o, useNavigationStateController as on, FieldBlock as or, zodToFormErrors as ot, fromSerializableProperty as p, LabelWithIconAndTooltip as pn, getIconForProperty as pr, DetailViewBinding as pt, useResolvedCollections as q, StorageThumbnailInternal as qn, removeTrailingSlash$1 as qt, idToPropertiesPath as r, UrlContext as rn, FormSections as rr, getChanges as rt, DEFAULT_COLLECTION_GENERATION_ENDPOINT as s, useSideDialogsController as sn, LABEL_ICON_SIZE as sr, CollectionViewBinding as st, getFullId as t, CollectionRowActions as tn, sanitizeUrl as tr, EditViewBinding as tt, fromSerializableCollectionConfig as u, ArrayCustomShapedFieldBinding as un, PropertyIdCopyTooltip as ur, editEntityAction as ut, EntityFormBinding as v, UserPreview as vn, isReferenceProperty as vr, useCollectionEditorController as vt, getFieldConfig as w, ArrayOneOfPreview as wn, flattenEntry as wt, DEFAULT_FIELD_CONFIGS as x, DatePreview as xn, ImportNewPropertyFieldPreview as xt, EntityForm as y, NumberPropertyPreview as yn, isRelationProperty as yr, ConfigControllerProvider as yt, ArrayOfReferencesFieldBinding as z, useResolvedUser as zn, resolveEntityView as zt };
24442
+ export { getEntityViewWidth as $, SkeletonPropertyComponent as $n, resolveCollectionPathIds$1 as $t, SelectFieldBinding as A, ArrayOfStringsPreview as An, saveImportedEntities as At, useSelectionDialog as B, useSidePanel as Bn, FieldCaption as Bt, getDefaultFieldId as C, UserPreview as Cn, isReferenceProperty as Cr, convertDataToEntity as Ct, TextFieldBinding as D, KeyValuePreview as Dn, useImportConfig as Dt, VectorFieldBinding as E, DatePreview as En, getInferenceType as Et, MapFieldBinding as F, ArrayOfReferencesPreview as Fn, parseCsvToObjects as Ft, useTopLevelNavigation as G, getEntityPreviewKeys as Gn, mergeEntityActions as Gt, SideDialogs as H, useCollectionRegistryController as Hn, BreadcrumbsProvider as Ht, KeyValueFieldBinding as I, InlineEntityListPreview as In, unflattenObject as It, useBuildCollectionRegistryController as J, ArrayPropertyPreview as Jn, getCollectionPathsCombinations as Jt, useResolvedViews as K, getEntityTitlePropertyKey as Kn, addInitialSlash as Kt, DateTimeFieldBinding as L, ReferencePreview as Ln, ArrayContainer as Lt, ReferenceFieldBinding as M, ArrayEnumPreview as Mn, convertFileToJson as Mt, MultiSelectFieldBinding as N, ArrayOfStorageComponentsPreview as Nn, detectCsvDelimiter as Nt, SwitchFieldBinding as O, MapPropertyPreview as On, ImportSaveInProgress as Ot, MarkdownEditorFieldBinding as P, RelationPreview as Pn, parseCsvRows as Pt, buildSidePanelsFromUrl as Q, StorageThumbnailInternal as Qn, removeTrailingSlash$1 as Qt, BlockFieldBinding as R, EntityPreviewBinding as Rn, PropertyConfigBadge as Rt, getDefaultFieldConfig as S, PropertyPreview as Sn, getResolvedPropertyInPath as Sr, DataNewPropertiesMapping as St, getFieldId as T, BooleanPreview as Tn, processValueMapping as Tt, useBuildUrlController as U, getUserLabel as Un, resolveEntityAction as Ut, SelectionTableBinding as V, CollectionRegistryContext as Vn, useBreadcrumbsController as Vt, useBuildNavigationStateController as W, useResolvedUser as Wn, resolveEntityView as Wt, resolveNavigationFrom as X, EnumValuesChip as Xn, removeInitialAndTrailingSlashes$1 as Xt, useHistory as Y, StringPropertyPreview as Yn, getLastSegment$1 as Yt, useResolvedNavigationFrom as Z, StorageThumbnail as Zn, removeInitialSlash as Zt, useCollectionsConfigController as _, ReadOnlyFieldBinding as _n, getDefaultPropertiesOrder as _r, CollectionViewActions as _t, namespaceToPropertiesPath as a, SelectableTable as an, ImagePreview as ar, removeEmptyContainers as at, PropertyFieldBinding as b, FieldHelperText as bn, getPropertiesWithPropertiesOrder as br, useCollectionEditorDialogsState as bt, buildCollectionGenerationCallback as c, UrlContext as cn, FormSections as cr, copyEntityAction as ct, fromSerializableCollectionConfigs as d, useNavigationStateController as dn, FieldBlock as dr, resetPasswordAction as dt, resolveOpenEntityMode as en, renderSkeletonCaptionText as er, useBuildSidePanel as et, fromSerializableProperties as f, useSideDialogsController as fn, LABEL_ICON_SIZE as fr, CreationResultDialog as ft, toSerializableProperty as g, useClearRestoreValue as gn, getBracketNotation as gr, EntityCardBinding as gt, toSerializableProperties as h, ArrayCustomShapedFieldBinding as hn, PropertyIdCopyTooltip as hr, CollectionCardViewBinding as ht, namespaceToPropertiesOrderPath as i, CollectionTableBinding as in, UrlComponentPreview as ir, getInitialEntityValues as it, RepeatFieldBinding as j, ArrayPropertyEnumPreview as jn, ImportFileUpload as jt, StorageUploadFieldBinding as k, ArrayOneOfPreview as kn, IMPORT_BATCH_SIZE as kt, validateCollectionJson as l, useUrlController as ln, FormRail as lr, deleteEntityAction as lt, toSerializableCollectionConfig as m, SelectableTableContext as mn, spanClass as mr, EntityViewBinding as mt, getFullIdPath as n, useSelectionController as nn, renderSkeletonImageThumbnail as nr, extractTouchedValues as nt, CollectionGenerationApiError as o, CollectionRowActions as on, sanitizeUrl as or, zodToFormErrors as ot, fromSerializableProperty as p, SideDialogsControllerContext as pn, isSelfLabellingProperty as pr, DetailViewBinding as pt, useResolvedCollections as q, getEntityTitlePropertyKeyForEntity as qn, getCollectionBySlugWithin as qt, idToPropertiesPath as r, VirtualTableInput$1 as rn, renderSkeletonText as rr, getChanges as rt, DEFAULT_COLLECTION_GENERATION_ENDPOINT as s, useAdminContext as sn, EmptyValue as sr, CollectionViewBinding as st, getFullId as t, resolveViewMode as tn, renderSkeletonIcon as tr, EditViewBinding as tt, fromSerializableCollectionConfig as u, NavigationStateContext as un, RecordMeta as ur, editEntityAction as ut, EntityFormBinding as v, LabelWithIconAndTooltip as vn, getIconForProperty as vr, useCollectionEditorController as vt, getFieldConfig as w, NumberPropertyPreview as wn, isRelationProperty as wr, flattenEntry as wt, DEFAULT_FIELD_CONFIGS as x, ArrayOfMapsPreview as xn, getPropertyInPath$1 as xr, ImportNewPropertyFieldPreview as xt, EntityForm as y, LabelWithIcon as yn, getIconForWidget as yr, ConfigControllerProvider as yt, ArrayOfReferencesFieldBinding as z, SidePanelControllerContext as zn, SearchIconsView as zt };
23989
24443
 
23990
- //# sourceMappingURL=util-Bt6VadeZ.js.map
24444
+ //# sourceMappingURL=util-C-D5yD2n.js.map