@firecms/collection_editor 3.0.0-canary.241 → 3.0.0-canary.244

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.
@@ -1,6 +1,5 @@
1
1
  import React from "react";
2
2
  import { AdditionalFieldDelegate, CMSType, PropertiesOrBuilders, PropertyOrBuilder } from "@firecms/core";
3
- import { DraggableProvided } from "@hello-pangea/dnd";
4
3
  export declare const PropertyTree: React.MemoExoticComponent<(<M extends {
5
4
  [Key: string]: CMSType;
6
5
  }>({ namespace, selectedPropertyKey, onPropertyClick, properties, propertiesOrder: propertiesOrderProp, additionalFields, errors, onPropertyMove, onPropertyRemove, className, inferredPropertyKeys, collectionEditable }: {
@@ -17,13 +16,13 @@ export declare const PropertyTree: React.MemoExoticComponent<(<M extends {
17
16
  inferredPropertyKeys?: string[];
18
17
  collectionEditable: boolean;
19
18
  }) => import("react/jsx-runtime").JSX.Element)>;
20
- export declare function PropertyTreeEntry({ propertyKey, namespace, propertyOrBuilder, additionalField, provided, selectedPropertyKey, errors, onPropertyClick, onPropertyMove, onPropertyRemove, inferredPropertyKeys, collectionEditable }: {
19
+ export declare function PropertyTreeEntry({ id, propertyKey, namespace, propertyOrBuilder, additionalField, selectedPropertyKey, errors, onPropertyClick, onPropertyMove, onPropertyRemove, inferredPropertyKeys, collectionEditable }: {
20
+ id: string;
21
21
  propertyKey: string;
22
22
  namespace?: string;
23
23
  propertyOrBuilder: PropertyOrBuilder;
24
24
  additionalField?: AdditionalFieldDelegate<any>;
25
25
  selectedPropertyKey?: string;
26
- provided: DraggableProvided;
27
26
  errors: Record<string, any>;
28
27
  onPropertyClick?: (propertyKey: string, namespace?: string) => void;
29
28
  onPropertyMove?: (propertiesOrder: string[], namespace?: string) => void;
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@firecms/collection_editor",
3
3
  "type": "module",
4
- "version": "3.0.0-canary.241",
4
+ "version": "3.0.0-canary.244",
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.0.0-canary.241",
11
- "@firecms/data_import": "^3.0.0-canary.241",
12
- "@firecms/data_import_export": "^3.0.0-canary.241",
13
- "@firecms/formex": "^3.0.0-canary.241",
14
- "@firecms/schema_inference": "^3.0.0-canary.241",
15
- "@firecms/ui": "^3.0.0-canary.241",
10
+ "@firecms/data_export": "^3.0.0-canary.244",
11
+ "@firecms/data_import": "^3.0.0-canary.244",
12
+ "@firecms/data_import_export": "^3.0.0-canary.244",
13
+ "@firecms/formex": "^3.0.0-canary.244",
14
+ "@firecms/schema_inference": "^3.0.0-canary.244",
15
+ "@firecms/ui": "^3.0.0-canary.244",
16
16
  "@hello-pangea/dnd": "^17.0.0",
17
17
  "json5": "^2.2.3",
18
18
  "prism-react-renderer": "^2.4.1"
@@ -70,5 +70,5 @@
70
70
  "publishConfig": {
71
71
  "access": "public"
72
72
  },
73
- "gitHead": "5d3c3928250947d0363073d612a8c15f9bc10459"
73
+ "gitHead": "6b3dcdc942e0fd64e3023b5361c34ec2ac2bb1d3"
74
74
  }
@@ -195,6 +195,7 @@ export function CollectionDetailsForm({
195
195
  {groupOptions?.map((group, index) => {
196
196
  return <AutocompleteItem
197
197
  key={index + "_" + group}
198
+ className={"pr-6 pl-14"}
198
199
  onClick={() => {
199
200
  setAutoCompleteOpen(false);
200
201
  setFieldValue("group", group ?? null);
@@ -223,11 +224,11 @@ export function CollectionDetailsForm({
223
224
  position={"start"}
224
225
  size={"large"}
225
226
  allowIndeterminate={true}
226
- label={values.history === null ? "Document history revisions enabled if enabled globally" : (
227
+ label={values.history === null || values.history === undefined ? "Document history revisions enabled if enabled globally" : (
227
228
  values.history ? "Document history revisions ENABLED" : "Document history revisions NOT enabled"
228
229
  )}
229
230
  onValueChange={(v) => setFieldValue("history", v)}
230
- value={values.history ?? null}
231
+ value={values.history === undefined ? null : values.history}
231
232
  />
232
233
  <FieldCaption>
233
234
  When enabled, each document in this collection will have a history of changes.
@@ -343,6 +344,20 @@ export function CollectionDetailsForm({
343
344
  ))}
344
345
  </Select>
345
346
  </div>
347
+
348
+ <div className={"col-span-12"}>
349
+ <BooleanSwitchWithLabel
350
+ position={"start"}
351
+ size={"large"}
352
+ label={values.includeJsonView === undefined || values.includeJsonView ? "Include JSON view" : "Do not include JSON view"}
353
+ onValueChange={(v) => setFieldValue("includeJsonView", v)}
354
+ value={values.includeJsonView === undefined ? true : values.includeJsonView}
355
+ />
356
+ <FieldCaption>
357
+ Include the JSON representation of the document.
358
+ </FieldCaption>
359
+ </div>
360
+
346
361
  <div className={"col-span-12"}>
347
362
  <Select
348
363
  name="customId"
@@ -4,14 +4,30 @@ import equal from "react-fast-compare"
4
4
  import {
5
5
  AdditionalFieldDelegate,
6
6
  CMSType,
7
- ErrorBoundary,
8
7
  isPropertyBuilder,
9
8
  PropertiesOrBuilders,
10
9
  PropertyOrBuilder
11
10
  } from "@firecms/core";
12
11
  import { AutorenewIcon, defaultBorderMixin, DragHandleIcon, IconButton, RemoveIcon, Tooltip } from "@firecms/ui";
13
12
  import { NonEditablePropertyPreview, PropertyFieldPreview } from "./PropertyFieldPreview";
14
- import { DragDropContext, Draggable, DraggableProvided, Droppable } from "@hello-pangea/dnd";
13
+ import {
14
+ closestCenter,
15
+ DndContext,
16
+ DragEndEvent,
17
+ KeyboardSensor,
18
+ PointerSensor,
19
+ useSensor,
20
+ useSensors
21
+ } from "@dnd-kit/core";
22
+ import {
23
+ SortableContext,
24
+ sortableKeyboardCoordinates,
25
+ useSortable,
26
+ verticalListSortingStrategy
27
+ } from "@dnd-kit/sortable";
28
+ import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
29
+
30
+ import { CSS } from "@dnd-kit/utilities";
15
31
  import { getFullId, getFullIdPath } from "./util";
16
32
  import { editableProperty } from "../../utils/entities";
17
33
 
@@ -48,103 +64,109 @@ export const PropertyTree = React.memo(
48
64
 
49
65
  const propertiesOrder = propertiesOrderProp ?? Object.keys(properties);
50
66
 
51
- const onDragEnd = (result: any) => {
52
- // dropped outside the list
53
- if (!result.destination) {
67
+ const sensors = useSensors(
68
+ useSensor(PointerSensor, {
69
+ activationConstraint: {
70
+ distance: 5,
71
+ }
72
+ }),
73
+ useSensor(KeyboardSensor, {
74
+ coordinateGetter: sortableKeyboardCoordinates,
75
+ })
76
+ );
77
+
78
+ const handleDragEnd = (event: DragEndEvent) => {
79
+ const {
80
+ active,
81
+ over
82
+ } = event;
83
+
84
+ if (!over || active.id === over.id) {
54
85
  return;
55
86
  }
56
- const startIndex = result.source.index;
57
- const endIndex = result.destination.index;
58
-
59
- const newPropertiesOrder = Array.from(propertiesOrder);
60
- const [removed] = newPropertiesOrder.splice(startIndex, 1);
61
- newPropertiesOrder.splice(endIndex, 0, removed);
62
- if (onPropertyMove)
63
- onPropertyMove(newPropertiesOrder, namespace);
64
- }
87
+
88
+ const activeId = String(active.id);
89
+ const overId = String(over.id);
90
+
91
+ // Extract property keys from the full IDs
92
+ const activeKey = activeId.includes(".") ? activeId.split(".").pop() : activeId;
93
+ const overKey = overId.includes(".") ? overId.split(".").pop() : overId;
94
+
95
+ if (!activeKey || !overKey) return;
96
+
97
+ const oldIndex = propertiesOrder.indexOf(activeKey);
98
+ const newIndex = propertiesOrder.indexOf(overKey);
99
+
100
+ if (oldIndex !== -1 && newIndex !== -1) {
101
+ const newPropertiesOrder = [...propertiesOrder];
102
+ const [removed] = newPropertiesOrder.splice(oldIndex, 1);
103
+ newPropertiesOrder.splice(newIndex, 0, removed);
104
+
105
+ if (onPropertyMove) {
106
+ onPropertyMove(newPropertiesOrder, namespace);
107
+ }
108
+ }
109
+ };
110
+
111
+ const items = propertiesOrder.map(key => getFullId(key, namespace));
65
112
 
66
113
  return (
67
- <>
68
-
69
- <DragDropContext onDragEnd={onDragEnd}>
70
- <Droppable droppableId={`droppable_${namespace}`}>
71
- {(droppableProvided, droppableSnapshot) => (
72
- <div
73
- {...droppableProvided.droppableProps}
74
- ref={droppableProvided.innerRef}
75
- className={className}>
76
- {propertiesOrder && propertiesOrder
77
- .map((propertyKey: string, index: number) => {
78
- const property = properties[propertyKey] as PropertyOrBuilder;
79
- const additionalField = additionalFields?.find(field => field.key === propertyKey);
80
-
81
- if (!property && !additionalField) {
82
- console.warn(`Property ${propertyKey} not found in properties or additionalFields`);
83
- return null;
84
- }
85
- return (
86
- <Draggable
87
- key={`array_field_${namespace}_${propertyKey}}`}
88
- draggableId={`array_field_${namespace}_${propertyKey}}`}
89
- index={index}>
90
- {(provided, snapshot) => {
91
- return (
92
- <ErrorBoundary>
93
- <PropertyTreeEntry
94
- propertyKey={propertyKey as string}
95
- propertyOrBuilder={property}
96
- additionalField={additionalField}
97
- provided={provided}
98
- errors={errors}
99
- namespace={namespace}
100
- inferredPropertyKeys={inferredPropertyKeys}
101
- onPropertyMove={onPropertyMove}
102
- onPropertyRemove={onPropertyRemove}
103
- onPropertyClick={snapshot.isDragging ? undefined : onPropertyClick}
104
- selectedPropertyKey={selectedPropertyKey}
105
- collectionEditable={collectionEditable}
106
- />
107
- </ErrorBoundary>
108
- );
109
- }}
110
- </Draggable>);
111
- }).filter(Boolean)}
112
-
113
- {droppableProvided.placeholder}
114
-
115
- </div>
116
- )}
117
- </Droppable>
118
- </DragDropContext>
119
-
120
- </>
114
+ <DndContext
115
+ sensors={sensors}
116
+ collisionDetection={closestCenter}
117
+ onDragEnd={handleDragEnd}
118
+ modifiers={[restrictToVerticalAxis]}
119
+ >
120
+ <SortableContext
121
+ items={items}
122
+ strategy={verticalListSortingStrategy}
123
+ >
124
+ <div className={className}>
125
+
126
+ {propertiesOrder && propertiesOrder
127
+ .map((propertyKey: string, index: number) => {
128
+ const property = properties[propertyKey] as PropertyOrBuilder;
129
+ const additionalField = additionalFields?.find(field => field.key === propertyKey);
130
+
131
+ if (!property && !additionalField) {
132
+ console.warn(`Property ${propertyKey} not found in properties or additionalFields`);
133
+ return null;
134
+ }
135
+
136
+ const id = getFullId(propertyKey, namespace);
137
+
138
+ return (
139
+ <PropertyTreeEntry
140
+ key={id}
141
+ id={id}
142
+ propertyKey={propertyKey}
143
+ propertyOrBuilder={property}
144
+ additionalField={additionalField}
145
+ errors={errors}
146
+ namespace={namespace}
147
+ inferredPropertyKeys={inferredPropertyKeys}
148
+ onPropertyMove={onPropertyMove}
149
+ onPropertyRemove={onPropertyRemove}
150
+ onPropertyClick={onPropertyClick}
151
+ selectedPropertyKey={selectedPropertyKey}
152
+ collectionEditable={collectionEditable}
153
+ />
154
+ );
155
+ }).filter(Boolean)}
156
+ </div>
157
+ </SortableContext>
158
+ </DndContext>
121
159
  );
122
160
  },
123
- (prevProps, nextProps) => {
124
-
125
- const isSelected = nextProps.selectedPropertyKey?.startsWith(nextProps.namespace ?? "");
126
- const wasSelected = prevProps.selectedPropertyKey?.startsWith(prevProps.namespace ?? "");
127
- if (isSelected || wasSelected)
128
- return false;
129
-
130
- return equal(prevProps.properties, nextProps.properties) &&
131
- prevProps.propertiesOrder === nextProps.propertiesOrder &&
132
- equal(prevProps.additionalFields, nextProps.additionalFields) &&
133
- equal(prevProps.errors, nextProps.errors) &&
134
- equal(prevProps.onPropertyClick, nextProps.onPropertyClick) &&
135
- // equal(prevProps.onPropertyMove, nextProps.onPropertyMove) &&
136
- // equal(prevProps.onPropertyRemove, nextProps.onPropertyRemove) &&
137
- prevProps.namespace === nextProps.namespace &&
138
- prevProps.collectionEditable === nextProps.collectionEditable;
139
- }
161
+ equal
140
162
  );
141
163
 
142
164
  export function PropertyTreeEntry({
165
+ id,
143
166
  propertyKey,
144
167
  namespace,
145
168
  propertyOrBuilder,
146
169
  additionalField,
147
- provided,
148
170
  selectedPropertyKey,
149
171
  errors,
150
172
  onPropertyClick,
@@ -153,12 +175,12 @@ export function PropertyTreeEntry({
153
175
  inferredPropertyKeys,
154
176
  collectionEditable
155
177
  }: {
178
+ id: string;
156
179
  propertyKey: string;
157
180
  namespace?: string;
158
181
  propertyOrBuilder: PropertyOrBuilder;
159
182
  additionalField?: AdditionalFieldDelegate<any>;
160
183
  selectedPropertyKey?: string;
161
- provided: DraggableProvided;
162
184
  errors: Record<string, any>;
163
185
  onPropertyClick?: (propertyKey: string, namespace?: string) => void;
164
186
  onPropertyMove?: (propertiesOrder: string[], namespace?: string) => void;
@@ -167,8 +189,27 @@ export function PropertyTreeEntry({
167
189
  collectionEditable: boolean;
168
190
  }) {
169
191
 
192
+ const {
193
+ attributes,
194
+ listeners,
195
+ setNodeRef,
196
+ transform,
197
+ transition,
198
+ isDragging
199
+ } = useSortable({
200
+ id
201
+ });
202
+
203
+ const style = {
204
+ // Key change: use Translate instead of Transform to prevent stretching
205
+ transform: CSS.Translate.toString(transform),
206
+ transition,
207
+ zIndex: isDragging ? 10 : undefined,
208
+ position: "relative" as const,
209
+ };
210
+
170
211
  const isPropertyInferred = inferredPropertyKeys?.includes(namespace ? `${namespace}.${propertyKey}` : propertyKey);
171
- const fullId = getFullId(propertyKey, namespace);
212
+ const fullId = id;
172
213
  const fullIdPath = getFullIdPath(propertyKey, namespace);
173
214
  const hasError = fullIdPath in errors;
174
215
 
@@ -190,65 +231,68 @@ export function PropertyTreeEntry({
190
231
  }
191
232
  }
192
233
 
193
- // const hasError = fullId ? getIn(errors, idToPropertiesPath(fullId)) : false;
194
234
  const selected = selectedPropertyKey === fullId;
195
235
  const editable = propertyOrBuilder && ((collectionEditable && !isPropertyBuilder(propertyOrBuilder)) || editableProperty(propertyOrBuilder));
196
236
 
197
237
  return (
198
238
  <div
199
- ref={provided.innerRef}
200
- {...provided.draggableProps}
201
- {...provided.dragHandleProps}
239
+ ref={setNodeRef}
240
+ style={style}
202
241
  className="relative -ml-8"
203
242
  >
204
- {subtree && <div
205
- className={"absolute border-l " + defaultBorderMixin}
206
- style={{
207
- left: "32px",
208
- top: "64px",
209
- bottom: "16px"
210
- }}/>}
211
-
212
- {!isPropertyBuilder(propertyOrBuilder) && !additionalField && editable
213
- ? <PropertyFieldPreview
214
- property={propertyOrBuilder}
215
- onClick={onPropertyClick ? () => onPropertyClick(propertyKey, namespace) : undefined}
216
- includeName={true}
217
- selected={selected}
218
- hasError={hasError}/>
219
- : <NonEditablePropertyPreview name={propertyKey}
220
- property={propertyOrBuilder}
221
- onClick={onPropertyClick ? () => onPropertyClick(propertyKey, namespace) : undefined}
222
- selected={selected}/>}
223
-
224
- <div className="absolute top-2 right-2 flex flex-row ">
225
-
226
- {isPropertyInferred && <Tooltip title={"Inferred property"}>
227
- <AutorenewIcon size="small" className={"p-2"}/>
228
- </Tooltip>}
229
-
230
- {onPropertyRemove && <Tooltip title={"Remove"}
231
- asChild={true}>
232
- <IconButton size="small"
233
- color="inherit"
234
- onClick={() => onPropertyRemove(propertyKey, namespace)}>
235
- <RemoveIcon size={"small"}/>
236
- </IconButton>
237
- </Tooltip>}
238
-
239
- {onPropertyMove && <Tooltip title={"Move"}
240
- asChild={true}>
241
- <IconButton
242
- component={"span"}
243
- size="small"
244
- >
245
- <DragHandleIcon size={"small"}/>
246
- </IconButton>
247
- </Tooltip>}
248
- </div>
243
+ <div className="relative">
244
+ {subtree && <div
245
+ className={"absolute border-l " + defaultBorderMixin}
246
+ style={{
247
+ left: "32px",
248
+ top: "64px",
249
+ bottom: "16px"
250
+ }}/>}
249
251
 
250
- {subtree && <div className={"ml-16"}>{subtree}</div>}
252
+ <div>
253
+ {!isPropertyBuilder(propertyOrBuilder) && !additionalField && editable
254
+ ? <PropertyFieldPreview
255
+ property={propertyOrBuilder}
256
+ onClick={onPropertyClick ? () => onPropertyClick(propertyKey, namespace) : undefined}
257
+ includeName={true}
258
+ selected={selected}
259
+ hasError={hasError}/>
260
+ : <NonEditablePropertyPreview name={propertyKey}
261
+ property={propertyOrBuilder}
262
+ onClick={onPropertyClick ? () => onPropertyClick(propertyKey, namespace) : undefined}
263
+ selected={selected}/>}
264
+ </div>
265
+
266
+ <div className="absolute top-2 right-2 flex flex-row">
267
+ {isPropertyInferred && <Tooltip title={"Inferred property"}>
268
+ <AutorenewIcon size="small" className={"p-2"}/>
269
+ </Tooltip>}
270
+
271
+ {onPropertyRemove && !isPropertyInferred && <Tooltip title={"Remove"}
272
+ asChild={true}>
273
+ <IconButton size="small"
274
+ color="inherit"
275
+ onClick={() => onPropertyRemove(propertyKey, namespace)}>
276
+ <RemoveIcon size={"small"}/>
277
+ </IconButton>
278
+ </Tooltip>}
279
+
280
+ {onPropertyMove && <Tooltip title={"Move"}
281
+ asChild={true}>
282
+ <IconButton
283
+ component={"span"}
284
+ size="small"
285
+ {...attributes}
286
+ {...listeners}
287
+ >
288
+ <DragHandleIcon size={"small"}/>
289
+ </IconButton>
290
+ </Tooltip>}
291
+
292
+ </div>
293
+
294
+ {subtree && <div className={"ml-16"}>{subtree}</div>}
295
+ </div>
251
296
  </div>
252
297
  );
253
-
254
298
  }
@@ -107,7 +107,7 @@ export function MapPropertyField({ disabled, getData, allowDataInference, proper
107
107
  <div className={"col-span-12"}>
108
108
  <BooleanSwitchWithLabel
109
109
  position={"start"}
110
- size={"small"}
110
+ size={"medium"}
111
111
  label="Spread children as columns"
112
112
  onValueChange={(v) => setFieldValue("spreadChildren", v)}
113
113
  value={values.spreadChildren ?? false}
@@ -19,6 +19,7 @@ export function AdvancedPropertyValidation({ disabled }: {
19
19
  {({ field, form }: FormexFieldProps) => {
20
20
  return <SwitchControl
21
21
  label={"Hide from collection"}
22
+ size={"medium"}
22
23
  disabled={disabled}
23
24
  form={form}
24
25
  tooltip={"Hide this field from the collection view. It will still be visible in the form view"}
@@ -33,6 +34,7 @@ export function AdvancedPropertyValidation({ disabled }: {
33
34
  {({ field, form }: FormexFieldProps) => {
34
35
  return <SwitchControl
35
36
  label={"Read only"}
37
+ size={"medium"}
36
38
  disabled={disabled}
37
39
  tooltip={"Is this a read only field. Display only as a preview"}
38
40
  form={form}