@rebasepro/cms 0.22.0 → 0.23.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 (58) hide show
  1. package/dist/{CollectionEditorDialog-CUtfS_49.js → CollectionEditorDialog-DLHCka00.js} +103 -58
  2. package/dist/CollectionEditorDialog-DLHCka00.js.map +1 -0
  3. package/dist/{PropertyEditView-BpEbC0Q2.js → PropertyEditView-_pu5hzZn.js} +53 -21
  4. package/dist/PropertyEditView-_pu5hzZn.js.map +1 -0
  5. package/dist/{RouterCollectionsStudioView-EK8Knq5I.js → RouterCollectionsStudioView-ChPNl_uU.js} +34 -10
  6. package/dist/RouterCollectionsStudioView-ChPNl_uU.js.map +1 -0
  7. package/dist/collection_editor/_cms_internals.d.ts +1 -0
  8. package/dist/collection_editor/relation_kinds.d.ts +11 -0
  9. package/dist/collection_editor/ui/collection_editor/CollectionStudioView.d.ts +8 -1
  10. package/dist/collection_editor_ui.js +4 -4
  11. package/dist/components/CollectionTableBinding/fields/CompactEntityCellField.d.ts +12 -13
  12. package/dist/components/CollectionTableBinding/fields/TableMultipleRelationField.d.ts +6 -3
  13. package/dist/components/CollectionTableBinding/fields/TableReferenceField.d.ts +8 -1
  14. package/dist/components/CollectionTableBinding/fields/TableRelationField.d.ts +8 -1
  15. package/dist/components/CollectionTableBinding/fields/TableRelationSelectorField.d.ts +13 -3
  16. package/dist/components/CollectionTableBinding/fields/VirtualTableSelect.d.ts +10 -0
  17. package/dist/components/CollectionTableBinding/fields/VirtualTableUserSelect.d.ts +10 -0
  18. package/dist/components/CollectionTableBinding/internal/EntityTableCell.d.ts +21 -0
  19. package/dist/components/CollectionTableBinding/internal/TableCellOpener.d.ts +45 -0
  20. package/dist/components/CollectionTableBinding/table_bindings.d.ts +24 -0
  21. package/dist/components/CollectionViewBinding/CollectionViewBinding.d.ts +7 -1
  22. package/dist/components/EntityPreviewBinding.d.ts +8 -1
  23. package/dist/components/EntityPreviewNesting.d.ts +15 -0
  24. package/dist/components/PickerTrigger.d.ts +17 -0
  25. package/dist/components/ReferenceTable/SelectionTableBinding.d.ts +12 -0
  26. package/dist/components/RelationSelector.d.ts +24 -7
  27. package/dist/components/UserSelector.d.ts +8 -1
  28. package/dist/data_export/export/additional_fields.d.ts +13 -0
  29. package/dist/data_export/export/export.d.ts +6 -0
  30. package/dist/data_import/utils/csv.d.ts +3 -2
  31. package/dist/data_import/utils/id_column.d.ts +16 -0
  32. package/dist/editor.js +1 -1
  33. package/dist/{export-LvIpE7sE.js → export-4bJWdlDK.js} +63 -209
  34. package/dist/export-4bJWdlDK.js.map +1 -0
  35. package/dist/form/form_utils.d.ts +13 -0
  36. package/dist/form/validation.d.ts +13 -0
  37. package/dist/{history-DG6Ln7CV.js → history-BElrZ5Rw.js} +2 -2
  38. package/dist/{history-DG6Ln7CV.js.map → history-BElrZ5Rw.js.map} +1 -1
  39. package/dist/hooks/useEntityDisplay.d.ts +4 -5
  40. package/dist/{import-Cxnmpyjl.js → import-TCgkTVlS.js} +3 -4
  41. package/dist/import-TCgkTVlS.js.map +1 -0
  42. package/dist/index.js +10 -10
  43. package/dist/index.js.map +1 -1
  44. package/dist/preview/util.d.ts +25 -0
  45. package/dist/util/navigation_utils.d.ts +14 -2
  46. package/dist/util/view_mode.d.ts +12 -18
  47. package/dist/{util-DB31IoJE.js → util-CC0ozHoD.js} +35 -2
  48. package/dist/util-CC0ozHoD.js.map +1 -0
  49. package/dist/{util-CyIcgnv2.js → util-DybQBkug.js} +1363 -636
  50. package/dist/util-DybQBkug.js.map +1 -0
  51. package/package.json +9 -9
  52. package/dist/CollectionEditorDialog-CUtfS_49.js.map +0 -1
  53. package/dist/PropertyEditView-BpEbC0Q2.js.map +0 -1
  54. package/dist/RouterCollectionsStudioView-EK8Knq5I.js.map +0 -1
  55. package/dist/export-LvIpE7sE.js.map +0 -1
  56. package/dist/import-Cxnmpyjl.js.map +0 -1
  57. package/dist/util-CyIcgnv2.js.map +0 -1
  58. package/dist/util-DB31IoJE.js.map +0 -1
