@rebasepro/app 0.12.1-canary.gf5f1d39 → 0.13.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 (49) hide show
  1. package/README.md +1 -1
  2. package/dist/auth/api.d.ts +3 -8
  3. package/dist/auth/index.d.ts +2 -2
  4. package/dist/auth/types.d.ts +1 -5
  5. package/dist/collections/form-layout.d.ts +107 -0
  6. package/dist/collections/index.d.ts +1 -0
  7. package/dist/collections/title-property.d.ts +22 -0
  8. package/dist/components/Debug/collection-views/sample_data.d.ts +3 -2
  9. package/dist/hooks/ApiConfigContext.d.ts +36 -2
  10. package/dist/hooks/useBackendStorageSource.d.ts +6 -1
  11. package/dist/hooks/useNavigationBlocker.d.ts +1 -1
  12. package/dist/hooks/useSnackbarController.d.ts +2 -1
  13. package/dist/hooks/useStudioBridge.d.ts +27 -0
  14. package/dist/index.d.ts +1 -1
  15. package/dist/index.es.js +681 -186
  16. package/dist/index.es.js.map +1 -1
  17. package/dist/internal/common.d.ts +14 -2
  18. package/package.json +29 -25
  19. package/src/auth/api.ts +8 -9
  20. package/src/auth/index.ts +2 -2
  21. package/src/auth/types.ts +5 -6
  22. package/src/collections/form-layout.ts +386 -0
  23. package/src/collections/index.ts +1 -0
  24. package/src/collections/title-property.ts +41 -0
  25. package/src/components/Debug/UIReferenceView.tsx +9 -1
  26. package/src/components/Debug/collection-views/sample_data.ts +4 -2
  27. package/src/components/NotFoundPage.tsx +1 -1
  28. package/src/components/common/useDataTableController.tsx +1 -1
  29. package/src/core/Rebase.tsx +5 -1
  30. package/src/core/RebaseRouter.tsx +2 -1
  31. package/src/core/RebaseRoutes.tsx +1 -1
  32. package/src/hooks/ApiConfigContext.tsx +45 -3
  33. package/src/hooks/useAuthSubscription.ts +29 -3
  34. package/src/hooks/useBackendStorageSource.ts +8 -1
  35. package/src/hooks/useNavigationBlocker.tsx +1 -1
  36. package/src/hooks/useSnackbarController.tsx +34 -6
  37. package/src/hooks/useStudioBridge.tsx +50 -2
  38. package/src/index.ts +1 -1
  39. package/src/internal/common.tsx +15 -3
  40. package/src/internal/useRestoreScroll.tsx +1 -1
  41. package/src/locales/de.ts +23 -2
  42. package/src/locales/en.ts +23 -1
  43. package/src/locales/es.ts +23 -1
  44. package/src/locales/fr.ts +23 -1
  45. package/src/locales/hi.ts +23 -1
  46. package/src/locales/it.ts +23 -1
  47. package/src/locales/pt.ts +23 -1
  48. package/src/util/enums.ts +4 -2
  49. package/src/util/useStorageUploadController.tsx +5 -1
