@firecms/collection_editor 3.1.0-canary.1df3b2c → 3.1.0-canary.7d91b7c

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.
@@ -29,5 +29,9 @@ export interface AICollectionGeneratorPopoverProps {
29
29
  * Whether to show the label text
30
30
  */
31
31
  showLabel?: boolean;
32
+ /**
33
+ * Optional analytics callback
34
+ */
35
+ onAnalyticsEvent?: (event: string, params?: object) => void;
32
36
  }
33
- export declare function AICollectionGeneratorPopover({ existingCollection, onGenerated, generateCollection, trigger, size, showLabel }: AICollectionGeneratorPopoverProps): import("react/jsx-runtime").JSX.Element;
37
+ export declare function AICollectionGeneratorPopover({ existingCollection, onGenerated, generateCollection, trigger, size, showLabel, onAnalyticsEvent }: AICollectionGeneratorPopoverProps): import("react/jsx-runtime").JSX.Element;
@@ -48,6 +48,10 @@ export interface CollectionEditorDialogProps {
48
48
  * The plugin is API-agnostic - the consumer provides the implementation.
49
49
  */
50
50
  generateCollection?: CollectionGenerationCallback;
51
+ /**
52
+ * Optional analytics callback
53
+ */
54
+ onAnalyticsEvent?: (event: string, params?: object) => void;
51
55
  }
52
56
  export declare function CollectionEditorDialog(props: CollectionEditorDialogProps): import("react/jsx-runtime").JSX.Element;
