@rebasepro/core 0.3.0 → 0.5.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rebasepro/core",
3
3
  "type": "module",
4
- "version": "0.3.0",
4
+ "version": "0.5.0",
5
5
  "description": "Rebase core — framework-agnostic runtime for data-driven admin panels",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/rebaseco"
@@ -51,13 +51,12 @@
51
51
  "fuse.js": "^7.3.0",
52
52
  "i18next": "^23.16.8",
53
53
  "notistack": "^3.0.2",
54
- "react-compiler-runtime": "1.0.0",
55
54
  "react-i18next": "^14.1.3",
56
- "@rebasepro/common": "0.3.0",
57
- "@rebasepro/formex": "0.3.0",
58
- "@rebasepro/ui": "0.3.0",
59
- "@rebasepro/utils": "0.3.0",
60
- "@rebasepro/types": "0.3.0"
55
+ "@rebasepro/common": "0.5.0",
56
+ "@rebasepro/formex": "0.5.0",
57
+ "@rebasepro/types": "0.5.0",
58
+ "@rebasepro/ui": "0.5.0",
59
+ "@rebasepro/utils": "0.5.0"
61
60
  },
62
61
  "peerDependencies": {
63
62
  "react": ">=19.0.0",
@@ -126,6 +126,16 @@ export interface LoginViewProps {
126
126
  * If not set, derived from `authController.capabilities.registration`.
127
127
  */
128
128
  registrationEnabled?: boolean;
129
+
130
+ /**
131
+ * Pre-fill the email field (e.g. for demo or testing environments).
132
+ */
133
+ defaultEmail?: string;
134
+
135
+ /**
136
+ * Pre-fill the password field (e.g. for demo or testing environments).
137
+ */
138
+ defaultPassword?: string;
129
139
  }
130
140
 
131
141
  type AuthMode = "buttons" | "login" | "register" | "forgot";
@@ -149,7 +159,9 @@ export function LoginView({
149
159
  subtitle,
150
160
  needsSetup,
151
161
  registrationEnabled,
152
- additionalComponent
162
+ additionalComponent,
163
+ defaultEmail,
164
+ defaultPassword
153
165
  }: LoginViewProps) {
154
166
 
155
167
  const modeState = useModeController();
@@ -301,6 +313,8 @@ export function LoginView({
301
313
  noUserComponent={noUserComponent}
302
314
  disableSignupScreen={false}
303
315
  bootstrapMode={true}
316
+ defaultEmail={defaultEmail}
317
+ defaultPassword={defaultPassword}
304
318
  />
305
319
  )}
306
320
 
@@ -376,6 +390,8 @@ export function LoginView({
376
390
  noUserComponent={noUserComponent}
377
391
  disableSignupScreen={disableSignupScreen}
378
392
  switchToRegister={showRegistration ? () => switchMode("register") : undefined}
393
+ defaultEmail={defaultEmail}
394
+ defaultPassword={defaultPassword}
379
395
  />
380
396
  )}
381
397
 
@@ -389,6 +405,8 @@ export function LoginView({
389
405
  noUserComponent={noUserComponent}
390
406
  disableSignupScreen={disableSignupScreen}
391
407
  switchToLogin={() => switchMode("login")}
408
+ defaultEmail={defaultEmail}
409
+ defaultPassword={defaultPassword}
392
410
  />
393
411
  )}
394
412
 
@@ -576,7 +594,9 @@ function LoginForm({
576
594
  disableSignupScreen,
577
595
  bootstrapMode = false,
578
596
  switchToRegister,
579
- switchToLogin
597
+ switchToLogin,
598
+ defaultEmail,
599
+ defaultPassword
580
600
  }: {
581
601
  onClose: () => void,
582
602
  onForgotPassword?: () => void,
@@ -586,12 +606,14 @@ function LoginForm({
586
606
  disableSignupScreen: boolean,
587
607
  bootstrapMode?: boolean,
588
608
  switchToRegister?: () => void,
589
- switchToLogin?: () => void
609
+ switchToLogin?: () => void,
610
+ defaultEmail?: string,
611
+ defaultPassword?: string
590
612
  }) {
591
613
  const passwordRef = useRef<HTMLInputElement | null>(null);
592
614
 
593
- const [email, setEmail] = useState<string>();
594
- const [password, setPassword] = useState<string>();
615
+ const [email, setEmail] = useState<string | undefined>(defaultEmail);
616
+ const [password, setPassword] = useState<string | undefined>(defaultPassword);
595
617
  const [displayName, setDisplayName] = useState<string>();
596
618
 
597
619
  useEffect(() => {
@@ -1,4 +1,4 @@
1
- import type { Property } from "@rebasepro/types";
1
+ import type { Property, Entity } from "@rebasepro/types";
2
2
  import { CollectionSize, SelectedCellProps } from "@rebasepro/types";
3
3
 
4
4
  export type EntityCollectionTableController<M extends Record<string, unknown>> = {
@@ -25,7 +25,7 @@ export type EntityCollectionTableController<M extends Record<string, unknown>> =
25
25
  * Callback used when the value of a cell has changed.
26
26
  * @param params
27
27
  */
28
- onValueChange?: (params: OnCellValueChangeParams<unknown, M>) => void;
28
+ onValueChange?: (params: OnCellValueChangeParams<unknown, Entity<M>>) => void;
29
29
  /**
30
30
  * Size of the elements in the collection
31
31
  */
@@ -58,7 +58,7 @@ export type UniqueFieldValidator = (props: {
58
58
  * Callback when a cell has changed in a table
59
59
  * @group Collection components
60
60
  */
61
- export type OnCellValueChange<T, M extends Record<string, unknown>> = (params: OnCellValueChangeParams<T, M>) => Promise<void> | void;
61
+ export type OnCellValueChange<T, M extends Record<string, unknown>> = (params: OnCellValueChangeParams<T, Entity<M>>) => Promise<void> | void;
62
62
 
63
63
  /**
64
64
  * @group Collection components
@@ -128,7 +128,7 @@ export function useDataTableController<M extends Record<string, any> = any, USER
128
128
 
129
129
  const { filterValues: urlFilterValues, sortBy: urlSortBy } = parseFilterAndSort(location.search);
130
130
  if (!fixedFilter) {
131
- setFilterValues(urlFilterValues as FilterValues<Extract<keyof M, string> | (string & {})> | undefined);
131
+ setFilterValues((urlFilterValues ?? defaultFilter) as FilterValues<Extract<keyof M, string> | (string & {})> | undefined);
132
132
  }
133
133
  if (urlSortBy && fixedFilter && !checkFilterCombination(fixedFilter, urlSortBy)) {
134
134
  console.warn("URL sort is not compatible with the force filter.");
@@ -186,7 +186,7 @@ export function useDataTableController<M extends Record<string, any> = any, USER
186
186
  const [dataLoadingError, setDataLoadingError] = useState<Error | undefined>();
187
187
  const [noMoreToLoad, setNoMoreToLoad] = useState<boolean>(false);
188
188
 
189
- const clearFilter = useCallback(() => setFilterValues(fixedFilter ?? undefined), [fixedFilter]);
189
+ const clearFilter = useCallback(() => setFilterValues(fixedFilter ?? defaultFilter ?? undefined), [fixedFilter, defaultFilter]);
190
190
 
191
191
  const updateFilterValues = useCallback((updatedFilter: FilterValues<Extract<keyof M, string> | (string & {})> | undefined) => {
192
192
  if (fixedFilter) {
@@ -246,16 +246,22 @@ export function useDataTableController<M extends Record<string, any> = any, USER
246
246
  if (filterValues) {
247
247
  Object.entries(filterValues).forEach(([key, value]) => {
248
248
  if (value && Array.isArray(value)) {
249
- const [op, val] = value;
250
- const postgrestOp = op === "==" ? "eq" : op === "!=" ? "neq" : op === ">" ? "gt" : op === ">=" ? "gte" : op === "<" ? "lt" : op === "<=" ? "lte" : op === "in" ? "in" : op === "not-in" ? "nin" : op === "array-contains" ? "cs" : op === "array-contains-any" ? "csa" : "eq";
251
-
252
- let stringVal: string;
253
- if (Array.isArray(val)) {
254
- stringVal = `(${val.join(",")})`;
255
- } else {
256
- stringVal = String(val);
249
+ const conditions: [WhereFilterOp, unknown][] = Array.isArray(value[0])
250
+ ? (value as [WhereFilterOp, unknown][])
251
+ : [value as [WhereFilterOp, unknown]];
252
+
253
+ const [op, val] = conditions[0] || [];
254
+ if (op) {
255
+ const postgrestOp = op === "==" ? "eq" : op === "!=" ? "neq" : op === ">" ? "gt" : op === ">=" ? "gte" : op === "<" ? "lt" : op === "<=" ? "lte" : op === "in" ? "in" : op === "not-in" ? "nin" : op === "array-contains" ? "cs" : op === "array-contains-any" ? "csa" : "eq";
256
+
257
+ let stringVal: string;
258
+ if (Array.isArray(val)) {
259
+ stringVal = `(${val.join(",")})`;
260
+ } else {
261
+ stringVal = String(val);
262
+ }
263
+ whereMap[key] = `${postgrestOp}.${stringVal}`;
257
264
  }
258
- whereMap[key] = `${postgrestOp}.${stringVal}`;
259
265
  }
260
266
  });
261
267
  }
@@ -388,34 +394,40 @@ function encodeFilterAndSort(filterValues?: FilterValues<string>, sortBy?: [stri
388
394
  if (filterValues) {
389
395
  Object.entries(filterValues).forEach(([key, value]) => {
390
396
  if (value) {
391
- const [op, val] = value;
392
- let encodedValue: unknown = val;
393
- try {
394
- if (typeof val === "object") {
395
- if (val instanceof Date) {
396
- encodedValue = val.toISOString();
397
- } else if (Array.isArray(val)) {
398
- encodedValue = JSON.stringify(val, (k, v) => {
399
- if (v instanceof EntityRelation) {
400
- return encodeRelation(v);
401
- }
402
- if (v instanceof EntityReference) {
403
- return encodeReference(v);
404
- }
405
- return v;
406
- });
407
- } else if (val instanceof EntityRelation) {
408
- encodedValue = encodeRelation(val);
409
- } else if (val instanceof EntityReference) {
410
- encodedValue = encodeReference(val);
397
+ const conditions: [WhereFilterOp, unknown][] = Array.isArray(value[0])
398
+ ? (value as [WhereFilterOp, unknown][])
399
+ : [value as [WhereFilterOp, unknown]];
400
+
401
+ const [op, val] = conditions[0] || [];
402
+ if (op) {
403
+ let encodedValue: unknown = val;
404
+ try {
405
+ if (typeof val === "object") {
406
+ if (val instanceof Date) {
407
+ encodedValue = val.toISOString();
408
+ } else if (Array.isArray(val)) {
409
+ encodedValue = JSON.stringify(val, (k, v) => {
410
+ if (v instanceof EntityRelation) {
411
+ return encodeRelation(v);
412
+ }
413
+ if (v instanceof EntityReference) {
414
+ return encodeReference(v);
415
+ }
416
+ return v;
417
+ });
418
+ } else if (val instanceof EntityRelation) {
419
+ encodedValue = encodeRelation(val);
420
+ } else if (val instanceof EntityReference) {
421
+ encodedValue = encodeReference(val);
422
+ }
411
423
  }
424
+ } catch (e) {
425
+ encodedValue = val;
426
+ }
427
+ if (encodedValue !== undefined) {
428
+ entries[encodeURIComponent(`${key}_op`)] = encodeURIComponent(op);
429
+ entries[encodeURIComponent(`${key}_value`)] = encodedValue ? encodeURIComponent(String(encodedValue)) : "null";
412
430
  }
413
- } catch (e) {
414
- encodedValue = val;
415
- }
416
- if (encodedValue !== undefined) {
417
- entries[encodeURIComponent(`${key}_op`)] = encodeURIComponent(op);
418
- entries[encodeURIComponent(`${key}_value`)] = encodedValue ? encodeURIComponent(String(encodedValue)) : "null";
419
431
  }
420
432
  }
421
433
  });
@@ -22,6 +22,16 @@ export interface CollectionFetchProps<M extends Record<string, any>> {
22
22
  */
23
23
  itemCount?: number;
24
24
 
25
+ /**
26
+ * Number of items to skip
27
+ */
28
+ offset?: number;
29
+
30
+ /**
31
+ * Page number (1-indexed), alternative to offset
32
+ */
33
+ page?: number;
34
+
25
35
  /**
26
36
  * Filter the fetched data by the property
27
37
  */
@@ -46,6 +56,7 @@ export interface CollectionFetchResult<M extends Record<string, any>> {
46
56
  dataLoading: boolean;
47
57
  noMoreToLoad: boolean;
48
58
  dataLoadingError?: Error;
59
+ totalCount?: number;
49
60
  }
50
61
 
51
62
  /**
@@ -55,6 +66,8 @@ export interface CollectionFetchResult<M extends Record<string, any>> {
55
66
  * @param filterValues
56
67
  * @param sortBy
57
68
  * @param itemCount
69
+ * @param offset
70
+ * @param page
58
71
  * @param searchString
59
72
  * @group Hooks and utilities
60
73
  */
@@ -65,6 +78,8 @@ export function useCollectionFetch<M extends Record<string, any>, USER extends U
65
78
  filterValues,
66
79
  sortBy,
67
80
  itemCount,
81
+ offset,
82
+ page,
68
83
  searchString
69
84
  }: CollectionFetchProps<M>): CollectionFetchResult<M> {
70
85
  const dataClient = useData();
@@ -99,12 +114,13 @@ export function useCollectionFetch<M extends Record<string, any>, USER extends U
99
114
  const [dataLoading, setDataLoading] = useState<boolean>(false);
100
115
  const [dataLoadingError, setDataLoadingError] = useState<Error | undefined>();
101
116
  const [noMoreToLoad, setNoMoreToLoad] = useState<boolean>(false);
117
+ const [totalCount, setTotalCount] = useState<number | undefined>();
102
118
 
103
119
  useEffect(() => {
104
120
 
105
121
  setDataLoading(true);
106
122
 
107
- const onEntitiesUpdate = async (res: { data: Entity<M>[], meta: { hasMore: boolean } }) => {
123
+ const onEntitiesUpdate = async (res: { data: Entity<M>[], meta: { hasMore: boolean; total?: number } }) => {
108
124
  const entities = res.data;
109
125
  setDataLoading(false);
110
126
  setDataLoadingError(undefined);
@@ -112,6 +128,7 @@ export function useCollectionFetch<M extends Record<string, any>, USER extends U
112
128
  ...e
113
129
  })));
114
130
  setNoMoreToLoad(!res.meta.hasMore);
131
+ setTotalCount(res.meta.total);
115
132
  };
116
133
 
117
134
  const onError = (error: Error) => {
@@ -119,6 +136,7 @@ export function useCollectionFetch<M extends Record<string, any>, USER extends U
119
136
  setDataLoading(false);
120
137
  setData([]);
121
138
  setDataLoadingError(error);
139
+ setTotalCount(undefined);
122
140
  };
123
141
 
124
142
  const accessor = dataClient.collection(path);
@@ -133,6 +151,8 @@ export function useCollectionFetch<M extends Record<string, any>, USER extends U
133
151
  return accessor.listen({
134
152
  where: whereParams,
135
153
  limit: itemCount,
154
+ offset,
155
+ page,
136
156
  orderBy: orderByParams,
137
157
  searchString,
138
158
  include: includeParams
@@ -141,6 +161,8 @@ export function useCollectionFetch<M extends Record<string, any>, USER extends U
141
161
  accessor.find({
142
162
  where: whereParams,
143
163
  limit: itemCount,
164
+ offset,
165
+ page,
144
166
  orderBy: orderByParams,
145
167
  searchString,
146
168
  include: includeParams
@@ -150,13 +172,14 @@ export function useCollectionFetch<M extends Record<string, any>, USER extends U
150
172
  return () => {
151
173
  };
152
174
  }
153
- }, [path, itemCount, currentSort, sortByProperty, filterValues, searchString, dataClient, collection]);
175
+ }, [path, itemCount, offset, page, currentSort, sortByProperty, filterValues, searchString, dataClient, collection]);
154
176
 
155
177
  return useMemo(() => ({
156
178
  data,
157
179
  dataLoading,
158
180
  dataLoadingError,
159
- noMoreToLoad
160
- }), [data, dataLoading, dataLoadingError, noMoreToLoad]);
181
+ noMoreToLoad,
182
+ totalCount
183
+ }), [data, dataLoading, dataLoadingError, noMoreToLoad, totalCount]);
161
184
 
162
185
  }
@@ -142,7 +142,7 @@ export function useUserSelector(
142
142
  }, []);
143
143
 
144
144
  const getUser = useCallback((uid: string): User | null => {
145
- return userManagement?.getUser(uid) ?? null;
145
+ return userManagement?.getUser?.(uid) ?? null;
146
146
  }, [userManagement]);
147
147
 
148
148
  return useMemo(() => ({
@@ -14,7 +14,7 @@ import { isLazyComponentRef } from "@rebasepro/types";
14
14
  * plain Map since they can't be WeakMap keys.
15
15
  */
16
16
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
- const lazyCache = new WeakMap<object | Function, React.ComponentType<any>>();
17
+ const lazyCache = new WeakMap<object | ((...args: any[]) => any), React.ComponentType<any>>();
18
18
 
19
19
  /**
20
20
  * Resolves a `ComponentRef` into a renderable `React.ComponentType`.
@@ -63,7 +63,7 @@ export function useResolvedComponent<P = unknown>(
63
63
  * same loader always returns the same lazy component identity.
64
64
  */
65
65
  function getOrCreateLazy<P>(
66
- key: object | Function,
66
+ key: object | ((...args: any[]) => any),
67
67
  loader: () => Promise<{ default: React.ComponentType<P> }>
68
68
  ): React.ComponentType<P> {
69
69
  const cached = lazyCache.get(key);
@@ -109,7 +109,7 @@ export function resolveComponentRef<P = unknown>(
109
109
 
110
110
  // 3. Function — either a React component or a lazy import loader.
111
111
  if (typeof ref === "function") {
112
- const fn = ref as Function;
112
+ const fn = ref as (...args: any[]) => any;
113
113
 
114
114
  // Class components (React.Component / PureComponent) have this flag
115
115
  if (fn.prototype?.isReactComponent) {
package/src/locales/en.ts CHANGED
@@ -406,14 +406,14 @@ export const en: RebaseTranslations = {
406
406
  data_imported_successfully: "Data imported successfully",
407
407
  export: "Export",
408
408
  export_data: "Export data",
409
- download_table_csv: "Download the the content of this table as a CSV",
409
+ download_table_csv: "Download the content of this table as a CSV",
410
410
  csv: "CSV",
411
411
  json: "JSON",
412
412
  dates_as_timestamps: "Dates as timestamps",
413
413
  dates_as_strings: "Dates as strings",
414
414
  flatten_arrays: "Flatten arrays",
415
415
  download: "Download",
416
- large_number_of_documents: "This collections has a large number of documents ({{count}}).",
416
+ large_number_of_documents: "This collection has a large number of documents ({{count}}).",
417
417
  include_undefined_values: "Include undefined values",
418
418
  submit: "Submit",
419
419
 
@@ -22,6 +22,11 @@ function isRelationProperty(property: Property) {
22
22
  return false;
23
23
  }
24
24
 
25
+ function isHiddenProperty(property: Property | undefined): boolean {
26
+ if (!property) return false;
27
+ return Boolean(property.ui?.hideFromCollection);
28
+ }
29
+
25
30
  export function getEntityPreviewKeys(
26
31
  authController: AuthController,
27
32
  targetCollection: EntityCollection<any>,
@@ -45,7 +50,7 @@ export function getEntityPreviewKeys(
45
50
  })
46
51
  .filter(key => {
47
52
  const property = targetCollection.properties[key];
48
- return property && !isPropertyBuilder(property) && !isReferenceProperty(property) && !isRelationProperty(property);
53
+ return property && !isPropertyBuilder(property) && !isReferenceProperty(property) && !isRelationProperty(property) && !isHiddenProperty(property);
49
54
  }).slice(0, limit);
50
55
  }
51
56
  }
@@ -62,6 +67,9 @@ export function getEntityTitlePropertyKey<M extends Record<string, any>>(collect
62
67
  const property = collection.properties[key];
63
68
  if (property && !isPropertyBuilder(property)) {
64
69
  const prop = property as Property;
70
+ if (isHiddenProperty(prop)) {
71
+ continue;
72
+ }
65
73
  if (prop.type === "string" && !prop.ui?.multiline && !prop.ui?.markdown && !prop.storage && !prop.isId) {
66
74
  if (!firstStringCandidate) {
67
75
  firstStringCandidate = key;