jazz-tools 0.19.0 → 0.19.2

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 (84) hide show
  1. package/.turbo/turbo-build.log +53 -53
  2. package/CHANGELOG.md +21 -0
  3. package/dist/{chunk-P3YLNFN4.js → chunk-NCNM6UDZ.js} +61 -22
  4. package/dist/chunk-NCNM6UDZ.js.map +1 -0
  5. package/dist/index.js +1 -1
  6. package/dist/inspector/{custom-element-QESCMFY7.js → custom-element-ABVPHX53.js} +1118 -465
  7. package/dist/inspector/custom-element-ABVPHX53.js.map +1 -0
  8. package/dist/inspector/index.js +1090 -437
  9. package/dist/inspector/index.js.map +1 -1
  10. package/dist/inspector/register-custom-element.js +1 -1
  11. package/dist/inspector/tests/utils/history.test.d.ts +2 -0
  12. package/dist/inspector/tests/utils/history.test.d.ts.map +1 -0
  13. package/dist/inspector/tests/viewer/co-value-editor.test.d.ts +2 -0
  14. package/dist/inspector/tests/viewer/co-value-editor.test.d.ts.map +1 -0
  15. package/dist/inspector/tests/viewer/comap-view.test.d.ts +2 -0
  16. package/dist/inspector/tests/viewer/comap-view.test.d.ts.map +1 -0
  17. package/dist/inspector/ui/icon.d.ts +6 -0
  18. package/dist/inspector/ui/icon.d.ts.map +1 -1
  19. package/dist/inspector/ui/icons/add-icon.d.ts +2 -0
  20. package/dist/inspector/ui/icons/add-icon.d.ts.map +1 -0
  21. package/dist/inspector/ui/icons/edit-icon.d.ts +2 -0
  22. package/dist/inspector/ui/icons/edit-icon.d.ts.map +1 -0
  23. package/dist/inspector/ui/icons/history.d.ts +2 -0
  24. package/dist/inspector/ui/icons/history.d.ts.map +1 -0
  25. package/dist/inspector/utils/history.d.ts +3 -0
  26. package/dist/inspector/utils/history.d.ts.map +1 -0
  27. package/dist/inspector/utils/transactions-changes.d.ts +38 -0
  28. package/dist/inspector/utils/transactions-changes.d.ts.map +1 -0
  29. package/dist/inspector/viewer/co-map-view.d.ts +9 -0
  30. package/dist/inspector/viewer/co-map-view.d.ts.map +1 -0
  31. package/dist/inspector/viewer/co-value-editor.d.ts +10 -0
  32. package/dist/inspector/viewer/co-value-editor.d.ts.map +1 -0
  33. package/dist/inspector/viewer/grid-view.d.ts +3 -2
  34. package/dist/inspector/viewer/grid-view.d.ts.map +1 -1
  35. package/dist/inspector/viewer/history-view.d.ts.map +1 -1
  36. package/dist/inspector/viewer/page.d.ts.map +1 -1
  37. package/dist/testing.js +1 -1
  38. package/dist/tools/coValues/CoFieldInit.d.ts +2 -1
  39. package/dist/tools/coValues/CoFieldInit.d.ts.map +1 -1
  40. package/dist/tools/coValues/deepLoading.d.ts +8 -7
  41. package/dist/tools/coValues/deepLoading.d.ts.map +1 -1
  42. package/dist/tools/coValues/interfaces.d.ts +3 -3
  43. package/dist/tools/coValues/interfaces.d.ts.map +1 -1
  44. package/dist/tools/coValues/schemaUnion.d.ts +6 -9
  45. package/dist/tools/coValues/schemaUnion.d.ts.map +1 -1
  46. package/dist/tools/implementation/zodSchema/schemaTypes/CoDiscriminatedUnionSchema.d.ts +18 -7
  47. package/dist/tools/implementation/zodSchema/schemaTypes/CoDiscriminatedUnionSchema.d.ts.map +1 -1
  48. package/dist/tools/implementation/zodSchema/typeConverters/CoFieldSchemaInit.d.ts +3 -2
  49. package/dist/tools/implementation/zodSchema/typeConverters/CoFieldSchemaInit.d.ts.map +1 -1
  50. package/dist/tools/implementation/zodSchema/unionUtils.d.ts.map +1 -1
  51. package/dist/tools/subscribe/SubscriptionScope.d.ts +1 -0
  52. package/dist/tools/subscribe/SubscriptionScope.d.ts.map +1 -1
  53. package/package.json +4 -4
  54. package/src/inspector/tests/utils/history.test.ts +401 -0
  55. package/src/inspector/tests/viewer/co-value-editor.test.tsx +903 -0
  56. package/src/inspector/tests/viewer/comap-view.test.tsx +581 -0
  57. package/src/inspector/ui/icon.tsx +6 -0
  58. package/src/inspector/ui/icons/add-icon.tsx +21 -0
  59. package/src/inspector/ui/icons/edit-icon.tsx +17 -0
  60. package/src/inspector/ui/icons/history.tsx +28 -0
  61. package/src/inspector/ui/modal.tsx +3 -3
  62. package/src/inspector/utils/history.ts +49 -0
  63. package/src/inspector/utils/transactions-changes.ts +98 -0
  64. package/src/inspector/viewer/co-map-view.tsx +312 -0
  65. package/src/inspector/viewer/co-value-editor.tsx +164 -0
  66. package/src/inspector/viewer/grid-view.tsx +139 -10
  67. package/src/inspector/viewer/history-view.tsx +16 -118
  68. package/src/inspector/viewer/page.tsx +13 -0
  69. package/src/react-core/tests/usePassPhraseAuth.test.ts +1 -1
  70. package/src/tools/coValues/CoFieldInit.ts +6 -3
  71. package/src/tools/coValues/coList.ts +1 -1
  72. package/src/tools/coValues/deepLoading.ts +85 -71
  73. package/src/tools/coValues/interfaces.ts +3 -3
  74. package/src/tools/coValues/schemaUnion.ts +19 -14
  75. package/src/tools/implementation/zodSchema/schemaTypes/CoDiscriminatedUnionSchema.ts +69 -9
  76. package/src/tools/implementation/zodSchema/typeConverters/CoFieldSchemaInit.ts +12 -7
  77. package/src/tools/implementation/zodSchema/unionUtils.ts +35 -4
  78. package/src/tools/subscribe/SubscriptionScope.ts +3 -14
  79. package/src/tools/tests/coDiscriminatedUnion.test.ts +347 -5
  80. package/src/tools/tests/coVector.test.ts +43 -0
  81. package/src/tools/tests/deepLoading.test.ts +55 -59
  82. package/src/tools/tests/schema.resolved.test.ts +70 -1
  83. package/dist/chunk-P3YLNFN4.js.map +0 -1
  84. package/dist/inspector/custom-element-QESCMFY7.js.map +0 -1