package/dist/index.es.js CHANGED
@@ -1,15 +1,16 @@
1
1
  import React, { createContext, lazy, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
2
- import { Alert, AlertCircleIcon, AlertTriangleIcon, AppWindow, ArrowDownIcon, ArrowLeftIcon, ArrowRightLeftIcon, ArrowUpDownIcon, ArrowUpIcon, Avatar, BooleanSwitch, Button, CHIP_COLORS, CalendarIcon, Card, CenteredView, CheckCircle2Icon, CheckIcon, Checkbox, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronsLeftIcon, ChevronsRightIcon, Chip, CircleDotIcon, CircleUserIcon, CircularProgress, ColumnsIcon, Container, Dialog, DialogActions, DialogContent, DialogTitle, DollarSignIcon, ErrorBoundary, FileIcon, FileTextIcon, FilterChip, FilterIcon, FolderIcon, FolderKanbanIcon, IconButton, KanbanIcon, KanbanView, LanguagesIcon, LayoutGridIcon, ListIcon, ListPlusIcon, ListTodoIcon, LoadingButton, LogOutIcon, MailIcon, Menu, MenuItem, MessageCircleIcon, MoonIcon, MultiSelect, MultiSelectItem, PanelLeftIcon, Paper, PenLineIcon, PencilIcon, PhoneIcon, PinIcon, PlusIcon, Popover, RefreshCwIcon, SearchBar, Select, SelectItem, Separator, SettingsIcon, Skeleton, StickyNoteIcon, SunIcon, SunMoonIcon, Tab, Table, TableBody, TableCell, TableHeader, TableRow, Tabs, TagIcon, TextField, ToggleButtonGroup, Tooltip, Trash2Icon, TrendingUpIcon, TypeIcon, Typography, UserIcon, UserPlus, UsersIcon, VideoIcon, Wand2Icon, WrenchIcon, XIcon, cls, colorClassesMapping, coolIconKeys, defaultBorderMixin, getColorSchemeForSeed, iconKeys, iconSize, lucideIcons } from "@rebasepro/ui";
2
+ import { Alert, AlertCircleIcon, AlertTriangleIcon, AppWindow, ArrowDownIcon, ArrowLeftIcon, ArrowRightLeftIcon, ArrowUpDownIcon, ArrowUpIcon, Avatar, BooleanSwitch, Button, CalendarIcon, Card, CenteredView, CheckCircle2Icon, CheckIcon, Checkbox, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronsLeftIcon, ChevronsRightIcon, Chip, CircleDotIcon, CircleUserIcon, CircularProgress, ColumnsIcon, Container, Dialog, DialogActions, DialogContent, DialogTitle, DollarSignIcon, ErrorBoundary, FileIcon, FileTextIcon, FilterChip, FilterIcon, FolderIcon, FolderKanbanIcon, IconButton, KanbanIcon, KanbanView, LanguagesIcon, LayoutGridIcon, ListIcon, ListPlusIcon, ListTodoIcon, LoadingButton, LogOutIcon, MailIcon, Menu, MenuItem, MessageCircleIcon, MoonIcon, MultiSelect, MultiSelectItem, PanelLeftIcon, Paper, PenLineIcon, PencilIcon, PhoneIcon, PinIcon, PlusIcon, Popover, RefreshCwIcon, SearchBar, Select, SelectItem, Separator, SettingsIcon, Skeleton, StickyNoteIcon, SunIcon, SunMoonIcon, Tab, Table, TableBody, TableCell, TableHeader, TableRow, Tabs, TagIcon, TextField, ToggleButtonGroup, Tooltip, Trash2Icon, TrendingUpIcon, TypeIcon, Typography, UserIcon, UserPlus, UsersIcon, VideoIcon, Wand2Icon, WrenchIcon, XIcon, cls, colorClassesMapping, coolIconKeys, defaultBorderMixin, getColorSchemeForKey, getColorSchemeForSeed, iconKeys, iconSize, lucideIcons } from "@rebasepro/ui";
3
3
  import { ALL_WHERE_FILTER_OPS, DEFAULT_DATA_SOURCE_KEY, DEFAULT_FILTERABLE_RELATION_KINDS, DEFAULT_STORAGE_SOURCE_KEY, EntityReference, EntityRelation, GeoPoint, RebaseApiError, Vector, getDataSourceCapabilities, isLazyComponentRef, isRelationalCollectionConfig } from "@rebasepro/types";
4
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
5
  import { SnackbarProvider as SnackbarProvider$1, useSnackbar } from "notistack";
6
6
  import { buildRebaseData, canCreateEntity, canDeleteEntity, canEditEntity, canReadCollection, evaluateCondition, getEntityChildViews, getLabelOrConfigFrom, getPrimaryKeys, getSubcollections, isPropertyBuilder, resolveStorageFilenameString, resolveStoragePathString, resolveStorageSource, stripCollectionPath, wrapAsEntityData, wrapAsSdkData } from "@rebasepro/common";
7
7
  import { getTitlePropertyKey as getTitlePropertyKey$1, removeInitialAndTrailingSlashes as removeInitialAndTrailingSlashes$1 } from "@rebasepro/app";
8
- import { Link, RouterProvider, Routes, createBrowserRouter, useBlocker, useLocation } from "react-router-dom";
8
+ import { Link, Routes, createBrowserRouter, useBlocker, useLocation } from "react-router";
9
9
  import { generateForeignKeyName, hashString, isObject, isPlainObject, mergeDeep, randomString, slugify } from "@rebasepro/utils";
10
10
  import { I18nextProvider, initReactI18next, useTranslation as useTranslation$1 } from "react-i18next";
11
11
  import { deepEqual } from "fast-equals";
12
12
  import i18next from "i18next";
13
+ import { RouterProvider } from "react-router/dom";
13
14
  import Fuse from "fuse.js";
14
15
  import Compressor from "compressorjs";
15
16
  import { resolveAdminCollection } from "@rebasepro/admin-types";
@@ -162,12 +163,15 @@ var DialogsProvider = ({ children }) => {
162
163
  };
163
164
  const close = useCallback(() => {
164
165
  if (dialogEntries.length === 0) return;
165
- updateDialogEntries([...dialogEntriesRef.current.slice(0, -1)]);
166
+ const updatedPanels = [...dialogEntriesRef.current.slice(0, -1)];
167
+ updateDialogEntries(updatedPanels);
166
168
  }, [dialogEntries]);
167
169
  const open = useCallback((dialogEntry) => {
168
- updateDialogEntries([...dialogEntriesRef.current, dialogEntry]);
170
+ const updatedPanels = [...dialogEntriesRef.current, dialogEntry];
171
+ updateDialogEntries(updatedPanels);
169
172
  return { closeDialog: () => {
170
- updateDialogEntries(dialogEntriesRef.current.filter((e) => e.key !== dialogEntry.key));
173
+ const updatedPanels = dialogEntriesRef.current.filter((e) => e.key !== dialogEntry.key);
174
+ updateDialogEntries(updatedPanels);
171
175
  } };
172
176
  }, [dialogEntries]);
173
177
  const dialogsController = useMemo(() => ({
@@ -624,24 +628,37 @@ var useStorageSource = () => {
624
628
  var useSnackbarController = () => {
625
629
  const { enqueueSnackbar, closeSnackbar } = useSnackbar();
626
630
  const open = useCallback((props) => {
627
- const { type, message, autoHideDuration } = props;
631
+ const { type, message, autoHideDuration, action } = props;
628
632
  enqueueSnackbar({
629
633
  message: props.title ? /* @__PURE__ */ jsxs("div", {
630
634
  className: "flex flex-col",
631
635
  children: [/* @__PURE__ */ jsx("strong", { children: props.title }), message]
632
636
  }) : message,
633
637
  variant: type,
634
- autoHideDuration
638
+ autoHideDuration,
639
+ ...action ? { key: nextActionKey() } : {},
640
+ action: action ? (key) => /* @__PURE__ */ jsx(Button, {
641
+ variant: "text",
642
+ size: "small",
643
+ className: "text-inherit",
644
+ onClick: () => {
645
+ action.onClick();
646
+ closeSnackbar(key);
647
+ },
648
+ children: action.label
649
+ }) : void 0
635
650
  });
636
- }, []);
651
+ }, [enqueueSnackbar, closeSnackbar]);
637
652
  const close = useCallback(() => {
638
653
  closeSnackbar();
639
- }, []);
654
+ }, [closeSnackbar]);
640
655
  return useMemo(() => ({
641
656
  open,
642
657
  close
643
658
  }), [open, close]);
644
659
  };
660
+ var actionKeySeq = 0;
661
+ var nextActionKey = () => `rebase-snackbar-action-${actionKeySeq++}`;
645
662
  //#endregion
646
663
  //#region src/hooks/useUserConfigurationPersistence.tsx
647
664
  /**
@@ -908,7 +925,8 @@ function useRelationSelector({ path, collection, fixedFilter, pageSize = DEFAULT
908
925
  setLoading(true);
909
926
  const whereParams = fixedFilter && Object.keys(fixedFilter).length > 0 ? fixedFilter : void 0;
910
927
  const onEntitiesUpdate = (res) => {
911
- setItems(res.data.map((e) => entityToRelationItem(e)));
928
+ const newItems = res.data.map((e) => entityToRelationItem(e));
929
+ setItems(newItems);
912
930
  setHasMore(res.meta.hasMore);
913
931
  setLoading(false);
914
932
  };
@@ -1201,14 +1219,38 @@ function useUnsavedChangesDialog(when, onOk) {
1201
1219
  function useAuthSubscription(authClient) {
1202
1220
  const currentSession = authClient?.getSession();
1203
1221
  const [user, setUser] = useState(currentSession?.user ?? null);
1204
- const [initialLoading, setInitialLoading] = useState(!currentSession);
1205
1222
  const [authLoading, setAuthLoading] = useState(false);
1206
1223
  const [authError, setAuthError] = useState();
1207
1224
  const [loginSkipped, setLoginSkipped] = useState(false);
1208
1225
  const [extra, setExtra] = useState();
1226
+ /**
1227
+ * Whether asking the server "who am I?" could possibly answer.
1228
+ *
1229
+ * A client configured with `persistSession: false` and JSON auth — the
1230
+ * shape used when one client borrows another's credential, as the hosted
1231
+ * Studio does — has no stored session and no auth cookie, so the probe
1232
+ * below is guaranteed to 401. Sending it anyway put three failing
1233
+ * `GET /auth/me` calls on the wire per mount, each one landing in the
1234
+ * *customer's* request log as an authentication failure against their own
1235
+ * backend.
1236
+ *
1237
+ * A client that does not implement the capability is treated as "might
1238
+ * work" — the historical behaviour.
1239
+ */
1240
+ const mayHaveRestorableSession = authClient?.canRestoreSession?.() ?? true;
1241
+ /**
1242
+ * `true` only while a probe is genuinely outstanding.
1243
+ *
1244
+ * It used to be seeded from `!currentSession` alone, which left a client
1245
+ * that cannot restore a session — and therefore never runs the probe that
1246
+ * clears the flag — reporting "still loading" forever. Anything gated on
1247
+ * `initialLoading` (a spinner, a redirect to the login view) would never
1248
+ * resolve.
1249
+ */
1250
+ const [initialLoading, setInitialLoading] = useState(!currentSession && mayHaveRestorableSession);
1209
1251
  useEffect(() => {
1210
1252
  if (!authClient) return;
1211
- if (!currentSession) {
1253
+ if (!currentSession && mayHaveRestorableSession) {
1212
1254
  setInitialLoading(true);
1213
1255
  authClient.getUser().then((user) => {
1214
1256
  if (user) setUser(user);
@@ -1216,7 +1258,11 @@ function useAuthSubscription(authClient) {
1216
1258
  setInitialLoading(false);
1217
1259
  });
1218
1260
  }
1219
- }, [authClient, currentSession]);
1261
+ }, [
1262
+ authClient,
1263
+ currentSession,
1264
+ mayHaveRestorableSession
1265
+ ]);
1220
1266
  useEffect(() => {
1221
1267
  if (!authClient) return;
1222
1268
  return authClient.onAuthStateChange((event, session) => {
@@ -1751,6 +1797,60 @@ function useRebaseRegistryDispatch() {
1751
1797
  return dispatch;
1752
1798
  }
1753
1799
  //#endregion
1800
+ //#region src/hooks/ApiConfigContext.tsx
1801
+ /** What the server uses when `basePath` is not configured. */
1802
+ var DEFAULT_API_PATH = "/api";
1803
+ var ApiConfigContext = React.createContext(void 0);
1804
+ /**
1805
+ * Read the API config from context. Returns `undefined` if no provider is present,
1806
+ * allowing hooks to fall back to their own props.
1807
+ */
1808
+ function useApiConfig() {
1809
+ return useContext(ApiConfigContext);
1810
+ }
1811
+ /**
1812
+ * `apiUrl` and `apiPath` joined, with no trailing slash — the prefix to build a
1813
+ * request URL from.
1814
+ *
1815
+ * Returns `undefined` when there is no provider, so a caller can keep its own
1816
+ * fallback rather than silently requesting a relative path.
1817
+ */
1818
+ function useApiBase() {
1819
+ const config = useApiConfig();
1820
+ if (!config?.apiUrl) return void 0;
1821
+ return config.apiUrl.replace(/\/+$/, "") + (config.apiPath || "/api");
1822
+ }
1823
+ /**
1824
+ * The same prefix, derived from a client rather than from context.
1825
+ *
1826
+ * For the code that holds a `RebaseClient` but sits outside (or above) an
1827
+ * `ApiConfigProvider`. Returns `undefined` when the client has no base URL,
1828
+ * which is the signal every caller already treats as "cannot build a request".
1829
+ */
1830
+ function apiBaseOf(client) {
1831
+ if (!client?.baseUrl) return void 0;
1832
+ return client.baseUrl.replace(/\/+$/, "") + (client.apiPath || "/api");
1833
+ }
1834
+ /**
1835
+ * Provide API configuration (apiUrl, apiPath, getAuthToken) to the entire subtree.
1836
+ * Typically rendered inside `<Rebase>` or at the app root.
1837
+ */
1838
+ function ApiConfigProvider({ apiUrl, apiPath = DEFAULT_API_PATH, getAuthToken, children }) {
1839
+ const value = React.useMemo(() => ({
1840
+ apiUrl,
1841
+ apiPath,
1842
+ getAuthToken
1843
+ }), [
1844
+ apiUrl,
1845
+ apiPath,
1846
+ getAuthToken
1847
+ ]);
1848
+ return /* @__PURE__ */ jsx(ApiConfigContext.Provider, {
1849
+ value,
1850
+ children
1851
+ });
1852
+ }
1853
+ //#endregion
1754
1854
  //#region src/hooks/useBackendStorageSource.ts
1755
1855
  /**
1756
1856
  * React hook for using backend storage API as a StorageSource
@@ -1770,8 +1870,8 @@ function useRebaseRegistryDispatch() {
1770
1870
  * <Rebase storageSource={storageSource} ... />
1771
1871
  * ```
1772
1872
  */
1773
- function useBackendStorageSource({ apiUrl, getAuthToken }) {
1774
- const storageBasePath = `${apiUrl}/api/storage`;
1873
+ function useBackendStorageSource({ apiUrl, apiPath = DEFAULT_API_PATH, getAuthToken }) {
1874
+ const storageBasePath = `${apiUrl.replace(/\/+$/, "")}${apiPath}/storage`;
1775
1875
  const urlsCache = useMemo(() => /* @__PURE__ */ new Map(), []);
1776
1876
  /**
1777
1877
  * Make an authenticated request to the storage API
@@ -1933,30 +2033,6 @@ function usePermissions() {
1933
2033
  ]);
1934
2034
  }
1935
2035
  //#endregion
1936
- //#region src/hooks/ApiConfigContext.tsx
1937
- var ApiConfigContext = React.createContext(void 0);
1938
- /**
1939
- * Read the API config from context. Returns `undefined` if no provider is present,
1940
- * allowing hooks to fall back to their own props.
1941
- */
1942
- function useApiConfig() {
1943
- return useContext(ApiConfigContext);
1944
- }
1945
- /**
1946
- * Provide API configuration (apiUrl, getAuthToken) to the entire subtree.
1947
- * Typically rendered inside `<Rebase>` or at the app root.
1948
- */
1949
- function ApiConfigProvider({ apiUrl, getAuthToken, children }) {
1950
- const value = React.useMemo(() => ({
1951
- apiUrl,
1952
- getAuthToken
1953
- }), [apiUrl, getAuthToken]);
1954
- return /* @__PURE__ */ jsx(ApiConfigContext.Provider, {
1955
- value,
1956
- children
1957
- });
1958
- }
1959
- //#endregion
1960
2036
  //#region src/hooks/useTranslation.ts
1961
2037
  var REBASE_NS$1 = "rebase_core";
1962
2038
  /**
@@ -6318,7 +6394,7 @@ function KanbanBoardDemo() {
6318
6394
  const [columns, setColumns] = useState(STATUS_COLUMNS);
6319
6395
  const assignColumn = useCallback((item) => item.data.status, []);
6320
6396
  const handleItemsReorder = useCallback((items, moveInfo) => {
6321
- setBoardData(items.map((item) => {
6397
+ const updatedItems = items.map((item) => {
6322
6398
  if (moveInfo && item.id === moveInfo.itemId && moveInfo.sourceColumn !== moveInfo.targetColumn) return {
6323
6399
  ...item,
6324
6400
  data: {
@@ -6328,7 +6404,8 @@ function KanbanBoardDemo() {
6328
6404
  }
6329
6405
  };
6330
6406
  return item;
6331
- }));
6407
+ });
6408
+ setBoardData(updatedItems);
6332
6409
  }, []);
6333
6410
  const handleColumnReorder = useCallback((reordered) => setColumns(reordered), []);
6334
6411
  const columnLoadingState = useMemo(() => {
@@ -7563,6 +7640,22 @@ function UIReferenceView() {
7563
7640
  disabled: true,
7564
7641
  value: "Read only",
7565
7642
  onChange: () => {}
7643
+ }),
7644
+ /* @__PURE__ */ jsx(TextField, {
7645
+ size: "medium",
7646
+ label: "Medium",
7647
+ placeholder: "Placeholder under the label"
7648
+ }),
7649
+ /* @__PURE__ */ jsx(TextField, {
7650
+ size: "small",
7651
+ label: "Small",
7652
+ placeholder: "Placeholder under the label"
7653
+ }),
7654
+ /* @__PURE__ */ jsx(TextField, {
7655
+ size: "smallest",
7656
+ label: "Smallest",
7657
+ value: "Filled value",
7658
+ onChange: () => {}
7566
7659
  })
7567
7660
  ]
7568
7661
  })]
@@ -8012,7 +8105,7 @@ function UIReferenceView() {
8012
8105
  /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx("div", {
8013
8106
  className: "flex flex-wrap gap-2",
8014
8107
  children: user.roles.map((role) => /* @__PURE__ */ jsx(Chip, {
8015
- colorScheme: role.isAdmin ? "purpleDark" : "blueDark",
8108
+ colorScheme: role.isAdmin ? "purple" : "blue",
8016
8109
  size: "small",
8017
8110
  children: role.name
8018
8111
  }, role.id))
@@ -8336,8 +8429,10 @@ function UserSettingsView() {
8336
8429
  setLoadingSessions(true);
8337
8430
  setSessionsError(null);
8338
8431
  try {
8339
- if (authController.fetchSessions) setSessions((await authController.fetchSessions() || []).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()));
8340
- else throw new Error("fetchSessions not implemented in this auth controller.");
8432
+ if (authController.fetchSessions) {
8433
+ const sortedSessions = (await authController.fetchSessions() || []).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
8434
+ setSessions(sortedSessions);
8435
+ } else throw new Error("fetchSessions not implemented in this auth controller.");
8341
8436
  } catch (e) {
8342
8437
  setSessionsError(e instanceof Error ? e.message : String(e));
8343
8438
  } finally {
@@ -9690,6 +9785,8 @@ var en = {
9690
9785
  clear_filter: "Clear filter",
9691
9786
  clear_filter_sort: "Clear filter/sort",
9692
9787
  clear_sort: "Clear sort",
9788
+ sort: "Sort",
9789
+ sort_by: "Sort by",
9693
9790
  clear_all: "Clear all",
9694
9791
  no_items: "No items",
9695
9792
  no_entries_found: "No entries found",
@@ -9723,6 +9820,7 @@ var en = {
9723
9820
  log_out: "Log out",
9724
9821
  column_cannot_be_edited: "This column can't be edited directly",
9725
9822
  close: "Close",
9823
+ hide_list: "Hide list",
9726
9824
  unsaved_local_changes: "Unsaved Local changes",
9727
9825
  unsaved_local_changes_description: "This document was edited locally and has unsaved changes. These local changes will be lost if you don't apply them.",
9728
9826
  preview_changes: "Preview Changes",
@@ -10527,7 +10625,22 @@ var en = {
10527
10625
  datetime_automatic_value: "Automatic value",
10528
10626
  markdown_paste_behavior: "Paste behavior",
10529
10627
  markdown_strip_html: "Strip HTML on paste",
10530
- markdown_convert_pasted: "Convert pasted text to markdown"
10628
+ markdown_convert_pasted: "Convert pasted text to markdown",
10629
+ entity_saved: "Saved",
10630
+ more_actions: "More actions",
10631
+ record_history: "History",
10632
+ inspect_record: "Inspect record",
10633
+ inspect: "Inspect",
10634
+ close_inspector: "Close inspector",
10635
+ history_revert: "Revert to this version",
10636
+ history_revert_title: "Revert data to this version?",
10637
+ history_reverted: "Reverted to selected version",
10638
+ history_revert_error: "Error reverting entity",
10639
+ history_revert_dirty: "Please save or discard your changes before reverting",
10640
+ history_empty: "No history available",
10641
+ history_empty_description: "When you save a record, a new version is created and stored in the history.",
10642
+ history_new_entity: "History is only available for existing records",
10643
+ history_no_more: "No more history"
10531
10644
  };
10532
10645
  //#endregion
10533
10646
  //#region src/locales/es.ts
@@ -10571,6 +10684,8 @@ var es = {
10571
10684
  clear_filter: "Borrar filtro",
10572
10685
  clear_filter_sort: "Borrar filtro/orden",
10573
10686
  clear_sort: "Borrar orden",
10687
+ sort: "Ordenar",
10688
+ sort_by: "Ordenar por",
10574
10689
  clear_all: "Borrar todo",
10575
10690
  no_items: "Sin elementos",
10576
10691
  no_entries_found: "No se encontraron entradas",
@@ -10604,6 +10719,7 @@ var es = {
10604
10719
  log_out: "Cerrar sesión",
10605
10720
  column_cannot_be_edited: "Esta columna no se puede editar directamente",
10606
10721
  close: "Cerrar",
10722
+ hide_list: "Ocultar lista",
10607
10723
  unsaved_local_changes: "Cambios locales sin guardar",
10608
10724
  unsaved_local_changes_description: "Este documento ha sido editado localmente y tiene cambios sin guardar. Estos cambios locales se perderán si no los aplicas.",
10609
10725
  preview_changes: "Previsualizar cambios",
@@ -11373,7 +11489,22 @@ var es = {
11373
11489
  datetime_automatic_value: "Valor automático",
11374
11490
  markdown_paste_behavior: "Comportamiento al pegar",
11375
11491
  markdown_strip_html: "Eliminar HTML al pegar",
11376
- markdown_convert_pasted: "Convertir texto pegado a markdown"
11492
+ markdown_convert_pasted: "Convertir texto pegado a markdown",
11493
+ entity_saved: "Guardado",
11494
+ more_actions: "Más acciones",
11495
+ record_history: "Historial",
11496
+ inspect_record: "Inspeccionar registro",
11497
+ inspect: "Inspeccionar",
11498
+ close_inspector: "Cerrar el inspector",
11499
+ history_revert: "Volver a esta versión",
11500
+ history_revert_title: "¿Volver a esta versión de los datos?",
11501
+ history_reverted: "Se volvió a la versión seleccionada",
11502
+ history_revert_error: "Error al revertir el registro",
11503
+ history_revert_dirty: "Guarda o descarta tus cambios antes de revertir",
11504
+ history_empty: "No hay historial disponible",
11505
+ history_empty_description: "Al guardar un registro se crea una nueva versión y se almacena en el historial.",
11506
+ history_new_entity: "El historial solo está disponible para registros existentes",
11507
+ history_no_more: "No hay más historial"
11377
11508
  };
11378
11509
  //#endregion
11379
11510
  //#region src/locales/de.ts
@@ -11409,6 +11540,8 @@ var de = {
11409
11540
  clear_filter: "Filter löschen",
11410
11541
  clear_filter_sort: "Filter/Sortierung löschen",
11411
11542
  clear_sort: "Sortierung löschen",
11543
+ sort: "Sortieren",
11544
+ sort_by: "Sortieren nach",
11412
11545
  clear_all: "Alle löschen",
11413
11546
  no_items: "Keine Elemente",
11414
11547
  no_entries_found: "Keine Einträge gefunden",
@@ -11442,6 +11575,7 @@ var de = {
11442
11575
  log_out: "Abmelden",
11443
11576
  column_cannot_be_edited: "Diese Spalte kann nicht direkt bearbeitet werden",
11444
11577
  close: "Schließen",
11578
+ hide_list: "Liste ausblenden",
11445
11579
  unsaved_local_changes: "Ungespeicherte lokale Änderungen",
11446
11580
  unsaved_local_changes_description: "Dieses Dokument wurde lokal bearbeitet und weist ungespeicherte Änderungen auf. Diese lokalen Änderungen gehen verloren, wenn Sie sie nicht anwenden.",
11447
11581
  preview_changes: "Änderungen in der Vorschau anzeigen",
@@ -12201,7 +12335,22 @@ var de = {
12201
12335
  datetime_automatic_value: "Automatischer Wert",
12202
12336
  markdown_paste_behavior: "Einfügeverhalten",
12203
12337
  markdown_strip_html: "HTML beim Einfügen entfernen",
12204
- markdown_convert_pasted: "Eingefügten Text in Markdown umwandeln"
12338
+ markdown_convert_pasted: "Eingefügten Text in Markdown umwandeln",
12339
+ entity_saved: "Gespeichert",
12340
+ more_actions: "Weitere Aktionen",
12341
+ record_history: "Verlauf",
12342
+ inspect_record: "Datensatz inspizieren",
12343
+ inspect: "Inspizieren",
12344
+ close_inspector: "Inspektor schließen",
12345
+ history_revert: "Auf diese Version zurücksetzen",
12346
+ history_revert_title: "Daten auf diese Version zurücksetzen?",
12347
+ history_reverted: "Auf die gewählte Version zurückgesetzt",
12348
+ history_revert_error: "Fehler beim Zurücksetzen des Datensatzes",
12349
+ history_revert_dirty: "Bitte speichere oder verwirf deine Änderungen vor dem Zurücksetzen",
12350
+ history_empty: "Kein Verlauf vorhanden",
12351
+ history_empty_description: "Beim Speichern eines Datensatzes wird eine neue Version erstellt und im Verlauf abgelegt.",
12352
+ history_new_entity: "Der Verlauf ist nur für vorhandene Datensätze verfügbar",
12353
+ history_no_more: "Kein weiterer Verlauf"
12205
12354
  };
12206
12355
  //#endregion
12207
12356
  //#region src/locales/fr.ts
@@ -12237,6 +12386,8 @@ var fr = {
12237
12386
  clear_filter: "Effacer le filtre",
12238
12387
  clear_filter_sort: "Effacer le filtre/tri",
12239
12388
  clear_sort: "Effacer le tri",
12389
+ sort: "Trier",
12390
+ sort_by: "Trier par",
12240
12391
  clear_all: "Tout effacer",
12241
12392
  no_items: "Aucun élément",
12242
12393
  no_entries_found: "Aucune entrée trouvée",
@@ -12270,6 +12421,7 @@ var fr = {
12270
12421
  log_out: "Se déconnecter",
12271
12422
  column_cannot_be_edited: "Cette colonne ne peut pas être modifiée directement",
12272
12423
  close: "Fermer",
12424
+ hide_list: "Masquer la liste",
12273
12425
  unsaved_local_changes: "Modifications locales non enregistrées",
12274
12426
  unsaved_local_changes_description: "Ce document a été modifié localement et contient des modifications non enregistrées. Ces modifications locales seront perdues si vous ne les appliquez pas.",
12275
12427
  preview_changes: "Aperçu des modifications",
@@ -13029,7 +13181,22 @@ var fr = {
13029
13181
  datetime_automatic_value: "Valeur automatique",
13030
13182
  markdown_paste_behavior: "Comportement au collage",
13031
13183
  markdown_strip_html: "Supprimer le HTML au collage",
13032
- markdown_convert_pasted: "Convertir le texte collé en markdown"
13184
+ markdown_convert_pasted: "Convertir le texte collé en markdown",
13185
+ entity_saved: "Enregistré",
13186
+ more_actions: "Plus d'actions",
13187
+ record_history: "Historique",
13188
+ inspect_record: "Inspecter l'enregistrement",
13189
+ inspect: "Inspecter",
13190
+ close_inspector: "Fermer l'inspecteur",
13191
+ history_revert: "Revenir à cette version",
13192
+ history_revert_title: "Revenir à cette version des données ?",
13193
+ history_reverted: "Retour à la version sélectionnée",
13194
+ history_revert_error: "Erreur lors du retour en arrière",
13195
+ history_revert_dirty: "Enregistrez ou annulez vos modifications avant de revenir en arrière",
13196
+ history_empty: "Aucun historique disponible",
13197
+ history_empty_description: "À chaque enregistrement, une nouvelle version est créée et conservée dans l'historique.",
13198
+ history_new_entity: "L'historique n'est disponible que pour les enregistrements existants",
13199
+ history_no_more: "Plus d'historique"
13033
13200
  };
13034
13201
  //#endregion
13035
13202
  //#region src/locales/it.ts
@@ -13065,6 +13232,8 @@ var it = {
13065
13232
  clear_filter: "Cancella filtro",
13066
13233
  clear_filter_sort: "Cancella filtro/ordinamento",
13067
13234
  clear_sort: "Cancella ordinamento",
13235
+ sort: "Ordina",
13236
+ sort_by: "Ordina per",
13068
13237
  clear_all: "Cancella tutto",
13069
13238
  no_items: "Nessun elemento",
13070
13239
  no_entries_found: "Nessuna voce trovata",
@@ -13098,6 +13267,7 @@ var it = {
13098
13267
  log_out: "Esci",
13099
13268
  column_cannot_be_edited: "Questa colonna non può essere modificata direttamente",
13100
13269
  close: "Chiudi",
13270
+ hide_list: "Nascondi elenco",
13101
13271
  unsaved_local_changes: "Modifiche locali non salvate",
13102
13272
  unsaved_local_changes_description: "Questo documento è stato modificato localmente e ha modifiche non salvate. Queste modifiche locali andranno perse se non le applichi.",
13103
13273
  preview_changes: "Anteprima modifiche",
@@ -13857,7 +14027,22 @@ var it = {
13857
14027
  datetime_automatic_value: "Valore automatico",
13858
14028
  markdown_paste_behavior: "Comportamento incolla",
13859
14029
  markdown_strip_html: "Rimuovi HTML all'incolla",
13860
- markdown_convert_pasted: "Converti testo incollato in markdown"
14030
+ markdown_convert_pasted: "Converti testo incollato in markdown",
14031
+ entity_saved: "Salvato",
14032
+ more_actions: "Altre azioni",
14033
+ record_history: "Cronologia",
14034
+ inspect_record: "Ispeziona record",
14035
+ inspect: "Ispeziona",
14036
+ close_inspector: "Chiudi l'ispettore",
14037
+ history_revert: "Torna a questa versione",
14038
+ history_revert_title: "Ripristinare i dati a questa versione?",
14039
+ history_reverted: "Ripristinata la versione selezionata",
14040
+ history_revert_error: "Errore durante il ripristino del record",
14041
+ history_revert_dirty: "Salva o annulla le modifiche prima di ripristinare",
14042
+ history_empty: "Nessuna cronologia disponibile",
14043
+ history_empty_description: "Quando salvi un record viene creata una nuova versione e conservata nella cronologia.",
14044
+ history_new_entity: "La cronologia è disponibile solo per i record esistenti",
14045
+ history_no_more: "Nessun'altra cronologia"
13861
14046
  };
13862
14047
  //#endregion
13863
14048
  //#region src/locales/hi.ts
@@ -13893,6 +14078,8 @@ var hi = {
13893
14078
  clear_filter: "फ़िल्टर साफ़ करें",
13894
14079
  clear_filter_sort: "फ़िल्टर/सॉर्ट साफ़ करें",
13895
14080
  clear_sort: "सॉर्ट साफ़ करें",
14081
+ sort: "क्रमबद्ध करें",
14082
+ sort_by: "इसके अनुसार क्रमबद्ध करें",
13896
14083
  clear_all: "सभी साफ़ करें",
13897
14084
  no_items: "कोई आइटम नहीं",
13898
14085
  no_entries_found: "कोई प्रविष्टि नहीं मिली",
@@ -13926,6 +14113,7 @@ var hi = {
13926
14113
  log_out: "लॉग आउट",
13927
14114
  column_cannot_be_edited: "इस कॉलम को सीधे संपादित नहीं किया जा सकता",
13928
14115
  close: "बंद करें",
14116
+ hide_list: "सूची छिपाएँ",
13929
14117
  unsaved_local_changes: "सहेजे नहीं गए स्थानीय परिवर्तन",
13930
14118
  unsaved_local_changes_description: "इस दस्तावेज़ को स्थानीय स्तर पर संपादित किया गया था और इसमें सहेजे नहीं गए परिवर्तन हैं। यदि आप इन्हें लागू नहीं करते हैं तो ये स्थानीय परिवर्तन खो जाएंगे।",
13931
14119
  preview_changes: "परिवर्तनों का पूर्वावलोकन करें",
@@ -14685,7 +14873,22 @@ var hi = {
14685
14873
  datetime_automatic_value: "स्वचालित मान",
14686
14874
  markdown_paste_behavior: "पेस्ट व्यवहार",
14687
14875
  markdown_strip_html: "पेस्ट करते समय HTML हटाएं",
14688
- markdown_convert_pasted: "पेस्ट किए गए टेक्स्ट को मार्कडाउन में बदलें"
14876
+ markdown_convert_pasted: "पेस्ट किए गए टेक्स्ट को मार्कडाउन में बदलें",
14877
+ entity_saved: "सहेजा गया",
14878
+ more_actions: "अधिक क्रियाएँ",
14879
+ record_history: "इतिहास",
14880
+ inspect_record: "रिकॉर्ड जाँचें",
14881
+ inspect: "जाँचें",
14882
+ close_inspector: "इंस्पेक्टर बंद करें",
14883
+ history_revert: "इस संस्करण पर लौटें",
14884
+ history_revert_title: "डेटा को इस संस्करण पर लौटाएँ?",
14885
+ history_reverted: "चयनित संस्करण पर लौटा दिया गया",
14886
+ history_revert_error: "रिकॉर्ड लौटाने में त्रुटि",
14887
+ history_revert_dirty: "लौटाने से पहले अपने बदलाव सहेजें या रद्द करें",
14888
+ history_empty: "कोई इतिहास उपलब्ध नहीं",
14889
+ history_empty_description: "रिकॉर्ड सहेजने पर एक नया संस्करण बनता है और इतिहास में रखा जाता है।",
14890
+ history_new_entity: "इतिहास केवल मौजूदा रिकॉर्ड के लिए उपलब्ध है",
14891
+ history_no_more: "और इतिहास नहीं"
14689
14892
  };
14690
14893
  //#endregion
14691
14894
  //#region src/locales/pt.ts
@@ -14726,6 +14929,8 @@ var pt = {
14726
14929
  clear_filter: "Limpar filtro",
14727
14930
  clear_filter_sort: "Limpar filtro/ordenação",
14728
14931
  clear_sort: "Limpar ordenação",
14932
+ sort: "Ordenar",
14933
+ sort_by: "Ordenar por",
14729
14934
  clear_all: "Limpar tudo",
14730
14935
  no_items: "Sem itens",
14731
14936
  no_entries_found: "Nenhum registo encontrado",
@@ -14759,6 +14964,7 @@ var pt = {
14759
14964
  log_out: "Terminar sessão",
14760
14965
  column_cannot_be_edited: "Esta coluna não pode ser editada diretamente",
14761
14966
  close: "Fechar",
14967
+ hide_list: "Ocultar lista",
14762
14968
  unsaved_local_changes: "Alterações locais não guardadas",
14763
14969
  unsaved_local_changes_description: "Este documento foi editado localmente e tem alterações não guardadas. Estas alterações locais serão perdidas se não as aplicar.",
14764
14970
  preview_changes: "Pré-visualizar alterações",
@@ -15518,7 +15724,22 @@ var pt = {
15518
15724
  datetime_automatic_value: "Valor automático",
15519
15725
  markdown_paste_behavior: "Comportamento ao colar",
15520
15726
  markdown_strip_html: "Remover HTML ao colar",
15521
- markdown_convert_pasted: "Converter texto colado para markdown"
15727
+ markdown_convert_pasted: "Converter texto colado para markdown",
15728
+ entity_saved: "Guardado",
15729
+ more_actions: "Mais ações",
15730
+ record_history: "Histórico",
15731
+ inspect_record: "Inspecionar registo",
15732
+ inspect: "Inspecionar",
15733
+ close_inspector: "Fechar o inspetor",
15734
+ history_revert: "Voltar a esta versão",
15735
+ history_revert_title: "Reverter os dados para esta versão?",
15736
+ history_reverted: "Revertido para a versão selecionada",
15737
+ history_revert_error: "Erro ao reverter o registo",
15738
+ history_revert_dirty: "Guarde ou descarte as suas alterações antes de reverter",
15739
+ history_empty: "Nenhum histórico disponível",
15740
+ history_empty_description: "Ao guardar um registo é criada uma nova versão, guardada no histórico.",
15741
+ history_new_entity: "O histórico só está disponível para registos existentes",
15742
+ history_no_more: "Sem mais histórico"
15522
15743
  };
15523
15744
  //#endregion
15524
15745
  //#region src/i18n/RebaseI18nProvider.tsx
@@ -15852,6 +16073,7 @@ function Rebase(props) {
15852
16073
  const resolvedApiUrl = apiUrl || client?.baseUrl || (typeof window !== "undefined" ? window.location.origin : "");
15853
16074
  if (resolvedApiUrl) return /* @__PURE__ */ jsx(ApiConfigProvider, {
15854
16075
  apiUrl: resolvedApiUrl,
16076
+ apiPath: client?.apiPath,
15855
16077
  getAuthToken: authController.getAuthToken,
15856
16078
  children: content
15857
16079
  });
@@ -15998,7 +16220,8 @@ function useRebaseAuthController(props = {}) {
15998
16220
  try {
15999
16221
  await auth.signOut();
16000
16222
  } catch (error) {
16001
- setAuthProviderError(error instanceof Error ? error : new Error(String(error)));
16223
+ const err = error instanceof Error ? error : new Error(String(error));
16224
+ setAuthProviderError(err);
16002
16225
  } finally {
16003
16226
  setAuthLoading(false);
16004
16227
  }
@@ -16010,7 +16233,8 @@ function useRebaseAuthController(props = {}) {
16010
16233
  try {
16011
16234
  await auth.signInWithEmail(email, password);
16012
16235
  } catch (error) {
16013
- setAuthProviderError(error instanceof Error ? error : new Error(String(error)));
16236
+ const err = error instanceof Error ? error : new Error(String(error));
16237
+ setAuthProviderError(err);
16014
16238
  throw error;
16015
16239
  } finally {
16016
16240
  setAuthLoading(false);
@@ -16023,7 +16247,8 @@ function useRebaseAuthController(props = {}) {
16023
16247
  try {
16024
16248
  await auth.signUp(email, password, displayName);
16025
16249
  } catch (error) {
16026
- setAuthProviderError(error instanceof Error ? error : new Error(String(error)));
16250
+ const err = error instanceof Error ? error : new Error(String(error));
16251
+ setAuthProviderError(err);
16027
16252
  throw error;
16028
16253
  } finally {
16029
16254
  setAuthLoading(false);
@@ -16036,7 +16261,8 @@ function useRebaseAuthController(props = {}) {
16036
16261
  try {
16037
16262
  await auth.signInWithGoogle(payload);
16038
16263
  } catch (error) {
16039
- setAuthProviderError(error instanceof Error ? error : new Error(String(error)));
16264
+ const err = error instanceof Error ? error : new Error(String(error));
16265
+ setAuthProviderError(err);
16040
16266
  throw error;
16041
16267
  } finally {
16042
16268
  setAuthLoading(false);
@@ -16049,7 +16275,8 @@ function useRebaseAuthController(props = {}) {
16049
16275
  try {
16050
16276
  await auth.signInWithOAuth(providerId, payload);
16051
16277
  } catch (error) {
16052
- setAuthProviderError(error instanceof Error ? error : new Error(String(error)));
16278
+ const err = error instanceof Error ? error : new Error(String(error));
16279
+ setAuthProviderError(err);
16053
16280
  throw error;
16054
16281
  } finally {
16055
16282
  setAuthLoading(false);
@@ -16066,7 +16293,8 @@ function useRebaseAuthController(props = {}) {
16066
16293
  try {
16067
16294
  await auth.resetPasswordForEmail(email);
16068
16295
  } catch (error) {
16069
- setAuthProviderError(error instanceof Error ? error : new Error(String(error)));
16296
+ const err = error instanceof Error ? error : new Error(String(error));
16297
+ setAuthProviderError(err);
16070
16298
  throw error;
16071
16299
  } finally {
16072
16300
  setAuthLoading(false);
@@ -16079,7 +16307,8 @@ function useRebaseAuthController(props = {}) {
16079
16307
  try {
16080
16308
  await auth.resetPassword(token, password);
16081
16309
  } catch (error) {
16082
- setAuthProviderError(error instanceof Error ? error : new Error(String(error)));
16310
+ const err = error instanceof Error ? error : new Error(String(error));
16311
+ setAuthProviderError(err);
16083
16312
  throw error;
16084
16313
  } finally {
16085
16314
  setAuthLoading(false);
@@ -16093,7 +16322,8 @@ function useRebaseAuthController(props = {}) {
16093
16322
  await auth.changePassword(oldPassword, newPassword);
16094
16323
  await auth.signOut();
16095
16324
  } catch (error) {
16096
- setAuthProviderError(error instanceof Error ? error : new Error(String(error)));
16325
+ const err = error instanceof Error ? error : new Error(String(error));
16326
+ setAuthProviderError(err);
16097
16327
  throw error;
16098
16328
  } finally {
16099
16329
  setAuthLoading(false);
@@ -16109,7 +16339,8 @@ function useRebaseAuthController(props = {}) {
16109
16339
  photoURL
16110
16340
  });
16111
16341
  } catch (error) {
16112
- setAuthProviderError(error instanceof Error ? error : new Error(String(error)));
16342
+ const err = error instanceof Error ? error : new Error(String(error));
16343
+ setAuthProviderError(err);
16113
16344
  throw error;
16114
16345
  } finally {
16115
16346
  setAuthLoading(false);
@@ -16180,12 +16411,6 @@ function useRebaseAuthController(props = {}) {
16180
16411
  * here is the unauthenticated `/api/auth/config` probe used to detect
16181
16412
  * bootstrap mode and enabled providers before a session exists.
16182
16413
  */
16183
- /**
16184
- * @deprecated Use {@link RebaseApiError} (from `@rebasepro/types`, re-exported by
16185
- * `@rebasepro/client`). Kept as an alias so `instanceof` / imports keep working;
16186
- * errors thrown here now carry `.code` and `.status` on the unified type.
16187
- */
16188
- var AuthApiError = RebaseApiError;
16189
16414
  async function handleResponse(response) {
16190
16415
  let data;
16191
16416
  try {
@@ -16238,11 +16463,11 @@ function createAuthConfigCache() {
16238
16463
  * Concurrent calls are deduplicated: only one network request is made
16239
16464
  * and all callers share the same promise.
16240
16465
  */
16241
- async function fetchAuthConfig(apiUrl, cache) {
16466
+ async function fetchAuthConfig(apiUrl, cache, apiPath = DEFAULT_API_PATH) {
16242
16467
  if (cache.cached) return cache.cached;
16243
16468
  if (cache.inflight) return cache.inflight;
16244
16469
  cache.inflight = (async () => {
16245
- return handleResponse(await fetchWithHandling(`${apiUrl}/api/auth/config`, {
16470
+ return handleResponse(await fetchWithHandling(`${apiUrl.replace(/\/+$/, "")}${apiPath}/auth/config`, {
16246
16471
  method: "GET",
16247
16472
  headers: { "Content-Type": "application/json" }
16248
16473
  }));
@@ -16568,7 +16793,7 @@ function useStorageUploadController({ entityId, entityValues, path, value, prope
16568
16793
  if (!storage) throw Error("Storage meta must be specified");
16569
16794
  const processFile = storage?.processFile;
16570
16795
  const metadata = storage?.metadata;
16571
- const size = multipleFilesSupported ? "medium" : "large";
16796
+ const size = "medium";
16572
16797
  const imageResize = storage?.imageResize;
16573
16798
  const internalInitialValue = getInternalInitialValue(multipleFilesSupported, value, metadata, size);
16574
16799
  const [initialValue, setInitialValue] = useState(value);
@@ -16660,7 +16885,8 @@ function useStorageUploadController({ entityId, entityValues, path, value, prope
16660
16885
  ]),
16661
16886
  onFileUploadError: useCallback((entry) => {
16662
16887
  console.debug("onFileUploadError", entry);
16663
- setInternalValue(internalValue.filter((item) => item.id !== entry.id));
16888
+ const newValue = internalValue.filter((item) => item.id !== entry.id);
16889
+ setInternalValue(newValue);
16664
16890
  }, [internalValue]),
16665
16891
  onFilesAdded: useCallback(async (acceptedFiles) => {
16666
16892
  if (!acceptedFiles.length || disabled) return;
@@ -16775,7 +17001,7 @@ function getColorScheme(enumValues, key) {
16775
17001
  const labelOrConfig = getLabelOrConfigFrom(enumValues, key);
16776
17002
  if (!labelOrConfig?.color) return getColorSchemeForSeed(key.toString());
16777
17003
  if (typeof labelOrConfig === "object" && "color" in labelOrConfig) {
16778
- if (typeof labelOrConfig.color === "string") return CHIP_COLORS[labelOrConfig.color];
17004
+ if (typeof labelOrConfig.color === "string") return getColorSchemeForKey(labelOrConfig.color);
16779
17005
  if (typeof labelOrConfig.color === "object") return labelOrConfig.color;
16780
17006
  }
16781
17007
  }
@@ -16809,9 +17035,21 @@ var STUDIO_NAVIGATION_GROUPS = [
16809
17035
  */
16810
17036
  var CONTAINER_FULL_WIDTH = "100vw";
16811
17037
  /** @internal See {@link CONTAINER_FULL_WIDTH}. */
16812
- var ADDITIONAL_TAB_WIDTH = "55vw";
16813
- /** @internal See {@link CONTAINER_FULL_WIDTH}. */
16814
17038
  var FORM_CONTAINER_WIDTH = "768px";
17039
+ /**
17040
+ * How wide the side panel opens when neither the caller nor the collection
17041
+ * says otherwise.
17042
+ *
17043
+ * Sized from what it has to hold rather than from a round number: the form's
17044
+ * content column plus its metadata rail plus gutters, which is also the width
17045
+ * at which the form stops being a single stack and picks up the same
17046
+ * two-column layout it has in full screen. Capped against the viewport so a
17047
+ * panel never becomes the whole window on a laptop — at that point you want
17048
+ * full screen, and there is a button for it.
17049
+ *
17050
+ * @internal See {@link CONTAINER_FULL_WIDTH}.
17051
+ */
17052
+ var SIDE_PANEL_DEFAULT_WIDTH = "min(1160px, 88vw)";
16815
17053
  //#endregion
16816
17054
  //#region src/internal/useRestoreScroll.tsx
16817
17055
  var scrollsMap = {};
@@ -16935,7 +17173,8 @@ var NOOP_BRIDGE = {
16935
17173
  breadcrumbs: [],
16936
17174
  set: () => {},
16937
17175
  updateCount: () => {}
16938
- }
17176
+ },
17177
+ capabilities: { codebase: true }
16939
17178
  };
16940
17179
  var StudioBridgeContext = createContext(NOOP_BRIDGE);
16941
17180
  /**
@@ -16959,7 +17198,14 @@ function StudioBridgeProvider({ value, children }) {
16959
17198
  const merged = React.useMemo(() => ({
16960
17199
  ...NOOP_BRIDGE,
16961
17200
  ...value
16962
- }), [value]);
17201
+ }), [
17202
+ value.collectionRegistry,
17203
+ value.sidePanelController,
17204
+ value.urlController,
17205
+ value.navigationState,
17206
+ value.breadcrumbs,
17207
+ value.capabilities
17208
+ ]);
16963
17209
  return /* @__PURE__ */ jsx(StudioBridgeContext.Provider, {
16964
17210
  value: merged,
16965
17211
  children
@@ -16985,6 +17231,10 @@ function useStudioNavigationState() {
16985
17231
  function useStudioBreadcrumbs() {
16986
17232
  return useContext(StudioBridgeContext).breadcrumbs;
16987
17233
  }
17234
+ /** What the host can do — see {@link StudioCapabilities}. */
17235
+ function useStudioCapabilities() {
17236
+ return useContext(StudioBridgeContext).capabilities;
17237
+ }
16988
17238
  var StudioBridgeRegistryContext = createContext(null);
16989
17239
  /**
16990
17240
  * Provider that creates a self-assembling bridge.
@@ -17247,6 +17497,326 @@ function resolveFilterOperators({ property, isArray, engine }) {
17247
17497
  return typeDefaults.filter((op) => engineOps.has(op) && (narrowingSet === void 0 || narrowingSet.has(op)));
17248
17498
  }
17249
17499
  //#endregion
17500
+ //#region src/collections/property_presentation.ts
17501
+ function isReadOnly(property) {
17502
+ if (property.admin?.readOnly) return true;
17503
+ if (property.type === "date") {
17504
+ if (property.autoValue) return true;
17505
+ }
17506
+ if (property.type === "reference") return !property.path && !("Field" in (property.admin || {}) && property.admin?.Field);
17507
+ return false;
17508
+ }
17509
+ function isHidden(property) {
17510
+ return typeof property.admin?.disabled === "object" && Boolean(property.admin?.disabled.hidden);
17511
+ }
17512
+ function applyPropertyConditions(property, context) {
17513
+ const { conditions } = property;
17514
+ if (!conditions) return property;
17515
+ const result = { ...property };
17516
+ if (conditions.disabled) {
17517
+ if (evaluateCondition(conditions.disabled, context)) {
17518
+ result.admin = result.admin || {};
17519
+ result.admin.disabled = {
17520
+ clearOnDisabled: conditions.clearOnDisabled ?? false,
17521
+ disabledMessage: conditions.disabledMessage,
17522
+ hidden: false
17523
+ };
17524
+ }
17525
+ }
17526
+ if (conditions.hidden) {
17527
+ if (evaluateCondition(conditions.hidden, context)) {
17528
+ result.admin = result.admin || {};
17529
+ result.admin.disabled = {
17530
+ ...typeof result.admin?.disabled === "object" ? result.admin.disabled : {},
17531
+ hidden: true,
17532
+ clearOnDisabled: conditions.clearOnDisabled ?? false
17533
+ };
17534
+ }
17535
+ }
17536
+ if (conditions.readOnly) {
17537
+ if (evaluateCondition(conditions.readOnly, context)) {
17538
+ result.admin = result.admin || {};
17539
+ result.admin.readOnly = true;
17540
+ }
17541
+ }
17542
+ if (conditions.required !== void 0) {
17543
+ const isRequired = evaluateCondition(conditions.required, context);
17544
+ result.validation = {
17545
+ ...result.validation,
17546
+ required: isRequired,
17547
+ requiredMessage: conditions.requiredMessage
17548
+ };
17549
+ }
17550
+ if (context.isNew && conditions.defaultValue !== void 0) result.defaultValue = evaluateCondition(conditions.defaultValue, context);
17551
+ if ("enum" in result && result.enum && (conditions.enumConditions || conditions.allowedEnumValues || conditions.excludedEnumValues)) result.enum = applyEnumConditions(result.enum, conditions, context);
17552
+ if (result.type === "reference") {
17553
+ if (conditions.referencePath) result.path = evaluateCondition(conditions.referencePath, context);
17554
+ if (conditions.referenceFilter) {
17555
+ result.admin = result.admin || {};
17556
+ result.admin.fixedFilter = evaluateCondition(conditions.referenceFilter, context);
17557
+ }
17558
+ }
17559
+ if (result.type === "array") {
17560
+ if (conditions.canAddElements !== void 0) {
17561
+ result.admin = result.admin || {};
17562
+ result.admin.canAddElements = evaluateCondition(conditions.canAddElements, context);
17563
+ }
17564
+ if (conditions.sortable !== void 0) {
17565
+ result.admin = result.admin || {};
17566
+ result.admin.sortable = evaluateCondition(conditions.sortable, context);
17567
+ }
17568
+ }
17569
+ return result;
17570
+ }
17571
+ /**
17572
+ * Convert an object with numeric keys back to an array.
17573
+ * Firestore stores arrays as {"0": "a", "1": "b"} to avoid nested arrays.
17574
+ */
17575
+ function objectToArray(obj) {
17576
+ if (Array.isArray(obj)) return obj.map(String);
17577
+ if (obj && typeof obj === "object") {
17578
+ const keys = Object.keys(obj);
17579
+ if (keys.length > 0 && keys.every((k) => !isNaN(Number(k)))) return keys.sort((a, b) => Number(a) - Number(b)).map((k) => obj[k]).filter((v) => typeof v === "string" || typeof v === "number").map(String);
17580
+ }
17581
+ return [];
17582
+ }
17583
+ /**
17584
+ * Apply enum-specific conditions to filter and modify enum values.
17585
+ */
17586
+ function applyEnumConditions(enumValues, conditions, context) {
17587
+ let result = [...enumValues];
17588
+ if (conditions.allowedEnumValues) {
17589
+ const allowedArray = objectToArray(evaluateCondition(conditions.allowedEnumValues, context));
17590
+ if (allowedArray.length > 0) result = result.filter((ev) => allowedArray.includes(String(ev.id)));
17591
+ }
17592
+ if (conditions.excludedEnumValues) {
17593
+ const excludedArray = objectToArray(evaluateCondition(conditions.excludedEnumValues, context));
17594
+ if (excludedArray.length > 0) result = result.filter((ev) => !excludedArray.includes(String(ev.id)));
17595
+ }
17596
+ if (conditions.enumConditions) result = result.map((ev) => {
17597
+ const evConditions = conditions.enumConditions?.[ev.id];
17598
+ if (!evConditions) return ev;
17599
+ if (evConditions.hidden && evaluateCondition(evConditions.hidden, context)) return null;
17600
+ if (evConditions.disabled && evaluateCondition(evConditions.disabled, context)) return {
17601
+ ...ev,
17602
+ disabled: true
17603
+ };
17604
+ return ev;
17605
+ }).filter((ev) => ev !== null);
17606
+ return result;
17607
+ }
17608
+ //#endregion
17609
+ //#region src/collections/form-layout.ts
17610
+ /**
17611
+ * Columns in the form grid.
17612
+ *
17613
+ * A local literal rather than an import of `FORM_GRID_COLUMNS`: everything else
17614
+ * this module takes from `@rebasepro/admin-types` is a *type*, erased at build
17615
+ * time, and a runtime value would make this pure function depend on that
17616
+ * package's built output — which is exactly what broke the dev server, since
17617
+ * `@rebasepro/app` resolves that package to its dist. `satisfies PropertySpan`
17618
+ * keeps the two honest: a full-width field and the column count are the same
17619
+ * number by definition.
17620
+ */
17621
+ var GRID_COLUMNS = 4;
17622
+ /** Does this property's editor need the full width of the column? */
17623
+ function needsFullWidth(property) {
17624
+ switch (property.type) {
17625
+ case "map":
17626
+ case "binary":
17627
+ case "vector": return true;
17628
+ case "array": {
17629
+ if (property.oneOf) return true;
17630
+ const of = Array.isArray(property.of) ? void 0 : property.of;
17631
+ if (!of) return true;
17632
+ if (of.type !== "string" && of.type !== "number") return true;
17633
+ if (of.type === "string" && (of.storage || of.admin?.markdown || of.admin?.multiline)) return true;
17634
+ return false;
17635
+ }
17636
+ case "string": return Boolean(property.storage || property.admin?.markdown || property.admin?.multiline);
17637
+ default: return false;
17638
+ }
17639
+ }
17640
+ /**
17641
+ * The span a property gets when nothing is declared.
17642
+ *
17643
+ * Deliberately coarse: three buckets, chosen by how much room the *editor*
17644
+ * needs, not by how important the field is. Importance is what `admin.form`
17645
+ * sections are for.
17646
+ */
17647
+ function deriveSpan(property, isTitleProperty) {
17648
+ if (needsFullWidth(property)) return 4;
17649
+ if (isTitleProperty) return 4;
17650
+ switch (property.type) {
17651
+ case "number":
17652
+ case "boolean": return 1;
17653
+ case "date":
17654
+ case "geopoint":
17655
+ case "reference":
17656
+ case "relation": return 2;
17657
+ case "array": return 2;
17658
+ case "string": return 2;
17659
+ default: return 2;
17660
+ }
17661
+ }
17662
+ /**
17663
+ * Can the user still type this id?
17664
+ *
17665
+ * A `manual` id is a real field while creating and frozen once the row exists.
17666
+ * Every generated strategy (`uuid`, `cuid`, a raw SQL default) is never typed.
17667
+ * Getting this wrong in the "frozen" direction would make manual-id collections
17668
+ * uncreatable, so it fails towards showing the field.
17669
+ */
17670
+ function isIdPropertyEditable(property, status) {
17671
+ if (!("isId" in property) || !property.isId) return true;
17672
+ if (status === "existing") return false;
17673
+ return property.isId === "manual";
17674
+ }
17675
+ function isIdProperty(property) {
17676
+ return Boolean(property && "isId" in property && property.isId);
17677
+ }
17678
+ /**
17679
+ * An audit timestamp the database maintains — `created_at`, `updated_at`.
17680
+ *
17681
+ * Not a guess from the name: `autoValue` says the column is written by the
17682
+ * system on create or update, so the field is never typed. Rendering it as an
17683
+ * input in the middle of the form promised an edit that cannot happen, and the
17684
+ * record block already shows both values — the same two dates twice on one
17685
+ * screen.
17686
+ */
17687
+ function isAuditTimestamp(property) {
17688
+ return Boolean(property && property.type === "date" && property.autoValue);
17689
+ }
17690
+ /**
17691
+ * Close the trailing gap in each row.
17692
+ *
17693
+ * Without this a section of half-width fields with an odd count leaves a hole —
17694
+ * `name | sku` then `brand | ␣␣` — a field stranded beside half a row of
17695
+ * nothing, which reads as a mistake rather than a layout. The remainder is
17696
+ * spread across the row's derived fields rather than dumped on the last one, so
17697
+ * a lone half-width field becomes full width and a pair grows evenly. Fields
17698
+ * with an explicit span are never resized.
17699
+ */
17700
+ function fillRows(fields) {
17701
+ const rows = [];
17702
+ let row = [];
17703
+ let used = 0;
17704
+ for (const field of fields) {
17705
+ if (used + field.span > GRID_COLUMNS && row.length) {
17706
+ rows.push(row);
17707
+ row = [];
17708
+ used = 0;
17709
+ }
17710
+ row.push(field);
17711
+ used += field.span;
17712
+ }
17713
+ if (row.length) rows.push(row);
17714
+ return rows.flatMap((entries) => {
17715
+ let gap = GRID_COLUMNS - entries.reduce((sum, f) => sum + f.span, 0);
17716
+ if (gap <= 0) return entries;
17717
+ const growable = entries.map((f, i) => [f, i]).filter(([f]) => !f.spanExplicit);
17718
+ if (!growable.length) return entries;
17719
+ const next = [...entries];
17720
+ let cursor = 0;
17721
+ while (gap > 0) {
17722
+ const [, index] = growable[cursor % growable.length];
17723
+ next[index] = {
17724
+ ...next[index],
17725
+ span: next[index].span + 1
17726
+ };
17727
+ gap -= 1;
17728
+ cursor += 1;
17729
+ }
17730
+ return next;
17731
+ });
17732
+ }
17733
+ function buildField(key, collection, titlePropertyKey) {
17734
+ const property = collection.properties?.[key];
17735
+ if (!property) return collection.additionalFields?.some((f) => f.key === key) ? {
17736
+ key,
17737
+ span: 4,
17738
+ additional: true
17739
+ } : void 0;
17740
+ if (isHidden(property)) return void 0;
17741
+ const admin = property.admin;
17742
+ const explicit = admin?.span !== void 0;
17743
+ return {
17744
+ key,
17745
+ span: admin?.span ?? deriveSpan(property, key === titlePropertyKey),
17746
+ additional: false,
17747
+ spanExplicit: explicit
17748
+ };
17749
+ }
17750
+ /**
17751
+ * Resolve the layout for a collection's generated form.
17752
+ *
17753
+ * Never drops a field: anything not named by a section lands in a trailing
17754
+ * group, so adding a column to the database cannot make it silently invisible
17755
+ * in the panel.
17756
+ */
17757
+ function resolveFormLayout({ collection, fieldKeys, status }) {
17758
+ const config = collection.form;
17759
+ const titlePropertyKey = collection.titleProperty;
17760
+ const available = /* @__PURE__ */ new Map();
17761
+ for (const key of fieldKeys) {
17762
+ const field = buildField(key, collection, titlePropertyKey);
17763
+ if (field) available.set(key, field);
17764
+ }
17765
+ const idKeys = [...available.keys()].filter((key) => isIdProperty(collection.properties?.[key]));
17766
+ if (idKeys.length === 1) {
17767
+ const property = collection.properties?.[idKeys[0]];
17768
+ if (!isIdPropertyEditable(property, status)) available.delete(idKeys[0]);
17769
+ }
17770
+ const showRecordMetaResolved = config?.showRecordMeta ?? !collection.hideIdFromForm;
17771
+ if (showRecordMetaResolved) for (const key of [...available.keys()]) {
17772
+ const property = collection.properties?.[key];
17773
+ if (config?.sidebar?.includes(key)) continue;
17774
+ if (isAuditTimestamp(property)) available.delete(key);
17775
+ }
17776
+ const sidebar = [];
17777
+ if (config?.sidebar) for (const key of config.sidebar) {
17778
+ const field = available.get(key);
17779
+ if (!field) continue;
17780
+ sidebar.push(field);
17781
+ available.delete(key);
17782
+ }
17783
+ const showRecordMeta = showRecordMetaResolved;
17784
+ const sections = [];
17785
+ if (config?.sections?.length) for (const section of config.sections) {
17786
+ const fields = [];
17787
+ for (const key of section.properties) {
17788
+ const field = available.get(key);
17789
+ if (!field) continue;
17790
+ fields.push(field);
17791
+ available.delete(key);
17792
+ }
17793
+ const titled = Boolean(section.title);
17794
+ sections.push({
17795
+ key: section.key,
17796
+ title: section.title,
17797
+ collapsible: section.collapsible ?? titled,
17798
+ collapsed: titled ? Boolean(section.collapsed) : false,
17799
+ fields
17800
+ });
17801
+ }
17802
+ const leftovers = fieldKeys.map((key) => available.get(key)).filter((f) => Boolean(f));
17803
+ if (leftovers.length) sections.push({
17804
+ key: sections.length ? "__other" : "__main",
17805
+ collapsible: false,
17806
+ collapsed: false,
17807
+ fields: leftovers
17808
+ });
17809
+ return {
17810
+ sections: sections.filter((s) => s.fields.length > 0).map((s) => ({
17811
+ ...s,
17812
+ fields: fillRows(s.fields)
17813
+ })),
17814
+ sidebar,
17815
+ showRecordMeta,
17816
+ hasRail: sidebar.length > 0 || showRecordMeta
17817
+ };
17818
+ }
17819
+ //#endregion
17250
17820
  //#region src/collections/navigation_utils.ts
17251
17821
  function removeInitialAndTrailingSlashes(s) {
17252
17822
  return removeInitialSlash(removeTrailingSlash(s));
@@ -17458,115 +18028,6 @@ function getParentReferencesFromPath(props) {
17458
18028
  return result;
17459
18029
  }
17460
18030
  //#endregion
17461
- //#region src/collections/property_presentation.ts
17462
- function isReadOnly(property) {
17463
- if (property.admin?.readOnly) return true;
17464
- if (property.type === "date") {
17465
- if (property.autoValue) return true;
17466
- }
17467
- if (property.type === "reference") return !property.path && !("Field" in (property.admin || {}) && property.admin?.Field);
17468
- return false;
17469
- }
17470
- function isHidden(property) {
17471
- return typeof property.admin?.disabled === "object" && Boolean(property.admin?.disabled.hidden);
17472
- }
17473
- function applyPropertyConditions(property, context) {
17474
- const { conditions } = property;
17475
- if (!conditions) return property;
17476
- const result = { ...property };
17477
- if (conditions.disabled) {
17478
- if (evaluateCondition(conditions.disabled, context)) {
17479
- result.admin = result.admin || {};
17480
- result.admin.disabled = {
17481
- clearOnDisabled: conditions.clearOnDisabled ?? false,
17482
- disabledMessage: conditions.disabledMessage,
17483
- hidden: false
17484
- };
17485
- }
17486
- }
17487
- if (conditions.hidden) {
17488
- if (evaluateCondition(conditions.hidden, context)) {
17489
- result.admin = result.admin || {};
17490
- result.admin.disabled = {
17491
- ...typeof result.admin?.disabled === "object" ? result.admin.disabled : {},
17492
- hidden: true,
17493
- clearOnDisabled: conditions.clearOnDisabled ?? false
17494
- };
17495
- }
17496
- }
17497
- if (conditions.readOnly) {
17498
- if (evaluateCondition(conditions.readOnly, context)) {
17499
- result.admin = result.admin || {};
17500
- result.admin.readOnly = true;
17501
- }
17502
- }
17503
- if (conditions.required !== void 0) {
17504
- const isRequired = evaluateCondition(conditions.required, context);
17505
- result.validation = {
17506
- ...result.validation,
17507
- required: isRequired,
17508
- requiredMessage: conditions.requiredMessage
17509
- };
17510
- }
17511
- if (context.isNew && conditions.defaultValue !== void 0) result.defaultValue = evaluateCondition(conditions.defaultValue, context);
17512
- if ("enum" in result && result.enum && (conditions.enumConditions || conditions.allowedEnumValues || conditions.excludedEnumValues)) result.enum = applyEnumConditions(result.enum, conditions, context);
17513
- if (result.type === "reference") {
17514
- if (conditions.referencePath) result.path = evaluateCondition(conditions.referencePath, context);
17515
- if (conditions.referenceFilter) {
17516
- result.admin = result.admin || {};
17517
- result.admin.fixedFilter = evaluateCondition(conditions.referenceFilter, context);
17518
- }
17519
- }
17520
- if (result.type === "array") {
17521
- if (conditions.canAddElements !== void 0) {
17522
- result.admin = result.admin || {};
17523
- result.admin.canAddElements = evaluateCondition(conditions.canAddElements, context);
17524
- }
17525
- if (conditions.sortable !== void 0) {
17526
- result.admin = result.admin || {};
17527
- result.admin.sortable = evaluateCondition(conditions.sortable, context);
17528
- }
17529
- }
17530
- return result;
17531
- }
17532
- /**
17533
- * Convert an object with numeric keys back to an array.
17534
- * Firestore stores arrays as {"0": "a", "1": "b"} to avoid nested arrays.
17535
- */
17536
- function objectToArray(obj) {
17537
- if (Array.isArray(obj)) return obj.map(String);
17538
- if (obj && typeof obj === "object") {
17539
- const keys = Object.keys(obj);
17540
- if (keys.length > 0 && keys.every((k) => !isNaN(Number(k)))) return keys.sort((a, b) => Number(a) - Number(b)).map((k) => obj[k]).filter((v) => typeof v === "string" || typeof v === "number").map(String);
17541
- }
17542
- return [];
17543
- }
17544
- /**
17545
- * Apply enum-specific conditions to filter and modify enum values.
17546
- */
17547
- function applyEnumConditions(enumValues, conditions, context) {
17548
- let result = [...enumValues];
17549
- if (conditions.allowedEnumValues) {
17550
- const allowedArray = objectToArray(evaluateCondition(conditions.allowedEnumValues, context));
17551
- if (allowedArray.length > 0) result = result.filter((ev) => allowedArray.includes(String(ev.id)));
17552
- }
17553
- if (conditions.excludedEnumValues) {
17554
- const excludedArray = objectToArray(evaluateCondition(conditions.excludedEnumValues, context));
17555
- if (excludedArray.length > 0) result = result.filter((ev) => !excludedArray.includes(String(ev.id)));
17556
- }
17557
- if (conditions.enumConditions) result = result.map((ev) => {
17558
- const evConditions = conditions.enumConditions?.[ev.id];
17559
- if (!evConditions) return ev;
17560
- if (evConditions.hidden && evaluateCondition(evConditions.hidden, context)) return null;
17561
- if (evConditions.disabled && evaluateCondition(evConditions.disabled, context)) return {
17562
- ...ev,
17563
- disabled: true
17564
- };
17565
- return ev;
17566
- }).filter((ev) => ev !== null);
17567
- return result;
17568
- }
17569
- //#endregion
17570
18031
  //#region src/collections/title-property.ts
17571
18032
  /**
17572
18033
  * How good a property is as the human-readable title of an entity.
@@ -17600,7 +18061,7 @@ var SCORE = {
17600
18061
  * This is a *bonus* on top of the structural rules — never a requirement, and
17601
18062
  * never the mechanism that keeps identifiers out of the title slot.
17602
18063
  */
17603
- var TITLE_LIKE_KEYS = new Set([
18064
+ var TITLE_LIKE_KEYS = /* @__PURE__ */ new Set([
17604
18065
  "name",
17605
18066
  "fullname",
17606
18067
  "displayname",
@@ -17730,6 +18191,40 @@ function getTitlePropertyCandidates(collection) {
17730
18191
  return scored.map((candidate) => candidate.key);
17731
18192
  }
17732
18193
  /**
18194
+ * The relation a collection *leads* with, if it leads with one.
18195
+ *
18196
+ * A junction-shaped collection — a pipeline entry, a membership, a booking —
18197
+ * holds no label of its own: it is "this candidate, for that vacancy". The
18198
+ * general ranking puts relations last (they only read once the target
18199
+ * resolves), so the title slot would otherwise fall to whatever free text
18200
+ * happens to follow: a note, a comment. Preview surfaces that render one row
18201
+ * per entity (list, cards, board) prefer the leading relation instead.
18202
+ *
18203
+ * The decision is structural — the first property that could be a title, in the
18204
+ * order the developer declared (or ordered) them. Declaration order is a
18205
+ * statement about what the collection is about; property *names* are not
18206
+ * consulted here.
18207
+ *
18208
+ * Returns `undefined` when that first property is anything but a
18209
+ * single-cardinality relation, or when the collection states its own
18210
+ * `titleProperty`.
18211
+ *
18212
+ * @group Collections
18213
+ */
18214
+ function getLeadingRelationTitleKey(collection) {
18215
+ if (!collection.properties) return void 0;
18216
+ if (collection.titleProperty) return void 0;
18217
+ const idKeys = new Set(getPrimaryKeys(collection));
18218
+ const foreignKeys = getForeignKeyColumns(collection);
18219
+ const order = collection.propertiesOrder ?? Object.keys(collection.properties);
18220
+ for (const key of order) {
18221
+ const property = collection.properties[key];
18222
+ if (!property || isPropertyBuilder(property)) continue;
18223
+ if (scoreTitleCandidate(property, key, idKeys, foreignKeys) === SCORE.DISQUALIFIED) continue;
18224
+ return property.type === "relation" ? key : void 0;
18225
+ }
18226
+ }
18227
+ /**
17733
18228
  * The property that should fill the title slot for a collection, ignoring any
17734
18229
  * concrete values. Prefer {@link getTitlePropertyKeyForValues} when an entity
17735
18230
  * is at hand — it can skip candidates that happen to be empty or to hold an id.
@@ -17779,6 +18274,6 @@ function getTitlePropertyKeyForValues(collection, values, entityId) {
17779
18274
  return candidates[0];
17780
18275
  }
17781
18276
  //#endregion
17782
- export { ADDITIONAL_TAB_WIDTH, AIIcon, AIModifiedIndicator, AdminModeControllerContext, AdminModeControllerProvider, AnalyticsContext, ApiConfigProvider, AuthApiError, AuthControllerContext, CONTAINER_FULL_WIDTH, CollectionComponentOverrideProvider, CollectionResolverRegistrationContext, CollectionScopeContext, CollectionScopeProvider, ComponentOverrideContext, ConfirmationDialog, CrmDashboardDemo, CustomizationControllerContext, DEFAULT_PAGE_SIZE, DataDriverContext, DataSourcesContext, DialogsControllerContext, DialogsProvider, EffectiveRoleControllerContext, EffectiveRoleControllerProvider, ErrorTooltip, ErrorView, FORM_CONTAINER_WIDTH, GlobalComponentOverrideProvider, IconForView, LanguageToggle, LoginView, ModeControllerContext, ModeControllerProvider, NavigationBlockerProvider, NotFoundPage, PluginProviderStack, REBASE_LOCALE_STORAGE_KEY, Rebase, RebaseAuth, RebaseClientInstanceContext, RebaseDataContext, RebaseI18nProvider, RebaseLogo, RebaseRegistryProvider, RebaseRouter, RebaseRoutes, STUDIO_NAVIGATION_GROUPS, SchemaDriftBanner, SchemaDriftProvider, SnackbarProvider, StorageSourceContext, StorageSourcesContext, StudioBridgeContext, StudioBridgeProvider, StudioBridgeRegistryContext, StudioBridgeRegistryProvider, UIReferenceView, UIStyleGuide, UnsavedChangesDialog, UserConfigurationPersistenceContext, UserDisplay, UserSelectPopover, UserSettingsView, addInitialSlash, applyPropertyConditions, buildCollapsedDefaults, buildEnumLabel, clearAuthConfigCache, clearFetchCache, createAuthConfigCache, createFormexStub, deleteEntityWithCallbacks, en, es, fetchAuthConfig, flattenKeys, getAdminEntityChildViews, getAdminSubcollections, getCollectionBySlugWithin, getCollectionPathsCombinations, getColorScheme, getColumnKeysForProperty, getEntityFromCache, getEntityFromMemoryCache, getEntityImagePreviewPropertyKey, getEntityPreviewKeys, getEntityTitlePropertyKey, getFormFieldKeys, getIcon, getLastSegment, getLocalChangesBackup, getNavigationEntriesFromPath, getParentReferencesFromPath, getRelationIncludeParams, getRowHeight, getSubcollectionColumnId, getTitlePropertyCandidates, getTitlePropertyKey, getTitlePropertyKeyForValues, iconsSearch, isEnumValueDisabled, isFilterableRelation, isHidden, isReadOnly, isSchemaDriftError, looksLikeIdentifierValue, populateFetchCache, removeEntityFromCache, removeEntityFromMemoryCache, removeInitialAndTrailingSlashes, removeInitialSlash, removeTrailingSlash, resolveCollectionPathIds, resolveComponentRef, resolveDefaultSelectedView, resolveFilterOperators, saveEntityToCache, saveEntityToMemoryCache, saveEntityWithCallbacks, useAdminModeController, useAnalyticsController, useApiConfig, useAuthController, useAuthSubscription, useBackendStorageSource, useBridgeRegistration, useBrowserTitleAndIcon, useBuildAdminModeController, useBuildEffectiveRoleController, useBuildLocalConfigurationPersistence, useBuildModeController, useClipboard, useCollapsedGroups, useCollection, useCollectionScope, useColumnIds, useComponentOverride, useCustomizationController, useData, useDataSources, useDataTableController, useDebouncedData, useDialogsController, useEffectiveRoleController, useFetch, useLargeLayout, useModeController, useNavigationBlocker, usePermissions, useRebaseAuthController, useRebaseClient, useRebaseContext, useRebaseRegistry, useRebaseRegistryDispatch, useRelationSelector, useResolvedComponent, useRestoreScroll, useSchemaDriftContext, useScrollRestoration, useSlot, useSnackbarController, useStorageSource, useStorageSources, useStorageUploadController, useStudioBreadcrumbs, useStudioCollectionRegistry, useStudioNavigationState, useStudioSidePanelController, useStudioUrlController, useTranslation, useUnsavedChangesDialog, useUserConfigurationPersistence };
18277
+ export { AIIcon, AIModifiedIndicator, AdminModeControllerContext, AdminModeControllerProvider, AnalyticsContext, ApiConfigProvider, AuthControllerContext, CONTAINER_FULL_WIDTH, CollectionComponentOverrideProvider, CollectionResolverRegistrationContext, CollectionScopeContext, CollectionScopeProvider, ComponentOverrideContext, ConfirmationDialog, CrmDashboardDemo, CustomizationControllerContext, DEFAULT_API_PATH, DEFAULT_PAGE_SIZE, DataDriverContext, DataSourcesContext, DialogsControllerContext, DialogsProvider, EffectiveRoleControllerContext, EffectiveRoleControllerProvider, ErrorTooltip, ErrorView, FORM_CONTAINER_WIDTH, GlobalComponentOverrideProvider, IconForView, LanguageToggle, LoginView, ModeControllerContext, ModeControllerProvider, NavigationBlockerProvider, NotFoundPage, PluginProviderStack, REBASE_LOCALE_STORAGE_KEY, Rebase, RebaseAuth, RebaseClientInstanceContext, RebaseDataContext, RebaseI18nProvider, RebaseLogo, RebaseRegistryProvider, RebaseRouter, RebaseRoutes, SIDE_PANEL_DEFAULT_WIDTH, STUDIO_NAVIGATION_GROUPS, SchemaDriftBanner, SchemaDriftProvider, SnackbarProvider, StorageSourceContext, StorageSourcesContext, StudioBridgeContext, StudioBridgeProvider, StudioBridgeRegistryContext, StudioBridgeRegistryProvider, UIReferenceView, UIStyleGuide, UnsavedChangesDialog, UserConfigurationPersistenceContext, UserDisplay, UserSelectPopover, UserSettingsView, addInitialSlash, apiBaseOf, applyPropertyConditions, buildCollapsedDefaults, buildEnumLabel, clearAuthConfigCache, clearFetchCache, createAuthConfigCache, createFormexStub, deleteEntityWithCallbacks, deriveSpan, en, es, fetchAuthConfig, fillRows, flattenKeys, getAdminEntityChildViews, getAdminSubcollections, getCollectionBySlugWithin, getCollectionPathsCombinations, getColorScheme, getColumnKeysForProperty, getEntityFromCache, getEntityFromMemoryCache, getEntityImagePreviewPropertyKey, getEntityPreviewKeys, getEntityTitlePropertyKey, getFormFieldKeys, getIcon, getLastSegment, getLeadingRelationTitleKey, getLocalChangesBackup, getNavigationEntriesFromPath, getParentReferencesFromPath, getRelationIncludeParams, getRowHeight, getSubcollectionColumnId, getTitlePropertyCandidates, getTitlePropertyKey, getTitlePropertyKeyForValues, iconsSearch, isAuditTimestamp, isEnumValueDisabled, isFilterableRelation, isHidden, isIdPropertyEditable, isReadOnly, isSchemaDriftError, looksLikeIdentifierValue, populateFetchCache, removeEntityFromCache, removeEntityFromMemoryCache, removeInitialAndTrailingSlashes, removeInitialSlash, removeTrailingSlash, resolveCollectionPathIds, resolveComponentRef, resolveDefaultSelectedView, resolveFilterOperators, resolveFormLayout, saveEntityToCache, saveEntityToMemoryCache, saveEntityWithCallbacks, useAdminModeController, useAnalyticsController, useApiBase, useApiConfig, useAuthController, useAuthSubscription, useBackendStorageSource, useBridgeRegistration, useBrowserTitleAndIcon, useBuildAdminModeController, useBuildEffectiveRoleController, useBuildLocalConfigurationPersistence, useBuildModeController, useClipboard, useCollapsedGroups, useCollection, useCollectionScope, useColumnIds, useComponentOverride, useCustomizationController, useData, useDataSources, useDataTableController, useDebouncedData, useDialogsController, useEffectiveRoleController, useFetch, useLargeLayout, useModeController, useNavigationBlocker, usePermissions, useRebaseAuthController, useRebaseClient, useRebaseContext, useRebaseRegistry, useRebaseRegistryDispatch, useRelationSelector, useResolvedComponent, useRestoreScroll, useSchemaDriftContext, useScrollRestoration, useSlot, useSnackbarController, useStorageSource, useStorageSources, useStorageUploadController, useStudioBreadcrumbs, useStudioCapabilities, useStudioCollectionRegistry, useStudioNavigationState, useStudioSidePanelController, useStudioUrlController, useTranslation, useUnsavedChangesDialog, useUserConfigurationPersistence };
17783
18278
 
17784
18279
  //# sourceMappingURL=index.es.js.map