@@ -1,4 +1,5 @@
1
1
  import { CollectionSize } from "@rebasepro/cms-types";
2
+ import type { DateProperty } from "@rebasepro/types";
2
3
  import type { PreviewSize } from "../types/components/PropertyPreviewProps.js";
3
4
  export declare function sanitizeUrl(url: string | undefined): string;
4
5
  export declare const SMALL_THUMBNAIL = 40;
@@ -6,3 +7,27 @@ export declare const MEDIUM_THUMBNAIL = 100;
6
7
  export declare const LARGE_THUMBNAIL = 200;
7
8
  export declare function getThumbnailMeasure(size: PreviewSize): number;
8
9
  export declare function getPreviewSizeFrom(size: CollectionSize): PreviewSize;
10
+ /**
11
+ * The zone a date property is shown and entered in.
12
+ *
13
+ * A `columnType: "date"` column holds a calendar day with no zone. It travels
14
+ * as that day's UTC midnight, and Postgres keeps the date part of whatever it
15
+ * is given, so it is read and written in UTC. In the browser's own zone a
16
+ * stored 15th showed as the 14th west of Greenwich, and a 15th picked east of
17
+ * it was stored as the 14th.
18
+ *
19
+ * Any other date is shown in the property's declared `timezone`, or the
20
+ * browser's when it declares none.
21
+ */
22
+ export declare function getDatePropertyTimezone(property: DateProperty): string | undefined;
23
+ /**
24
+ * Whether a date property is picked and shown as a day or a day and a time. A
25
+ * `date` column holds no time, so it is a day whatever `mode` says.
26
+ */
27
+ export declare function getDatePropertyMode(property: DateProperty): "date" | "date_time" | undefined;
28
+ /**
29
+ * Whether a value has nothing to show: absent, a blank string, an empty list.
30
+ * A preview of one draws the empty marker, which is the right thing for a
31
+ * column of values and the wrong thing for the one line under a record's name.
32
+ */
33
+ export declare function isEmptyPreviewValue(value: unknown): boolean;
@@ -1,10 +1,10 @@
1
- import type { UrlController, SidePanelController, AdminCollection } from "@rebasepro/cms-types";
1
+ import type { UrlController, SidePanelBindingProps, SidePanelController, AdminCollection } from "@rebasepro/cms-types";
2
2
  export { removeInitialAndTrailingSlashes, removeInitialSlash, removeTrailingSlash, addInitialSlash, getLastSegment, resolveCollectionPathIds, getCollectionBySlugWithin, getCollectionPathsCombinations } from "@rebasepro/app";
3
3
  /**
4
4
  * Navigate to a entity using either a side panel or full-screen mode.
5
5
  * This is an admin-specific UI concern and lives here (not in common).
6
6
  */