@@ -0,0 +1,49 @@
1
+ import type { JsonObject, JsonValue, RawCoMap, Role } from "cojson";
2
+ import type { MapOpPayload } from "cojson/dist/coValues/coMap.js";
3
+
4
+ export function restoreCoMapToTimestamp(
5
+ coValue: RawCoMap,
6
+ timestamp: number,
7
+ removeUnknownProperties: boolean,
8
+ ): void {
9
+ const myRole = coValue.group.myRole();
10
+
11
+ if (
12
+ myRole === undefined ||
13
+ !(["admin", "manager", "writer", "writerOnly"] as Role[]).includes(myRole)
14
+ ) {
15
+ return;
16
+ }
17
+
18
+ const newCoValue = coValue.atTime(timestamp).toJSON() as JsonObject | null;
19
+ const oldCoValue = coValue.toJSON() as JsonObject;
20
+
21
+ if (newCoValue === null) return;
22
+
23
+ let changes: MapOpPayload<string, JsonValue | undefined>[] = [];
24
+
25
+ if (removeUnknownProperties) {
26
+ for (const key in oldCoValue) {
27
+ if (!(key in newCoValue)) {
28
+ changes.push({
29
+ op: "del",
30
+ key,
31
+ });
32
+ }
33
+ }
34
+ }
35
+
36
+ for (const key in newCoValue) {
37
+ if (newCoValue[key] !== oldCoValue[key]) {
38
+ changes.push({
39
+ op: "set",
40
+ key,
41
+ value: newCoValue[key],
42
+ });
43
+ }
44
+ }
45
+
46
+ if (changes.length > 0) {
47
+ coValue.core.makeTransaction(changes, "private");
48
+ }
49
+ }
@@ -0,0 +1,98 @@
1
+ import type {
2
+ AccountRole,
3
+ BinaryStreamStart,
4
+ CoID,
5
+ RawCoValue,
6
+ Role,
7
+ } from "cojson";
8
+ import type { ListOpPayload } from "cojson/dist/coValues/coList.js";
9
+ import type { MapOpPayload } from "cojson/dist/coValues/coMap.js";
10
+ import type {
11
+ BinaryStreamChunk,
12
+ BinaryStreamEnd,
13
+ } from "cojson/dist/coValues/coStream.js";
14
+ import { isCoId } from "../viewer/types";
15
+
16
+ export const isGroupExtension = (
17
+ change: any,
18
+ ): change is Extract<
19
+ MapOpPayload<`child_${string}`, "extend">,
20
+ { op: "set" }
21
+ > => {
22
+ return change?.op === "set" && change?.value === "extend";
23
+ };
24
+
25
+ export const isGroupExtendRevocation = (
26
+ change: any,
27
+ ): change is Extract<
28
+ MapOpPayload<`child_${string}`, "revoked">,
29
+ { op: "set" }
30
+ > => {
31
+ return change?.op === "set" && change?.value === "revoked";
32
+ };
33
+
34
+ export const isGroupPromotion = (
35
+ change: any,
36
+ ): change is Extract<
37
+ MapOpPayload<`parent_co_${string}`, AccountRole>,
38
+ { op: "set" }
39
+ > => {
40
+ return change?.op === "set" && change?.key.startsWith("parent_co_");
41
+ };
42
+
43
+ export const isUserPromotion = (
44
+ change: any,
45
+ ): change is Extract<MapOpPayload<CoID<RawCoValue>, Role>, { op: "set" }> => {
46
+ return (
47
+ change?.op === "set" && (isCoId(change?.key) || change?.key === "everyone")
48
+ );
49
+ };
50
+
51
+ export const isKeyRevelation = (
52
+ change: any,
53
+ ): change is Extract<
54
+ MapOpPayload<`${string}_for_${string}`, string>,
55
+ { op: "set" }
56
+ > => {
57
+ return change?.op === "set" && change?.key.includes("_for_");
58
+ };
59
+
60
+ export const isPropertySet = (
61
+ change: any,
62
+ ): change is Extract<MapOpPayload<string, any>, { op: "set" }> => {
63
+ return change?.op === "set" && "key" in change && "value" in change;
64
+ };
65
+ export const isPropertyDeletion = (
66
+ change: any,
67
+ ): change is Extract<MapOpPayload<string, any>, { op: "del" }> => {
68
+ return change?.op === "del" && "key" in change;
69
+ };
70
+
71
+ export const isItemAppend = (
72
+ change: any,
73
+ ): change is Extract<ListOpPayload<any>, { op: "app" }> => {
74
+ return change?.op === "app" && "after" in change && "value" in change;
75
+ };
76
+ export const isItemPrepend = (
77
+ change: any,
78
+ ): change is Extract<ListOpPayload<any>, { op: "pre" }> => {
79
+ return change?.op === "pre" && "before" in change && "value" in change;
80
+ };
81
+
82
+ export const isItemDeletion = (
83
+ change: any,
84
+ ): change is Extract<ListOpPayload<any>, { op: "del" }> => {
85
+ return change?.op === "del" && "insertion" in change;
86
+ };
87
+
88
+ export const isStreamStart = (change: any): change is BinaryStreamStart => {
89
+ return change?.type === "start" && "mimeType" in change;
90
+ };
91
+
92
+ export const isStreamChunk = (change: any): change is BinaryStreamChunk => {
93
+ return change?.type === "chunk" && "chunk" in change;
94
+ };
95
+
96
+ export const isStreamEnd = (change: any): change is BinaryStreamEnd => {
97
+ return change?.type === "end";
98
+ };
@@ -0,0 +1,312 @@
1
+ import { JsonObject, LocalNode, RawCoMap } from "cojson";
2
+ import { PageInfo } from "./types";
3
+ import { GridView } from "./grid-view.js";
4
+ import { useState, useMemo } from "react";
5
+ import { Button, Icon, Input, Modal } from "../ui";
6
+ import { styled } from "goober";
7
+ import { restoreCoMapToTimestamp } from "../utils/history";
8
+ import { CoValueEditor } from "./co-value-editor.js";
9
+
10
+ export function CoMapView({
11
+ coValue,
12
+ data,
13
+ node,
14
+ onNavigate,
15
+ }: {
16
+ coValue: RawCoMap;
17
+ data: JsonObject;
18
+ node: LocalNode;
19
+ onNavigate: (pages: PageInfo[]) => void;
20
+ }) {
21
+ return (
22
+ <>
23
+ <GridView
24
+ data={data}
25
+ onNavigate={onNavigate}
26
+ node={node}
27
+ coValue={coValue}
28
+ />
29
+ <div>
30
+ <AddPropertyModal coValue={coValue} node={node} />{" "}
31
+ <RestoreSnapshotModal coValue={coValue} />
32
+ </div>
33
+ </>
34
+ );
35
+ }
36
+
37
+ function AddPropertyModal({
38
+ coValue,
39
+ node,
40
+ }: {
41
+ coValue: RawCoMap;
42
+ node: LocalNode;
43
+ }) {
44
+ const [isAddPropertyModalOpen, setIsAddPropertyModalOpen] = useState(false);
45
+ const [propertyName, setPropertyName] = useState("");
46
+
47
+ const openAddPropertyModal = () => {
48
+ setIsAddPropertyModalOpen(true);
49
+ setPropertyName("");
50
+ };
51
+
52
+ const handleCancel = () => {
53
+ setIsAddPropertyModalOpen(false);
54
+ setPropertyName("");
55
+ };
56
+
57
+ return (
58
+ <>
59
+ <Button
60
+ title="Add Property"
61
+ variant="secondary"
62
+ onClick={openAddPropertyModal}
63
+ >
64
+ <Icon name="edit" />
65
+ </Button>
66
+
67
+ <Modal
68
+ isOpen={isAddPropertyModalOpen}
69
+ onClose={handleCancel}
70
+ heading="Add Property"
71
+ showButtons={false}
72
+ >
73
+ <Input
74
+ label="Property Name"
75
+ value={propertyName}
76
+ onChange={(e) => setPropertyName(e.target.value)}
77
+ placeholder="Enter property name"
78
+ />
79
+ {propertyName && (
80
+ <EditorContainer>
81
+ <CoValueEditor
82
+ node={node}
83
+ property={propertyName}
84
+ value={undefined}
85
+ coValue={coValue}
86
+ onCancel={handleCancel}
87
+ />
88
+ </EditorContainer>
89
+ )}
90
+ </Modal>
91
+ </>
92
+ );
93
+ }
94
+
95
+ function RestoreSnapshotModal({ coValue }: { coValue: RawCoMap }) {
96
+ const [isRestoreModalOpen, setIsRestoreModalOpen] = useState(false);
97
+ const [selectedIndex, setSelectedIndex] = useState<number>(-1);
98
+ const [removeUnknownProperties, setRemoveUnknownProperties] = useState(false);
99
+
100
+ const timestamps = useMemo(
101
+ () => coValue.core.verifiedTransactions.map((tx) => tx.madeAt),
102
+ [coValue.core.verifiedTransactions.length],
103
+ );
104
+
105
+ const coMapAtSelectedIndex = useMemo(() => {
106
+ if (selectedIndex === -1) return null;
107
+ return coValue.atTime(timestamps[selectedIndex]!).toJSON() as JsonObject;
108
+ }, [coValue, timestamps, selectedIndex]);
109
+
110
+ const openRestoreModal = () => {
111
+ setIsRestoreModalOpen(true);
112
+ setSelectedIndex(timestamps.length - 1);
113
+ };
114
+
115
+ const handleRestore = () => {
116
+ if (timestamps.length < 2) return;
117
+ if (timestamps.length === 0) return;
118
+
119
+ const selectedTimestamp = timestamps[selectedIndex];
120
+ if (selectedTimestamp === undefined) return;
121
+
122
+ restoreCoMapToTimestamp(
123
+ coValue,
124
+ selectedTimestamp,
125
+ removeUnknownProperties,
126
+ );
127
+
128
+ setIsRestoreModalOpen(false);
129
+ };
130
+
131
+ const handleClose = () => {
132
+ setIsRestoreModalOpen(false);
133
+ };
134
+
135
+ return (
136
+ <>
137
+ <Button title="Timeline" variant="secondary" onClick={openRestoreModal}>
138
+ <Icon name="history" />
139
+ </Button>
140
+
141
+ <Modal
142
+ isOpen={isRestoreModalOpen}
143
+ onClose={handleClose}
144
+ heading="Timeline"
145
+ confirmText="Restore"
146
+ cancelText="Cancel"
147
+ onConfirm={handleRestore}
148
+ onCancel={handleClose}
149
+ showButtons={timestamps.length > 1}
150
+ >
151
+ {timestamps.length > 1 && (
152
+ <>
153
+ <RangeContainer>
154
+ <RangeLabel>Select Timestamp</RangeLabel>
155
+ <RangeInput
156
+ type="range"
157
+ min={0}
158
+ max={Math.max(0, timestamps.length - 1)}
159
+ value={selectedIndex}
160
+ onChange={(e) => setSelectedIndex(Number(e.target.value))}
161
+ disabled={timestamps.length === 0}
162
+ />
163
+ <TimestampDisplay>
164
+ {timestamps[selectedIndex] !== undefined
165
+ ? new Date(timestamps[selectedIndex]!).toISOString()
166
+ : "No timestamps available"}
167
+ </TimestampDisplay>
168
+ </RangeContainer>
169
+
170
+ <CheckboxContainer>
171
+ <CheckboxInput
172
+ type="checkbox"
173
+ id="remove-unknown-properties"
174
+ checked={removeUnknownProperties}
175
+ onChange={(e) => setRemoveUnknownProperties(e.target.checked)}
176
+ />
177
+ <CheckboxLabel htmlFor="remove-unknown-properties">
178
+ Remove unknown properties (properties that don't exist in the
179
+ selected snapshot)
180
+ </CheckboxLabel>
181
+ </CheckboxContainer>
182
+ </>
183
+ )}
184
+
185
+ {timestamps.length > 0 && timestamps[selectedIndex] !== undefined && (
186
+ <PreviewSection>
187
+ <PreviewLabel>State at that time:</PreviewLabel>
188
+ <PreviewPre>
189
+ {JSON.stringify(coMapAtSelectedIndex, null, 2)}
190
+ </PreviewPre>
191
+ </PreviewSection>
192
+ )}
193
+
194
+ {timestamps.length < 2 && (
195
+ <div style={{ color: "var(--j-text-color)" }}>
196
+ At least 2 timestamps are required to restore a snapshot.
197
+ </div>
198
+ )}
199
+ </Modal>
200
+ </>
201
+ );
202
+ }
203
+
204
+ const PreviewSection = styled("div")`
205
+ margin-top: 1.5rem;
206
+ `;
207
+
208
+ const PreviewLabel = styled("div")`
209
+ font-weight: 500;
210
+ margin-bottom: 0.5rem;
211
+ color: var(--j-text-color-strong);
212
+ `;
213
+
214
+ const PreviewPre = styled("pre")`
215
+ background-color: var(--j-foreground);
216
+ border: 1px solid var(--j-border-color);
217
+ border-radius: var(--j-radius-md);
218
+ padding: 1rem;
219
+ overflow-x: auto;
220
+ font-size: 0.875rem;
221
+ max-height: 400px;
222
+ overflow-y: auto;
223
+ color: var(--j-text-color);
224
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
225
+ `;
226
+
227
+ const RangeContainer = styled("div")`
228
+ display: flex;
229
+ flex-direction: column;
230
+ gap: 0.75rem;
231
+ `;
232
+
233
+ const RangeLabel = styled("label")`
234
+ font-weight: 500;
235
+ color: var(--j-text-color-strong);
236
+ font-size: 0.875rem;
237
+ `;
238
+
239
+ const RangeInput = styled("input")`
240
+ width: 100%;
241
+ height: 0.5rem;
242
+ border-radius: var(--j-radius-sm);
243
+ outline: none;
244
+ -webkit-appearance: none;
245
+ appearance: none;
246
+ background: var(--j-foreground);
247
+ cursor: pointer;
248
+
249
+ &::-webkit-slider-thumb {
250
+ -webkit-appearance: none;
251
+ appearance: none;
252
+ width: 1.25rem;
253
+ height: 1.25rem;
254
+ border-radius: 50%;
255
+ background: var(--j-primary-color);
256
+ cursor: pointer;
257
+ border: 2px solid var(--j-background);
258
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
259
+ }
260
+
261
+ &::-moz-range-thumb {
262
+ width: 1.25rem;
263
+ height: 1.25rem;
264
+ border-radius: 50%;
265
+ background: var(--j-primary-color);
266
+ cursor: pointer;
267
+ border: 2px solid var(--j-background);
268
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
269
+ }
270
+
271
+ &:disabled {
272
+ opacity: 0.5;
273
+ cursor: not-allowed;
274
+ }
275
+ `;
276
+
277
+ const TimestampDisplay = styled("div")`
278
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
279
+ font-size: 0.875rem;
280
+ color: var(--j-text-color);
281
+ padding: 0.5rem;
282
+ background-color: var(--j-foreground);
283
+ border: 1px solid var(--j-border-color);
284
+ border-radius: var(--j-radius-md);
285
+ text-align: center;
286
+ `;
287
+
288
+ const CheckboxContainer = styled("div")`
289
+ display: flex;
290
+ align-items: flex-start;
291
+ gap: 0.5rem;
292
+ margin-top: 1rem;
293
+ `;
294
+
295
+ const CheckboxInput = styled("input")`
296
+ width: 1rem;
297
+ height: 1rem;
298
+ margin-top: 0.125rem;
299
+ cursor: pointer;
300
+ accent-color: var(--j-primary-color);
301
+ `;
302
+
303
+ const CheckboxLabel = styled("label")`
304
+ font-size: 0.875rem;
305
+ color: var(--j-text-color);
306
+ cursor: pointer;
307
+ line-height: 1.25rem;
308
+ `;
309
+
310
+ const EditorContainer = styled("div")`
311
+ margin-top: 1rem;
312
+ `;
@@ -0,0 +1,164 @@
1
+ import { LocalNode, RawCoValue } from "cojson";
2
+ import { JsonValue } from "cojson";
3
+ import { useState } from "react";
4
+ import { styled } from "goober";
5
+ import { Button } from "../ui/button.js";
6
+ import { Select } from "../ui/select.js";
7
+
8
+ type ValueType =
9
+ | "number"
10
+ | "string"
11
+ | "true"
12
+ | "false"
13
+ | "object"
14
+ | "null"
15
+ | "undefined";
16
+
17
+ export function CoValueEditor({
18
+ node,
19
+ property,
20
+ value,
21
+ coValue,
22
+ onCancel,
23
+ }: {
24
+ node: LocalNode;
25
+ property: string;
26
+ value: JsonValue | undefined;
27
+ coValue: RawCoValue;
28
+ onCancel: () => void;
29
+ }) {
30
+ const getInitialType = (): ValueType => {
31
+ if (value === null) return "null";
32
+ if (value === undefined) return "undefined";
33
+ if (typeof value === "number") return "number";
34
+ if (typeof value === "string") return "string";
35
+ if (typeof value === "boolean") return value ? "true" : "false";
36
+ if (typeof value === "object") return "object";
37
+ return "undefined";
38
+ };
39
+
40
+ const [selectedType, setSelectedType] = useState<ValueType>(getInitialType());
41
+ const [editValue, setEditValue] = useState(
42
+ value === undefined || value === null
43
+ ? ""
44
+ : typeof value === "object"
45
+ ? JSON.stringify(value, null, 2)
46
+ : String(value),
47
+ );
48
+
49
+ const handleSubmit = (e: React.FormEvent) => {
50
+ e.preventDefault();
51
+ e.stopPropagation();
52
+
53
+ let newValue;
54
+ switch (selectedType) {
55
+ case "null":
56
+ newValue = null;
57
+ break;
58
+ case "undefined":
59
+ newValue = undefined;
60
+ break;
61
+ case "true":
62
+ newValue = true;
63
+ break;
64
+ case "false":
65
+ newValue = false;
66
+ break;
67
+ case "number":
68
+ newValue = parseFloat(editValue);
69
+ break;
70
+ case "string":
71
+ newValue = editValue;
72
+ break;
73
+ case "object":
74
+ newValue = JSON.parse(editValue);
75
+ break;
76
+ default:
77
+ throw new Error(`Invalid type: ${selectedType}`);
78
+ }
79
+
80
+ coValue.core.makeTransaction(
81
+ [
82
+ {
83
+ op: "set",
84
+ key: property,
85
+ value: newValue,
86
+ },
87
+ ],
88
+ "private",
89
+ );
90
+
91
+ onCancel();
92
+ };
93
+
94
+ const showTextarea =
95
+ selectedType === "number" ||
96
+ selectedType === "string" ||
97
+ selectedType === "object";
98
+
99
+ return (
100
+ <EditForm onSubmit={handleSubmit}>
101
+ <Select
102
+ label="Type"
103
+ value={selectedType}
104
+ onChange={(e) => {
105
+ setSelectedType(e.target.value as ValueType);
106
+ }}
107
+ onClick={(e) => e.stopPropagation()}
108
+ >
109
+ <option value="number">number</option>
110
+ <option value="string">string</option>
111
+ <option value="true">true</option>
112
+ <option value="false">false</option>
113
+ <option value="object">object</option>
114
+ <option value="null">null</option>
115
+ <option value="undefined">undefined</option>
116
+ </Select>
117
+ {showTextarea && (
118
+ <StyledTextarea
119
+ value={editValue}
120
+ onChange={(e) => setEditValue(e.target.value)}
121
+ onClick={(e) => e.stopPropagation()}
122
+ />
123
+ )}
124
+ <FormActions>
125
+ <Button type="button" variant="secondary" onClick={onCancel}>
126
+ Cancel
127
+ </Button>
128
+ <Button type="submit" variant="primary">
129
+ Submit
130
+ </Button>
131
+ </FormActions>
132
+ </EditForm>
133
+ );
134
+ }
135
+
136
+ const EditForm = styled("form")`
137
+ display: flex;
138
+ flex-direction: column;
139
+ gap: 0.75rem;
140
+ `;
141
+
142
+ const StyledTextarea = styled("textarea")`
143
+ width: 100%;
144
+ min-height: 120px;
145
+ border-radius: var(--j-radius-md);
146
+ border: 1px solid var(--j-border-color);
147
+ padding: 0.5rem 0.875rem;
148
+ box-shadow: var(--j-shadow-sm);
149
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
150
+ font-size: 0.875rem;
151
+ background-color: white;
152
+ color: var(--j-text-color-strong);
153
+ resize: vertical;
154
+
155
+ @media (prefers-color-scheme: dark) {
156
+ background-color: var(--j-foreground);
157
+ }
158
+ `;
159
+
160
+ const FormActions = styled("div")`
161
+ display: flex;
162
+ gap: 0.5rem;
163
+ justify-content: flex-end;
164
+ `;