53
57
  export declare function CollectionEditor(props: CollectionEditorDialogProps & {
@@ -1,12 +1,13 @@
1
1
  import React from "react";
2
2
  import { EntityCollection } from "@firecms/core";
3
3
  import { CollectionGenerationCallback } from "../../api/generateCollectionApi";
4
- export declare function CollectionEditorWelcomeView({ path, parentCollection, onContinue, existingCollectionPaths, generateCollection }: {
4
+ export declare function CollectionEditorWelcomeView({ path, parentCollection, onContinue, existingCollectionPaths, generateCollection, onAnalyticsEvent }: {
5
5
  path: string;
6
6
  parentCollection?: EntityCollection;
7
7
  onContinue: (importData?: object[], propertiesOrder?: string[]) => void;
8
8
  existingCollectionPaths?: string[];
9
9
  generateCollection?: CollectionGenerationCallback;
10
+ onAnalyticsEvent?: (event: string, params?: object) => void;
10
11
  }): import("react/jsx-runtime").JSX.Element;
11
12
  export declare function TemplateButton({ title, subtitle, icon, onClick }: {
12
13
  title: string;
package/package.json CHANGED
@@ -1,24 +1,24 @@
1
1
  {
2
2
  "name": "@firecms/collection_editor",
3
3
  "type": "module",
4
- "version": "3.1.0-canary.1df3b2c",
4
+ "version": "3.1.0-canary.7d91b7c",
5
5
  "main": "./dist/index.umd.js",
6
6
  "module": "./dist/index.es.js",
7
7
  "types": "./dist/index.d.ts",
8
8
  "source": "src/index.ts",
9
9
  "dependencies": {
10
- "@firecms/data_export": "^3.1.0-canary.1df3b2c",
11
- "@firecms/data_import": "^3.1.0-canary.1df3b2c",
12
- "@firecms/data_import_export": "^3.1.0-canary.1df3b2c",
13
- "@firecms/formex": "^3.1.0-canary.1df3b2c",
14
- "@firecms/schema_inference": "^3.1.0-canary.1df3b2c",
15
- "@firecms/ui": "^3.1.0-canary.1df3b2c",
10
+ "@firecms/data_export": "^3.1.0-canary.7d91b7c",
11
+ "@firecms/data_import": "^3.1.0-canary.7d91b7c",
12
+ "@firecms/data_import_export": "^3.1.0-canary.7d91b7c",
13
+ "@firecms/formex": "^3.1.0-canary.7d91b7c",
14
+ "@firecms/schema_inference": "^3.1.0-canary.7d91b7c",
15
+ "@firecms/ui": "^3.1.0-canary.7d91b7c",
16
16
  "json5": "^2.2.3",
17
17
  "prism-react-renderer": "^2.4.1"
18
18
  },
19
19
  "peerDependencies": {
20
- "react": ">=18.0.0",
21
- "react-dom": ">=18.0.0",
20
+ "react": ">=18.3.1 || >=19.0.0",
21
+ "react-dom": ">=18.3.1 || >=19.0.0",
22
22
  "react-router": "^6.28.0",
23
23
  "react-router-dom": "^6.28.0"
24
24
  },
@@ -50,8 +50,8 @@
50
50
  },
51
51
  "devDependencies": {
52
52
  "@jest/globals": "^30.2.0",
53
- "@types/react": "^18.3.24",
54
- "@types/react-dom": "^18.3.7",
53
+ "@types/react": "^19.2.3",
54
+ "@types/react-dom": "^19.2.3",
55
55
  "@vitejs/plugin-react": "^4.7.0",
56
56
  "babel-plugin-react-compiler": "^19.0.0-beta-af1b7da-20250417",
57
57
  "eslint-plugin-react-compiler": "^19.1.0-rc.2",
@@ -69,5 +69,5 @@
69
69
  "publishConfig": {
70
70
  "access": "public"
71
71
  },
72
- "gitHead": "5074584b15be0d0507a4dadc148f03d82e9fe495"
72
+ "gitHead": "c444882e108a37a1e90dff02c1c4d58e891ef054"
73
73
  }
@@ -272,6 +272,7 @@ export const ConfigControllerProvider = React.memo(
272
272
  extraView={extraView}
273
273
  getUser={getUser}
274
274
  generateCollection={generateCollection}
275
+ onAnalyticsEvent={onAnalyticsEvent}
275
276
  handleClose={(collection) => {
276
277
  if (currentDialog?.redirect) {
277
278
  if (collection && currentDialog?.isNewCollection && !currentDialog.parentCollectionIds.length) {
@@ -48,6 +48,11 @@ export interface AICollectionGeneratorPopoverProps {
48
48
  * Whether to show the label text
49
49
  */
50
50
  showLabel?: boolean;
51
+
52
+ /**
53
+ * Optional analytics callback
54
+ */
55
+ onAnalyticsEvent?: (event: string, params?: object) => void;
51
56
  }
52
57
 
53
58
  export function AICollectionGeneratorPopover({
@@ -56,7 +61,8 @@ export function AICollectionGeneratorPopover({
56
61
  generateCollection,
57
62
  trigger,
58
63
  size = "small",
59
- showLabel = true
64
+ showLabel = true,
65
+ onAnalyticsEvent
60
66
  }: AICollectionGeneratorPopoverProps) {
61
67
  const [menuOpen, setMenuOpen] = useState(false);
62
68
  const [prompt, setPrompt] = useState("");
@@ -74,6 +80,9 @@ export function AICollectionGeneratorPopover({
74
80
  setLoading(true);
75
81
  setError(null);
76
82
 
83
+ const mode = existingCollection ? "modify" : "create";
84
+ onAnalyticsEvent?.("ai_collection_generate_start", { mode });
85
+
77
86
  try {
78
87
  const collectionsContext = existingCollections.map(c => ({
79
88
  path: c.path,
@@ -100,6 +109,10 @@ export function AICollectionGeneratorPopover({
100
109
  onGenerated(result.collection, result.operations);
101
110
  setMenuOpen(false);
102
111
  setPrompt("");
112
+ onAnalyticsEvent?.("ai_collection_generate_success", {
113
+ mode,
114
+ operationsCount: result.operations?.length
115
+ });
103
116
  snackbarController.open({
104
117
  type: "success",
105
118
  message: existingCollection
@@ -112,6 +125,10 @@ export function AICollectionGeneratorPopover({
112
125
  ? e.message
113
126
  : "Failed to generate collection. Please try again.";
114
127
  setError(errorMessage);
128
+ onAnalyticsEvent?.("ai_collection_generate_error", {
129
+ mode,
130
+ error: errorMessage
131
+ });
115
132
  snackbarController.open({
116
133
  type: "error",
117
134
  message: errorMessage
@@ -8,6 +8,7 @@ import {
8
8
  Container,
9
9
  DebouncedTextField,
10
10
  Dialog,
11
+ HistoryIcon,
11
12
  IconButton,
12
13
  Select,
13
14
  SelectItem,
@@ -375,9 +376,9 @@ export function CollectionDetailsForm({
375
376
  position={"start"}
376
377
  size={"large"}
377
378
  allowIndeterminate={true}
378
- label={values.history === null || values.history === undefined ? "Document history revisions enabled if enabled globally" : (
379
+ label={<span className="flex items-center gap-2"><HistoryIcon size={"smallest"} />{values.history === null || values.history === undefined ? "Document history revisions enabled if enabled globally" : (
379
380
  values.history ? "Document history revisions ENABLED" : "Document history revisions NOT enabled"
380
- )}
381
+ )}</span>}
381
382
  onValueChange={(v) => setFieldValue("history", v)}
382
383
  value={values.history === undefined ? null : values.history}
383
384
  />
@@ -106,6 +106,10 @@ export interface CollectionEditorDialogProps {
106
106
  * The plugin is API-agnostic - the consumer provides the implementation.
107
107
  */
108
108
  generateCollection?: CollectionGenerationCallback;
109
+ /**
110
+ * Optional analytics callback
111
+ */
112
+ onAnalyticsEvent?: (event: string, params?: object) => void;
109
113
  }
110
114
 
111
115
  export function CollectionEditorDialog(props: CollectionEditorDialogProps) {
@@ -290,7 +294,8 @@ function CollectionEditorInternal<M extends Record<string, any>>({
290
294
  existingEntities,
291
295
  initialView: initialViewProp,
292
296
  expandKanban,
293
- generateCollection
297
+ generateCollection,
298
+ onAnalyticsEvent
294
299
  }: CollectionEditorDialogProps & {
295
300
  handleCancel: () => void,
296
301
  setFormDirty: (dirty: boolean) => void,
@@ -630,6 +635,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
630
635
  existingCollection={values}
631
636
  onGenerated={handleAIGenerated}
632
637
  generateCollection={generateCollection}
638
+ onAnalyticsEvent={onAnalyticsEvent}
633
639
  />
634
640
  )}
635
641
  <Tabs value={currentView}
@@ -669,7 +675,8 @@ function CollectionEditorInternal<M extends Record<string, any>>({
669
675
  onContinue={onWelcomeScreenContinue}
670
676
  existingCollectionPaths={existingPaths}
671
677
  parentCollection={parentCollection}
672
- generateCollection={generateCollection} />}
678
+ generateCollection={generateCollection}
679
+ onAnalyticsEvent={onAnalyticsEvent} />}
673
680
 
674
681
  {currentView === "import_data_mapping" && importConfig &&
675
682
  <CollectionEditorImportMapping importConfig={importConfig}
@@ -867,7 +874,9 @@ function applyPropertyConfigs<M extends Record<string, any> = any>(collection: P
867
874
  const propertiesResult: PropertiesOrBuilders<any> = {};
868
875
  if (properties) {
869
876
  Object.keys(properties).forEach((key) => {
870
- propertiesResult[key] = applyPropertiesConfig(properties[key] as PropertyOrBuilder, propertyConfigs);
877
+ const prop = properties[key];
878
+ if (prop == null) return;
879
+ propertiesResult[key] = applyPropertiesConfig(prop as PropertyOrBuilder, propertyConfigs);
871
880
  });
872
881
  }
873
882
 
@@ -879,7 +888,7 @@ function applyPropertyConfigs<M extends Record<string, any> = any>(collection: P
879
888
 
880
889
  function applyPropertiesConfig(property: PropertyOrBuilder, propertyConfigs: Record<string, PropertyConfig<any>>) {
881
890
  let internalProperty = property;
882
- if (propertyConfigs && typeof internalProperty === "object" && internalProperty.propertyConfig) {
891
+ if (propertyConfigs && internalProperty && typeof internalProperty === "object" && internalProperty.propertyConfig) {
883
892
  const propertyConfig = propertyConfigs[internalProperty.propertyConfig];
884
893
  if (propertyConfig && isPropertyBuilder(propertyConfig.property)) {
885
894
  internalProperty = propertyConfig.property;
@@ -18,13 +18,15 @@ export function CollectionEditorWelcomeView({
18
18
  parentCollection,
19
19
  onContinue,
20
20
  existingCollectionPaths,
21
- generateCollection
21
+ generateCollection,
22
+ onAnalyticsEvent
22
23
  }: {
23
24
  path: string;
24
25
  parentCollection?: EntityCollection;
25
26
  onContinue: (importData?: object[], propertiesOrder?: string[]) => void;
26
27
  existingCollectionPaths?: string[];
27
28
  generateCollection?: CollectionGenerationCallback;
29
+ onAnalyticsEvent?: (event: string, params?: object) => void;
28
30
  }) {
29
31
 
30
32
  const { pathSuggestions } = useCollectionEditorController();
@@ -99,6 +101,7 @@ export function CollectionEditorWelcomeView({
99
101
  onContinue();
100
102
  }}
101
103
  generateCollection={generateCollection}
104
+ onAnalyticsEvent={onAnalyticsEvent}
102
105
  trigger={
103
106
  <Button
104
107
  variant="outlined"
@@ -17,7 +17,9 @@ import {
17
17
  DebouncedTextField,
18
18
  Dialog,
19
19
  ExpandablePanel,
20
+ HistoryIcon,
20
21
  IconButton,
22
+ SearchIcon,
21
23
  Select,
22
24
  SelectItem,
23
25
  TextField,
@@ -179,10 +181,10 @@ export function GeneralSettingsForm({
179
181
  {/* Collection ID */}
180
182
  <div className={"col-span-12"}>
181
183
  <Field name={"id"}
182
- as={DebouncedTextField}
183
- disabled={!isNewCollection}
184
- label={"Collection ID"}
185
- error={showErrors && Boolean(errors.id)} />
184
+ as={DebouncedTextField}
185
+ disabled={!isNewCollection}
186
+ label={"Collection ID"}
187
+ error={showErrors && Boolean(errors.id)} />
186
188
  <FieldCaption error={touched.id && Boolean(errors.id)}>
187
189
  {touched.id && Boolean(errors.id) ? errors.id : "This ID identifies this collection. Typically the same as the path."}
188
190
  </FieldCaption>
@@ -218,9 +220,9 @@ export function GeneralSettingsForm({
218
220
  position={"start"}
219
221
  size={"large"}
220
222
  allowIndeterminate={true}
221
- label={values.history === null || values.history === undefined ? "Document history revisions enabled if enabled globally" : (
223
+ label={<span className="flex items-center gap-2"><HistoryIcon size={"smallest"} />{values.history === null || values.history === undefined ? "Document history revisions enabled if enabled globally" : (
222
224
  values.history ? "Document history revisions ENABLED" : "Document history revisions NOT enabled"
223
- )}
225
+ )}</span>}
224
226
  onValueChange={(v) => setFieldValue("history", v)}
225
227
  value={values.history === undefined ? null : values.history}
226
228
  />
@@ -283,7 +285,7 @@ export function GeneralSettingsForm({
283
285
  <BooleanSwitchWithLabel
284
286
  position={"start"}
285
287
  size={"large"}
286
- label="Enable text search for this collection"
288
+ label={<span className="flex items-center gap-2"><SearchIcon size={"smallest"} />Enable text search for this collection</span>}
287
289
  onValueChange={(v) => setFieldValue("textSearchEnabled", v)}
288
290
  value={values.textSearchEnabled ?? false}
289
291
  />
@@ -313,7 +313,7 @@ export function PropertyFormDialog({
313
313
  onOkClicked?: () => void;
314
314
  onCancel?: () => void;
315
315
  }) {
316
- const formexRef = useRef<FormexController<PropertyWithId>>();
316
+ const formexRef = useRef<FormexController<PropertyWithId>>(undefined);
317
317
  const getController = (helpers: FormexController<PropertyWithId>) => {
318
318
  formexRef.current = helpers;
319
319
  };