7
- export declare function navigateToEntity({ openEntityMode, collection, entityId, copy, path, selectedTab, defaultValues, sidePanelController, onClose, navigation, replace }: {
7
+ export declare function navigateToEntity({ openEntityMode, collection, entityId, copy, path, selectedTab, defaultValues, sidePanelController, onClose, onUpdate, navigation, replace }: {
8
8
  openEntityMode?: "side_panel" | "full_screen" | "split" | "dialog";
9
9
  collection?: AdminCollection;
10
10
  entityId?: string | number;
@@ -22,6 +22,18 @@ export declare function navigateToEntity({ openEntityMode, collection, entityId,
22
22
  path: string;
23
23
  sidePanelController: SidePanelController;
24
24
  onClose?: () => void;
25
+ /**
26
+ * Called when the side panel or dialog saves the record. A full-screen
27
+ * record replaces the view that opened it, and that view reads
28
+ * everything again when it comes back, so nothing is called there.
29
+ */
30
+ onUpdate?: SidePanelBindingProps["onUpdate"];
25
31
  navigation: UrlController;
26
32
  replace?: boolean;
27
33
  }): void;
34
+ /**
35
+ * Whether `pathname` is the record at `entityPath` or somewhere below it — one
36
+ * of its tabs, its `/edit`. A path segment, not a string prefix:
37
+ * `/c/products/12` is another record, not a place inside `/c/products/1`.
38
+ */
39
+ export declare function isPathWithinRecord(pathname: string, entityPath: string): boolean;
@@ -29,32 +29,26 @@ export declare function isViewMode(value: string | null | undefined, customKeys?
29
29
  * @param customKeys keys of the collection's custom views, if any.
30
30
  */
31
31
  export declare function getViewModeFromSearch(search?: string, customKeys?: readonly string[]): ViewMode | null;
32
- /**
33
- * Append the active view mode to a URL so the target route keeps rendering the
34
- * view the user was last looking at.
35
- *
36
- * Every entity/collection navigation must go through this — a missed call site
37
- * silently resets the user's view mode mid-session.
38
- *
39
- * @param url the URL to decorate. May already contain a query string and/or hash.
40
- * @param search query string to read the view mode from. Defaults to the
41
- * current browser location.
42
- */
43
- export declare function withViewMode(url: string, search?: string): string;
44
32
  /**
45
33
  * Carry the whole of the collection list's URL state onto a navigation target.
46
34
  *
47
- * `withViewMode` exists because losing the view mode mid-session is jarring.
48
- * Everything else the list writes to the URL — the search string, the filters,
49
- * the sort — is state the user built up deliberately, and losing *that* is
50
- * worse: opening a record from a search and pressing back returned an
51
- * unfiltered list with an empty search box, so the search had to be retyped
52
- * for every record they wanted to look at.
35
+ * Every navigation to the collection or one of its records goes through this —
36
+ * a missed call site silently resets the list mid-session. The list reads its
37
+ * state back from the URL on each navigation, so the view mode, the search
38
+ * string, the filters and the sort all have to travel: a URL without them
39
+ * returned an unfiltered list with an empty search box, and the search had to
40
+ * be retyped for every record the user wanted to look at.
53
41
  *
54
42
  * Copies every param the list currently has rather than an allowlist, because
55
43
  * filters are encoded under the field's own name — there is no fixed set to
56
44
  * list. Params already on the target win: the caller is more specific.
57
45
  *
46
+ * Carries whatever `__view` holds rather than validating it. These call sites
47
+ * are navigations with no collection in hand, so they cannot know whether
48
+ * `map` is one of *this* collection's custom views; checking against the
49
+ * built-ins here would drop every custom view on the first record click. The
50
+ * binding validates on read, which is where the answer is knowable.
51
+ *
58
52
  * @param url the URL to decorate. May carry a query string and/or a hash.
59
53
  * @param search query string to read from. Defaults to the browser location.
60
54
  */
@@ -51,7 +51,40 @@ function getPreviewSizeFrom(size) {
51
51
  default: throw Error("Missing mapping value in getPreviewSizeFrom: " + size);
52
52
  }
53
53
  }
54
+ /**
55
+ * The zone a date property is shown and entered in.
56
+ *
57
+ * A `columnType: "date"` column holds a calendar day with no zone. It travels
58
+ * as that day's UTC midnight, and Postgres keeps the date part of whatever it
59
+ * is given, so it is read and written in UTC. In the browser's own zone a
60
+ * stored 15th showed as the 14th west of Greenwich, and a 15th picked east of
61
+ * it was stored as the 14th.
62
+ *
63
+ * Any other date is shown in the property's declared `timezone`, or the
64
+ * browser's when it declares none.
65
+ */
66
+ function getDatePropertyTimezone(property) {
67
+ return property.columnType === "date" ? "UTC" : property.timezone;
68
+ }
69
+ /**
70
+ * Whether a date property is picked and shown as a day or a day and a time. A
71
+ * `date` column holds no time, so it is a day whatever `mode` says.
72
+ */
73
+ function getDatePropertyMode(property) {
74
+ return property.columnType === "date" ? "date" : property.mode;
75
+ }
76
+ /**
77
+ * Whether a value has nothing to show: absent, a blank string, an empty list.
78
+ * A preview of one draws the empty marker, which is the right thing for a
79
+ * column of values and the wrong thing for the one line under a record's name.
80
+ */
81
+ function isEmptyPreviewValue(value) {
82
+ if (value === void 0 || value === null) return true;
83
+ if (typeof value === "string") return value.trim() === "";
84
+ if (Array.isArray(value)) return value.length === 0;
85
+ return false;
86
+ }
54
87
  //#endregion
55
- export { getThumbnailMeasure as n, sanitizeUrl as r, getPreviewSizeFrom as t };
88
+ export { isEmptyPreviewValue as a, getThumbnailMeasure as i, getDatePropertyTimezone as n, sanitizeUrl as o, getPreviewSizeFrom as r, getDatePropertyMode as t };
56
89
 
57
- //# sourceMappingURL=util-DB31IoJE.js.map
90
+ //# sourceMappingURL=util-CC0ozHoD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util-CC0ozHoD.js","names":[],"sources":["../src/preview/util.ts"],"sourcesContent":["import { CollectionSize } from \"@rebasepro/cms-types\";\nimport type { DateProperty } from \"@rebasepro/types\";\nimport type { PreviewSize } from \"../types/components/PropertyPreviewProps\";\n/**\n * The schemes a preview link may use — everything else becomes `about:blank`.\n *\n * An allowlist, because the blocklist it replaces could be walked around and\n * the walk-arounds are not exotic. Browsers strip tabs, newlines and carriage\n * returns from a URL before parsing the scheme, so `java\\tscript:alert(1)` and\n * `java%0ascript:…` navigate exactly as `javascript:` does while matching none\n * of the names a blocklist knows. A leading control character does the same.\n *\n * Turned around, none of that matters: a string is either one of these four\n * schemes or it is not a link this panel will follow. `mailto:` and `tel:` are\n * here because a contact field is an ordinary thing to preview; `data:` is not,\n * even for images — a preview renders an `<img>` from its own value rather than\n * navigating to one.\n *\n * Relative URLs are allowed and deliberately: a preview commonly holds\n * `/uploads/x.png`, and a string with no scheme cannot carry one.\n */\nconst ALLOWED_URL_SCHEMES = new Set([\"http:\", \"https:\", \"mailto:\", \"tel:\"]);\n\nexport function sanitizeUrl(url: string | undefined): string {\n if (!url) return \"about:blank\";\n\n // The characters a browser removes before it decides what the scheme is.\n // Stripped here for the same reason, so this function is looking at the\n // string the browser will act on rather than the one it was handed.\n //\n // The control characters are the point, so the rule that objects to them is\n // off for this line rather than the expression rewritten to hide them from\n // it: `java\\tscript:` and `java\\nscript:` are the bypasses this exists to\n // close, and a regex that cannot name a control character cannot remove one.\n // eslint-disable-next-line no-control-regex\n const trimmed = url.trim().replace(/[\\u0000-\\u001F\\u007F]/g, \"\");\n if (!trimmed) return \"about:blank\";\n\n // No scheme at all — a relative link, which cannot navigate anywhere the\n // page could not already reach.\n if (!/^[a-z][a-z0-9+.-]*:/i.test(trimmed)) return trimmed;\n\n try {\n // `new URL` normalizes percent-encoding and case, so\n // `JaVaScRiPt%3aalert(1)` is judged as what it resolves to. The base is\n // there only so a protocol-relative `//host/path` parses.\n const parsed = new URL(trimmed, \"https://example.invalid\");\n return ALLOWED_URL_SCHEMES.has(parsed.protocol) ? trimmed : \"about:blank\";\n } catch {\n return \"about:blank\";\n }\n}\n\nexport const SMALL_THUMBNAIL = 40;\nexport const MEDIUM_THUMBNAIL = 100;\nexport const LARGE_THUMBNAIL = 200;\n\nexport function getThumbnailMeasure(size: PreviewSize): number {\n if (size === \"small\")\n return SMALL_THUMBNAIL;\n else if (size === \"medium\")\n return MEDIUM_THUMBNAIL;\n else if (size === \"large\")\n return LARGE_THUMBNAIL;\n else throw Error(\"Thumbnail size not mapped\");\n}\n\nexport function getPreviewSizeFrom(size: CollectionSize): PreviewSize {\n switch (size) {\n case \"xs\":\n case \"s\":\n case \"m\":\n return \"small\";\n case \"l\":\n return \"medium\";\n case \"xl\":\n return \"large\";\n default:\n throw Error(\"Missing mapping value in getPreviewSizeFrom: \" + size);\n }\n}\n\n/**\n * The zone a date property is shown and entered in.\n *\n * A `columnType: \"date\"` column holds a calendar day with no zone. It travels\n * as that day's UTC midnight, and Postgres keeps the date part of whatever it\n * is given, so it is read and written in UTC. In the browser's own zone a\n * stored 15th showed as the 14th west of Greenwich, and a 15th picked east of\n * it was stored as the 14th.\n *\n * Any other date is shown in the property's declared `timezone`, or the\n * browser's when it declares none.\n */\nexport function getDatePropertyTimezone(property: DateProperty): string | undefined {\n return property.columnType === \"date\" ? \"UTC\" : property.timezone;\n}\n\n/**\n * Whether a date property is picked and shown as a day or a day and a time. A\n * `date` column holds no time, so it is a day whatever `mode` says.\n */\nexport function getDatePropertyMode(property: DateProperty): \"date\" | \"date_time\" | undefined {\n return property.columnType === \"date\" ? \"date\" : property.mode;\n}\n\n/**\n * Whether a value has nothing to show: absent, a blank string, an empty list.\n * A preview of one draws the empty marker, which is the right thing for a\n * column of values and the wrong thing for the one line under a record's name.\n */\nexport function isEmptyPreviewValue(value: unknown): boolean {\n if (value === undefined || value === null) return true;\n if (typeof value === \"string\") return value.trim() === \"\";\n if (Array.isArray(value)) return value.length === 0;\n return false;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAqBA,IAAM,sCAAsB,IAAI,IAAI;CAAC;CAAS;CAAU;CAAW;AAAM,CAAC;AAE1E,SAAgB,YAAY,KAAiC;CACzD,IAAI,CAAC,KAAK,OAAO;CAWjB,MAAM,UAAU,IAAI,KAAK,CAAC,CAAC,QAAQ,0BAA0B,EAAE;CAC/D,IAAI,CAAC,SAAS,OAAO;CAIrB,IAAI,CAAC,uBAAuB,KAAK,OAAO,GAAG,OAAO;CAElD,IAAI;EAIA,MAAM,SAAS,IAAI,IAAI,SAAS,yBAAyB;EACzD,OAAO,oBAAoB,IAAI,OAAO,QAAQ,IAAI,UAAU;CAChE,QAAQ;EACJ,OAAO;CACX;AACJ;AAMA,SAAgB,oBAAoB,MAA2B;CAC3D,IAAI,SAAS,SACT,OAAA;MACC,IAAI,SAAS,UACd,OAAA;MACC,IAAI,SAAS,SACd,OAAA;MACC,MAAM,MAAM,2BAA2B;AAChD;AAEA,SAAgB,mBAAmB,MAAmC;CAClE,QAAQ,MAAR;EACI,KAAK;EACL,KAAK;EACL,KAAK,KACD,OAAO;EACX,KAAK,KACD,OAAO;EACX,KAAK,MACD,OAAO;EACX,SACI,MAAM,MAAM,kDAAkD,IAAI;CAC1E;AACJ;;;;;;;;;;;;;AAcA,SAAgB,wBAAwB,UAA4C;CAChF,OAAO,SAAS,eAAe,SAAS,QAAQ,SAAS;AAC7D;;;;;AAMA,SAAgB,oBAAoB,UAA0D;CAC1F,OAAO,SAAS,eAAe,SAAS,SAAS,SAAS;AAC9D;;;;;;AAOA,SAAgB,oBAAoB,OAAyB;CACzD,IAAI,UAAU,KAAA,KAAa,UAAU,MAAM,OAAO;CAClD,IAAI,OAAO,UAAU,UAAU,OAAO,MAAM,KAAK,MAAM;CACvD,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,WAAW;CAClD,OAAO;AACX"}