modern-json-react 1.0.0 → 1.1.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/README.md +3 -3
- package/dist/index.d.mts +9 -2
- package/dist/index.d.ts +9 -2
- package/dist/index.js +608 -175
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +608 -176
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +937 -0
- package/package.json +25 -7
package/dist/index.js
CHANGED
|
@@ -84,6 +84,179 @@ var Toolbar = ({
|
|
|
84
84
|
)
|
|
85
85
|
] });
|
|
86
86
|
};
|
|
87
|
+
var SearchBar = ({
|
|
88
|
+
query,
|
|
89
|
+
onQueryChange,
|
|
90
|
+
currentMatchIndex,
|
|
91
|
+
totalMatches,
|
|
92
|
+
onNext,
|
|
93
|
+
onPrevious,
|
|
94
|
+
onClose,
|
|
95
|
+
options,
|
|
96
|
+
onOptionsChange
|
|
97
|
+
}) => {
|
|
98
|
+
const inputRef = react.useRef(null);
|
|
99
|
+
react.useEffect(() => {
|
|
100
|
+
inputRef.current?.focus();
|
|
101
|
+
}, []);
|
|
102
|
+
const handleKeyDown = react.useCallback(
|
|
103
|
+
(e) => {
|
|
104
|
+
if (e.key === "Enter") {
|
|
105
|
+
e.preventDefault();
|
|
106
|
+
if (e.shiftKey) {
|
|
107
|
+
onPrevious();
|
|
108
|
+
} else {
|
|
109
|
+
onNext();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (e.key === "Escape") {
|
|
113
|
+
e.preventDefault();
|
|
114
|
+
onClose();
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
[onNext, onPrevious, onClose]
|
|
118
|
+
);
|
|
119
|
+
const matchLabel = totalMatches === 0 ? query ? "No results" : "" : `${currentMatchIndex + 1} of ${totalMatches}`;
|
|
120
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mjr-search", role: "search", "aria-label": "Search in editor", children: [
|
|
121
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mjr-search__input-group", children: [
|
|
122
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
123
|
+
"svg",
|
|
124
|
+
{
|
|
125
|
+
className: "mjr-search__icon",
|
|
126
|
+
width: "14",
|
|
127
|
+
height: "14",
|
|
128
|
+
viewBox: "0 0 24 24",
|
|
129
|
+
fill: "none",
|
|
130
|
+
stroke: "currentColor",
|
|
131
|
+
strokeWidth: "2",
|
|
132
|
+
strokeLinecap: "round",
|
|
133
|
+
strokeLinejoin: "round",
|
|
134
|
+
"aria-hidden": "true",
|
|
135
|
+
children: [
|
|
136
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "11", cy: "11", r: "8" }),
|
|
137
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "21", y1: "21", x2: "16.65", y2: "16.65" })
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
),
|
|
141
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
142
|
+
"input",
|
|
143
|
+
{
|
|
144
|
+
ref: inputRef,
|
|
145
|
+
className: "mjr-search__input",
|
|
146
|
+
type: "text",
|
|
147
|
+
value: query,
|
|
148
|
+
onChange: (e) => onQueryChange(e.target.value),
|
|
149
|
+
onKeyDown: handleKeyDown,
|
|
150
|
+
placeholder: "Search...",
|
|
151
|
+
"aria-label": "Search query",
|
|
152
|
+
spellCheck: false,
|
|
153
|
+
autoComplete: "off"
|
|
154
|
+
}
|
|
155
|
+
),
|
|
156
|
+
query && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mjr-search__count", "aria-live": "polite", children: matchLabel })
|
|
157
|
+
] }),
|
|
158
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mjr-search__options", children: [
|
|
159
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
160
|
+
"button",
|
|
161
|
+
{
|
|
162
|
+
className: `mjr-search__option-btn ${options.caseSensitive ? "mjr-search__option-btn--active" : ""}`,
|
|
163
|
+
onClick: () => onOptionsChange({ caseSensitive: !options.caseSensitive }),
|
|
164
|
+
"aria-pressed": options.caseSensitive,
|
|
165
|
+
title: "Match case",
|
|
166
|
+
"aria-label": "Match case",
|
|
167
|
+
children: "Aa"
|
|
168
|
+
}
|
|
169
|
+
),
|
|
170
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
171
|
+
"button",
|
|
172
|
+
{
|
|
173
|
+
className: `mjr-search__option-btn ${options.useRegex ? "mjr-search__option-btn--active" : ""}`,
|
|
174
|
+
onClick: () => onOptionsChange({ useRegex: !options.useRegex }),
|
|
175
|
+
"aria-pressed": options.useRegex,
|
|
176
|
+
title: "Use regular expression",
|
|
177
|
+
"aria-label": "Use regular expression",
|
|
178
|
+
children: ".*"
|
|
179
|
+
}
|
|
180
|
+
)
|
|
181
|
+
] }),
|
|
182
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mjr-search__nav", children: [
|
|
183
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
184
|
+
"button",
|
|
185
|
+
{
|
|
186
|
+
className: "mjr-search__nav-btn",
|
|
187
|
+
onClick: onPrevious,
|
|
188
|
+
disabled: totalMatches === 0,
|
|
189
|
+
"aria-label": "Previous match",
|
|
190
|
+
title: "Previous match (Shift+Enter)",
|
|
191
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
192
|
+
"svg",
|
|
193
|
+
{
|
|
194
|
+
width: "12",
|
|
195
|
+
height: "12",
|
|
196
|
+
viewBox: "0 0 24 24",
|
|
197
|
+
fill: "none",
|
|
198
|
+
stroke: "currentColor",
|
|
199
|
+
strokeWidth: "2.5",
|
|
200
|
+
strokeLinecap: "round",
|
|
201
|
+
strokeLinejoin: "round",
|
|
202
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "18 15 12 9 6 15" })
|
|
203
|
+
}
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
),
|
|
207
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
208
|
+
"button",
|
|
209
|
+
{
|
|
210
|
+
className: "mjr-search__nav-btn",
|
|
211
|
+
onClick: onNext,
|
|
212
|
+
disabled: totalMatches === 0,
|
|
213
|
+
"aria-label": "Next match",
|
|
214
|
+
title: "Next match (Enter)",
|
|
215
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
216
|
+
"svg",
|
|
217
|
+
{
|
|
218
|
+
width: "12",
|
|
219
|
+
height: "12",
|
|
220
|
+
viewBox: "0 0 24 24",
|
|
221
|
+
fill: "none",
|
|
222
|
+
stroke: "currentColor",
|
|
223
|
+
strokeWidth: "2.5",
|
|
224
|
+
strokeLinecap: "round",
|
|
225
|
+
strokeLinejoin: "round",
|
|
226
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "6 9 12 15 18 9" })
|
|
227
|
+
}
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
)
|
|
231
|
+
] }),
|
|
232
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
233
|
+
"button",
|
|
234
|
+
{
|
|
235
|
+
className: "mjr-search__close",
|
|
236
|
+
onClick: onClose,
|
|
237
|
+
"aria-label": "Close search",
|
|
238
|
+
title: "Close (Escape)",
|
|
239
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
240
|
+
"svg",
|
|
241
|
+
{
|
|
242
|
+
width: "14",
|
|
243
|
+
height: "14",
|
|
244
|
+
viewBox: "0 0 24 24",
|
|
245
|
+
fill: "none",
|
|
246
|
+
stroke: "currentColor",
|
|
247
|
+
strokeWidth: "2",
|
|
248
|
+
strokeLinecap: "round",
|
|
249
|
+
strokeLinejoin: "round",
|
|
250
|
+
children: [
|
|
251
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
252
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
253
|
+
]
|
|
254
|
+
}
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
)
|
|
258
|
+
] });
|
|
259
|
+
};
|
|
87
260
|
var CodeEditor = ({
|
|
88
261
|
value,
|
|
89
262
|
onChange,
|
|
@@ -96,6 +269,7 @@ var CodeEditor = ({
|
|
|
96
269
|
className = ""
|
|
97
270
|
}) => {
|
|
98
271
|
const textareaRef = react.useRef(null);
|
|
272
|
+
const wrapperRef = react.useRef(null);
|
|
99
273
|
const lines = react.useMemo(() => value.split("\n"), [value]);
|
|
100
274
|
const handleChange = react.useCallback(
|
|
101
275
|
(e) => {
|
|
@@ -138,6 +312,19 @@ var CodeEditor = ({
|
|
|
138
312
|
},
|
|
139
313
|
[value, onChange, readOnly]
|
|
140
314
|
);
|
|
315
|
+
react.useEffect(() => {
|
|
316
|
+
const wrapper = wrapperRef.current;
|
|
317
|
+
if (!wrapper) return;
|
|
318
|
+
const syncScroll = () => {
|
|
319
|
+
const textarea = textareaRef.current;
|
|
320
|
+
if (textarea) {
|
|
321
|
+
textarea.scrollTop = wrapper.scrollTop;
|
|
322
|
+
textarea.scrollLeft = wrapper.scrollLeft;
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
wrapper.addEventListener("scroll", syncScroll, { passive: true });
|
|
326
|
+
return () => wrapper.removeEventListener("scroll", syncScroll);
|
|
327
|
+
}, []);
|
|
141
328
|
const handleCursorMove = react.useCallback(() => {
|
|
142
329
|
const textarea = textareaRef.current;
|
|
143
330
|
if (!textarea) return;
|
|
@@ -148,24 +335,37 @@ var CodeEditor = ({
|
|
|
148
335
|
const column = linesBefore[linesBefore.length - 1].length + 1;
|
|
149
336
|
onCursorChange({ line, column, offset: pos });
|
|
150
337
|
}, [value, onCursorChange]);
|
|
338
|
+
const matchesByLine = react.useMemo(() => {
|
|
339
|
+
const map = /* @__PURE__ */ new Map();
|
|
340
|
+
searchMatches.forEach((m, i) => {
|
|
341
|
+
if (!map.has(m.line)) map.set(m.line, []);
|
|
342
|
+
map.get(m.line).push({ match: m, isActive: i === currentMatchIndex });
|
|
343
|
+
});
|
|
344
|
+
return map;
|
|
345
|
+
}, [searchMatches, currentMatchIndex]);
|
|
346
|
+
react.useEffect(() => {
|
|
347
|
+
if (searchMatches.length === 0) return;
|
|
348
|
+
const currentMatch = searchMatches[currentMatchIndex];
|
|
349
|
+
if (!currentMatch) return;
|
|
350
|
+
const wrapper = wrapperRef.current;
|
|
351
|
+
if (!wrapper) return;
|
|
352
|
+
const activeEl = wrapper.querySelector(".mjr-code__match--active");
|
|
353
|
+
if (activeEl) {
|
|
354
|
+
activeEl.scrollIntoView?.({ block: "nearest", inline: "nearest" });
|
|
355
|
+
}
|
|
356
|
+
}, [currentMatchIndex, searchMatches]);
|
|
151
357
|
const highlightedLines = react.useMemo(() => {
|
|
152
358
|
return lines.map((line, idx) => {
|
|
153
359
|
const lineNum = idx + 1;
|
|
154
360
|
const isErrorLine = parseError?.line === lineNum;
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
{
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
lineNumbers && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mjr-code__line-number", "aria-hidden": "true", children: lineNum }),
|
|
161
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "mjr-code__line-content", children: highlightJsonLine(line) })
|
|
162
|
-
]
|
|
163
|
-
},
|
|
164
|
-
idx
|
|
165
|
-
);
|
|
361
|
+
const lineMatches = matchesByLine.get(lineNum);
|
|
362
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `mjr-code__line ${isErrorLine ? "mjr-code__line--error" : ""}`, children: [
|
|
363
|
+
lineNumbers && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mjr-code__line-number", "aria-hidden": "true", children: lineNum }),
|
|
364
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "mjr-code__line-content", children: lineMatches && lineMatches.length > 0 ? highlightLineWithMatches(line, lineMatches) : highlightJsonLine(line) })
|
|
365
|
+
] }, idx);
|
|
166
366
|
});
|
|
167
|
-
}, [lines, lineNumbers, parseError]);
|
|
168
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `mjr-code-editor ${className}`, children: [
|
|
367
|
+
}, [lines, lineNumbers, parseError, matchesByLine]);
|
|
368
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: wrapperRef, className: `mjr-code-editor ${className}`, children: [
|
|
169
369
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mjr-code__display", "aria-hidden": "true", children: highlightedLines }),
|
|
170
370
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
171
371
|
"textarea",
|
|
@@ -206,6 +406,36 @@ var CodeEditor = ({
|
|
|
206
406
|
)
|
|
207
407
|
] });
|
|
208
408
|
};
|
|
409
|
+
function highlightLineWithMatches(line, lineMatches) {
|
|
410
|
+
const sorted = [...lineMatches].sort((a, b) => a.match.columnStart - b.match.columnStart);
|
|
411
|
+
const result = [];
|
|
412
|
+
let pos = 0;
|
|
413
|
+
let key = 0;
|
|
414
|
+
for (const { match, isActive } of sorted) {
|
|
415
|
+
if (match.columnStart > pos) {
|
|
416
|
+
const before = line.substring(pos, match.columnStart);
|
|
417
|
+
result.push(/* @__PURE__ */ jsxRuntime.jsx("span", { children: highlightJsonLine(before) }, key++));
|
|
418
|
+
}
|
|
419
|
+
const matchText = line.substring(match.columnStart, match.columnEnd);
|
|
420
|
+
const cls = isActive ? "mjr-code__match mjr-code__match--active" : "mjr-code__match";
|
|
421
|
+
result.push(
|
|
422
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
423
|
+
"mark",
|
|
424
|
+
{
|
|
425
|
+
className: cls,
|
|
426
|
+
"data-testid": isActive ? "search-match-active" : "search-match",
|
|
427
|
+
children: highlightJsonLine(matchText)
|
|
428
|
+
},
|
|
429
|
+
key++
|
|
430
|
+
)
|
|
431
|
+
);
|
|
432
|
+
pos = match.columnEnd;
|
|
433
|
+
}
|
|
434
|
+
if (pos < line.length) {
|
|
435
|
+
result.push(/* @__PURE__ */ jsxRuntime.jsx("span", { children: highlightJsonLine(line.substring(pos)) }, key++));
|
|
436
|
+
}
|
|
437
|
+
return result;
|
|
438
|
+
}
|
|
209
439
|
function highlightJsonLine(line) {
|
|
210
440
|
const tokens = [];
|
|
211
441
|
let i = 0;
|
|
@@ -246,7 +476,7 @@ function highlightJsonLine(line) {
|
|
|
246
476
|
}
|
|
247
477
|
if (ch === "-" || ch >= "0" && ch <= "9") {
|
|
248
478
|
let num = "";
|
|
249
|
-
while (i < line.length && /[\d.eE
|
|
479
|
+
while (i < line.length && /[\d.eE+-]/.test(line[i])) {
|
|
250
480
|
num += line[i];
|
|
251
481
|
i++;
|
|
252
482
|
}
|
|
@@ -256,26 +486,36 @@ function highlightJsonLine(line) {
|
|
|
256
486
|
continue;
|
|
257
487
|
}
|
|
258
488
|
if (line.substring(i, i + 4) === "true") {
|
|
259
|
-
tokens.push(
|
|
489
|
+
tokens.push(
|
|
490
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "mjr-syn-boolean", children: "true" }, key++)
|
|
491
|
+
);
|
|
260
492
|
i += 4;
|
|
261
493
|
continue;
|
|
262
494
|
}
|
|
263
495
|
if (line.substring(i, i + 5) === "false") {
|
|
264
|
-
tokens.push(
|
|
496
|
+
tokens.push(
|
|
497
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "mjr-syn-boolean", children: "false" }, key++)
|
|
498
|
+
);
|
|
265
499
|
i += 5;
|
|
266
500
|
continue;
|
|
267
501
|
}
|
|
268
502
|
if (line.substring(i, i + 4) === "null") {
|
|
269
|
-
tokens.push(
|
|
503
|
+
tokens.push(
|
|
504
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "mjr-syn-null", children: "null" }, key++)
|
|
505
|
+
);
|
|
270
506
|
i += 4;
|
|
271
507
|
continue;
|
|
272
508
|
}
|
|
273
509
|
if (ch === "{" || ch === "}" || ch === "[" || ch === "]") {
|
|
274
|
-
tokens.push(
|
|
510
|
+
tokens.push(
|
|
511
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "mjr-syn-bracket", children: ch }, key++)
|
|
512
|
+
);
|
|
275
513
|
i++;
|
|
276
514
|
continue;
|
|
277
515
|
}
|
|
278
|
-
tokens.push(
|
|
516
|
+
tokens.push(
|
|
517
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "mjr-syn-punctuation", children: ch }, key++)
|
|
518
|
+
);
|
|
279
519
|
i++;
|
|
280
520
|
}
|
|
281
521
|
return tokens;
|
|
@@ -288,6 +528,14 @@ var TYPE_LABELS = {
|
|
|
288
528
|
object: "obj",
|
|
289
529
|
array: "arr"
|
|
290
530
|
};
|
|
531
|
+
var TYPE_COLORS = {
|
|
532
|
+
string: "mjr-badge--string",
|
|
533
|
+
number: "mjr-badge--number",
|
|
534
|
+
boolean: "mjr-badge--boolean",
|
|
535
|
+
null: "mjr-badge--null",
|
|
536
|
+
object: "mjr-badge--object",
|
|
537
|
+
array: "mjr-badge--array"
|
|
538
|
+
};
|
|
291
539
|
var TreeNodeComponent = ({
|
|
292
540
|
node,
|
|
293
541
|
onToggle,
|
|
@@ -295,7 +543,9 @@ var TreeNodeComponent = ({
|
|
|
295
543
|
onKeyChange,
|
|
296
544
|
onDelete,
|
|
297
545
|
onTypeChange,
|
|
298
|
-
readOnly
|
|
546
|
+
readOnly,
|
|
547
|
+
searchQuery = "",
|
|
548
|
+
searchCaseSensitive = false
|
|
299
549
|
}) => {
|
|
300
550
|
const [isEditing, setIsEditing] = react.useState(false);
|
|
301
551
|
const [editValue, setEditValue] = react.useState("");
|
|
@@ -303,9 +553,7 @@ var TreeNodeComponent = ({
|
|
|
303
553
|
const [editKey, setEditKey] = react.useState("");
|
|
304
554
|
const isExpandable = node.type === "object" || node.type === "array";
|
|
305
555
|
const handleToggle = react.useCallback(() => {
|
|
306
|
-
if (isExpandable)
|
|
307
|
-
onToggle(node.id);
|
|
308
|
-
}
|
|
556
|
+
if (isExpandable) onToggle(node.id);
|
|
309
557
|
}, [isExpandable, node.id, onToggle]);
|
|
310
558
|
const handleStartEdit = react.useCallback(() => {
|
|
311
559
|
if (readOnly || isExpandable) return;
|
|
@@ -346,17 +594,80 @@ var TreeNodeComponent = ({
|
|
|
346
594
|
}, [editKey, node.key, node.path, onKeyChange]);
|
|
347
595
|
const handleTypeChange = react.useCallback(
|
|
348
596
|
(e) => {
|
|
349
|
-
|
|
350
|
-
onTypeChange(node.path, newType);
|
|
597
|
+
onTypeChange(node.path, e.target.value);
|
|
351
598
|
},
|
|
352
599
|
[node.path, onTypeChange]
|
|
353
600
|
);
|
|
601
|
+
const renderArrow = () => /* @__PURE__ */ jsxRuntime.jsx(
|
|
602
|
+
"button",
|
|
603
|
+
{
|
|
604
|
+
className: `mjr-tree__arrow ${isExpandable ? "mjr-tree__arrow--expandable" : ""}`,
|
|
605
|
+
onClick: handleToggle,
|
|
606
|
+
tabIndex: isExpandable ? 0 : -1,
|
|
607
|
+
"aria-hidden": !isExpandable,
|
|
608
|
+
"aria-label": isExpandable ? node.expanded ? "Collapse" : "Expand" : void 0,
|
|
609
|
+
children: isExpandable ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
610
|
+
"svg",
|
|
611
|
+
{
|
|
612
|
+
className: `mjr-tree__chevron ${node.expanded ? "mjr-tree__chevron--open" : ""}`,
|
|
613
|
+
width: "12",
|
|
614
|
+
height: "12",
|
|
615
|
+
viewBox: "0 0 12 12",
|
|
616
|
+
fill: "none",
|
|
617
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
618
|
+
"path",
|
|
619
|
+
{
|
|
620
|
+
d: "M4.5 2.5L8 6L4.5 9.5",
|
|
621
|
+
stroke: "currentColor",
|
|
622
|
+
strokeWidth: "1.5",
|
|
623
|
+
strokeLinecap: "round",
|
|
624
|
+
strokeLinejoin: "round"
|
|
625
|
+
}
|
|
626
|
+
)
|
|
627
|
+
}
|
|
628
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mjr-tree__arrow-spacer" })
|
|
629
|
+
}
|
|
630
|
+
);
|
|
631
|
+
const renderKey = () => {
|
|
632
|
+
if (isEditingKey) {
|
|
633
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
634
|
+
"input",
|
|
635
|
+
{
|
|
636
|
+
className: "mjr-tree__input mjr-tree__input--key",
|
|
637
|
+
value: editKey,
|
|
638
|
+
onChange: (e) => setEditKey(e.target.value),
|
|
639
|
+
onBlur: handleFinishKeyEdit,
|
|
640
|
+
onKeyDown: (e) => {
|
|
641
|
+
if (e.key === "Enter") handleFinishKeyEdit();
|
|
642
|
+
if (e.key === "Escape") setIsEditingKey(false);
|
|
643
|
+
},
|
|
644
|
+
autoFocus: true,
|
|
645
|
+
"aria-label": "Edit key name"
|
|
646
|
+
}
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
const keyStr = String(node.key);
|
|
650
|
+
const keyContent = searchQuery ? highlightText(keyStr, searchQuery, searchCaseSensitive) : keyStr;
|
|
651
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
652
|
+
"span",
|
|
653
|
+
{
|
|
654
|
+
className: `mjr-tree__key ${typeof node.key !== "number" && !readOnly ? "mjr-tree__key--editable" : ""}`,
|
|
655
|
+
onDoubleClick: handleKeyEdit,
|
|
656
|
+
"data-testid": `key-${node.path}`,
|
|
657
|
+
children: typeof node.key === "number" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mjr-tree__index", children: node.key }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
658
|
+
'"',
|
|
659
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "mjr-tree__key-text", children: keyContent }),
|
|
660
|
+
'"'
|
|
661
|
+
] })
|
|
662
|
+
}
|
|
663
|
+
);
|
|
664
|
+
};
|
|
354
665
|
const renderValue = () => {
|
|
355
666
|
if (isEditing) {
|
|
356
667
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
357
668
|
"input",
|
|
358
669
|
{
|
|
359
|
-
className: "mjr-
|
|
670
|
+
className: "mjr-tree__input mjr-tree__input--value",
|
|
360
671
|
value: editValue,
|
|
361
672
|
onChange: (e) => setEditValue(e.target.value),
|
|
362
673
|
onBlur: handleFinishEdit,
|
|
@@ -371,31 +682,79 @@ var TreeNodeComponent = ({
|
|
|
371
682
|
);
|
|
372
683
|
}
|
|
373
684
|
if (isExpandable) {
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
685
|
+
const open = node.type === "object" ? "{" : "[";
|
|
686
|
+
const close = node.type === "object" ? "}" : "]";
|
|
687
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "mjr-tree__preview", children: [
|
|
688
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "mjr-tree__bracket", children: open }),
|
|
689
|
+
!node.expanded && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
690
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "mjr-tree__ellipsis", children: [
|
|
691
|
+
node.childCount,
|
|
692
|
+
" ",
|
|
693
|
+
node.childCount === 1 ? "item" : "items"
|
|
694
|
+
] }),
|
|
695
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "mjr-tree__bracket", children: close })
|
|
383
696
|
] })
|
|
384
697
|
] });
|
|
385
698
|
}
|
|
699
|
+
const display = formatDisplayValue(node.value, node.type);
|
|
700
|
+
const displayContent = searchQuery ? highlightText(display, searchQuery, searchCaseSensitive) : display;
|
|
386
701
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
387
702
|
"span",
|
|
388
703
|
{
|
|
389
|
-
className: `mjr-
|
|
704
|
+
className: `mjr-tree__value mjr-tree__value--${node.type} ${!readOnly ? "mjr-tree__value--editable" : ""}`,
|
|
390
705
|
onDoubleClick: handleStartEdit,
|
|
391
706
|
role: "button",
|
|
392
707
|
tabIndex: 0,
|
|
393
|
-
"aria-label": `Value: ${
|
|
708
|
+
"aria-label": `Value: ${display}. Double-click to edit.`,
|
|
394
709
|
"data-testid": `value-${node.path}`,
|
|
395
|
-
children:
|
|
710
|
+
children: displayContent
|
|
396
711
|
}
|
|
397
712
|
);
|
|
398
713
|
};
|
|
714
|
+
const renderBadge = () => {
|
|
715
|
+
const colorClass = TYPE_COLORS[node.type];
|
|
716
|
+
if (!readOnly) {
|
|
717
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
718
|
+
"select",
|
|
719
|
+
{
|
|
720
|
+
className: `mjr-tree__badge ${colorClass}`,
|
|
721
|
+
value: node.type,
|
|
722
|
+
onChange: handleTypeChange,
|
|
723
|
+
"aria-label": `Type for ${node.key}`,
|
|
724
|
+
"data-testid": `type-${node.path}`,
|
|
725
|
+
children: Object.entries(TYPE_LABELS).map(([type, label]) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: type, children: label }, type))
|
|
726
|
+
}
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: `mjr-tree__badge mjr-tree__badge--ro ${colorClass}`, children: TYPE_LABELS[node.type] });
|
|
730
|
+
};
|
|
731
|
+
const renderActions = () => {
|
|
732
|
+
if (readOnly) return null;
|
|
733
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mjr-tree__actions", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
734
|
+
"button",
|
|
735
|
+
{
|
|
736
|
+
className: "mjr-tree__delete",
|
|
737
|
+
onClick: () => onDelete(node.path),
|
|
738
|
+
"aria-label": `Delete ${node.key}`,
|
|
739
|
+
title: "Delete",
|
|
740
|
+
"data-testid": `delete-${node.path}`,
|
|
741
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
742
|
+
"path",
|
|
743
|
+
{
|
|
744
|
+
d: "M9 3L3 9M3 3l6 6",
|
|
745
|
+
stroke: "currentColor",
|
|
746
|
+
strokeWidth: "1.5",
|
|
747
|
+
strokeLinecap: "round"
|
|
748
|
+
}
|
|
749
|
+
) })
|
|
750
|
+
}
|
|
751
|
+
) });
|
|
752
|
+
};
|
|
753
|
+
const renderClosingBracket = () => {
|
|
754
|
+
if (!isExpandable || !node.expanded) return null;
|
|
755
|
+
const close = node.type === "object" ? "}" : "]";
|
|
756
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mjr-tree__close-bracket", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mjr-tree__bracket", children: close }) });
|
|
757
|
+
};
|
|
399
758
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
400
759
|
"div",
|
|
401
760
|
{
|
|
@@ -404,85 +763,67 @@ var TreeNodeComponent = ({
|
|
|
404
763
|
"aria-expanded": isExpandable ? node.expanded : void 0,
|
|
405
764
|
"aria-level": node.depth + 1,
|
|
406
765
|
"aria-label": `${node.key}: ${node.type}`,
|
|
407
|
-
style: { paddingLeft: `${node.depth * 20}px` },
|
|
408
766
|
"data-testid": `tree-node-${node.path}`,
|
|
409
767
|
children: [
|
|
410
|
-
/* @__PURE__ */ jsxRuntime.
|
|
411
|
-
|
|
412
|
-
{
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
768
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mjr-tree__row", children: [
|
|
769
|
+
renderArrow(),
|
|
770
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mjr-tree__content", children: [
|
|
771
|
+
renderKey(),
|
|
772
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "mjr-tree__colon", "aria-hidden": "true", children: ":" }),
|
|
773
|
+
renderValue(),
|
|
774
|
+
renderBadge()
|
|
775
|
+
] }),
|
|
776
|
+
renderActions()
|
|
777
|
+
] }),
|
|
778
|
+
isExpandable && node.expanded && node.children.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "group", className: "mjr-tree__children", children: [
|
|
779
|
+
node.children.map((child) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
780
|
+
TreeNodeComponent,
|
|
781
|
+
{
|
|
782
|
+
node: child,
|
|
783
|
+
onToggle,
|
|
784
|
+
onValueChange,
|
|
785
|
+
onKeyChange,
|
|
786
|
+
onDelete,
|
|
787
|
+
onTypeChange,
|
|
788
|
+
readOnly,
|
|
789
|
+
searchQuery,
|
|
790
|
+
searchCaseSensitive
|
|
429
791
|
},
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
)
|
|
434
|
-
|
|
435
|
-
{
|
|
436
|
-
className: "mjr-tree-node__key",
|
|
437
|
-
onDoubleClick: handleKeyEdit,
|
|
438
|
-
"data-testid": `key-${node.path}`,
|
|
439
|
-
children: typeof node.key === "number" ? node.key : `"${node.key}"`
|
|
440
|
-
}
|
|
441
|
-
),
|
|
442
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "mjr-tree-node__colon", "aria-hidden": "true", children: ":" }),
|
|
443
|
-
renderValue(),
|
|
444
|
-
!readOnly ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
445
|
-
"select",
|
|
446
|
-
{
|
|
447
|
-
className: "mjr-tree-node__type-badge",
|
|
448
|
-
value: node.type,
|
|
449
|
-
onChange: handleTypeChange,
|
|
450
|
-
"aria-label": `Type for ${node.key}`,
|
|
451
|
-
"data-testid": `type-${node.path}`,
|
|
452
|
-
children: Object.entries(TYPE_LABELS).map(([type, label]) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: type, children: label }, type))
|
|
453
|
-
}
|
|
454
|
-
) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mjr-tree-node__type-badge-ro", children: TYPE_LABELS[node.type] }),
|
|
455
|
-
!readOnly && /* @__PURE__ */ jsxRuntime.jsx(
|
|
456
|
-
"button",
|
|
457
|
-
{
|
|
458
|
-
className: "mjr-tree-node__delete",
|
|
459
|
-
onClick: () => onDelete(node.path),
|
|
460
|
-
"aria-label": `Delete ${node.key}`,
|
|
461
|
-
title: "Delete",
|
|
462
|
-
"data-testid": `delete-${node.path}`,
|
|
463
|
-
children: "\\u2715"
|
|
464
|
-
}
|
|
465
|
-
),
|
|
466
|
-
isExpandable && node.expanded && node.children.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { role: "group", className: "mjr-tree-node__children", children: node.children.map((child) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
467
|
-
TreeNodeComponent,
|
|
468
|
-
{
|
|
469
|
-
node: child,
|
|
470
|
-
onToggle,
|
|
471
|
-
onValueChange,
|
|
472
|
-
onKeyChange,
|
|
473
|
-
onDelete,
|
|
474
|
-
onTypeChange,
|
|
475
|
-
readOnly
|
|
476
|
-
},
|
|
477
|
-
child.id
|
|
478
|
-
)) })
|
|
792
|
+
child.id
|
|
793
|
+
)),
|
|
794
|
+
renderClosingBracket()
|
|
795
|
+
] }),
|
|
796
|
+
isExpandable && node.expanded && node.children.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mjr-tree__children", children: renderClosingBracket() })
|
|
479
797
|
]
|
|
480
798
|
}
|
|
481
799
|
);
|
|
482
800
|
};
|
|
801
|
+
function highlightText(text, query, caseSensitive) {
|
|
802
|
+
if (!query || !text) return text;
|
|
803
|
+
try {
|
|
804
|
+
const flags = caseSensitive ? "g" : "gi";
|
|
805
|
+
const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
806
|
+
const regex = new RegExp(`(${escaped})`, flags);
|
|
807
|
+
const parts = text.split(regex);
|
|
808
|
+
if (parts.length === 1) return text;
|
|
809
|
+
return parts.map((part, i) => {
|
|
810
|
+
if (regex.test(part)) {
|
|
811
|
+
regex.lastIndex = 0;
|
|
812
|
+
return /* @__PURE__ */ jsxRuntime.jsx("mark", { className: "mjr-tree__match", "data-testid": "tree-search-match", children: part }, i);
|
|
813
|
+
}
|
|
814
|
+
return part;
|
|
815
|
+
});
|
|
816
|
+
} catch {
|
|
817
|
+
return text;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
483
820
|
function formatDisplayValue(value, type) {
|
|
484
821
|
if (type === "null") return "null";
|
|
485
|
-
if (type === "string")
|
|
822
|
+
if (type === "string") {
|
|
823
|
+
const str = String(value);
|
|
824
|
+
if (str.length > 80) return `"${str.slice(0, 77)}..."`;
|
|
825
|
+
return `"${str}"`;
|
|
826
|
+
}
|
|
486
827
|
if (type === "boolean") return String(value);
|
|
487
828
|
if (type === "number") return String(value);
|
|
488
829
|
return "";
|
|
@@ -552,7 +893,7 @@ function deleteBySegments(current, segments, index) {
|
|
|
552
893
|
return current.filter((_, i) => i !== arrIndex);
|
|
553
894
|
}
|
|
554
895
|
if (typeof current === "object" && current !== null) {
|
|
555
|
-
const { [segment2]:
|
|
896
|
+
const { [segment2]: _omit, ...rest } = current;
|
|
556
897
|
return rest;
|
|
557
898
|
}
|
|
558
899
|
return current;
|
|
@@ -591,6 +932,8 @@ var TreeEditor = ({
|
|
|
591
932
|
value,
|
|
592
933
|
onChange,
|
|
593
934
|
readOnly,
|
|
935
|
+
searchQuery = "",
|
|
936
|
+
searchCaseSensitive = false,
|
|
594
937
|
className = ""
|
|
595
938
|
}) => {
|
|
596
939
|
const [expandedPaths, setExpandedPaths] = react.useState(/* @__PURE__ */ new Set(["$"]));
|
|
@@ -601,18 +944,14 @@ var TreeEditor = ({
|
|
|
601
944
|
setExpandedPaths((prev) => {
|
|
602
945
|
const next = new Set(prev);
|
|
603
946
|
const path = id;
|
|
604
|
-
if (next.has(path))
|
|
605
|
-
|
|
606
|
-
} else {
|
|
607
|
-
next.add(path);
|
|
608
|
-
}
|
|
947
|
+
if (next.has(path)) next.delete(path);
|
|
948
|
+
else next.add(path);
|
|
609
949
|
return next;
|
|
610
950
|
});
|
|
611
951
|
}, []);
|
|
612
952
|
const handleValueChange = react.useCallback(
|
|
613
953
|
(path, newValue) => {
|
|
614
|
-
|
|
615
|
-
onChange(updated);
|
|
954
|
+
onChange(setByPath(value, path, newValue));
|
|
616
955
|
},
|
|
617
956
|
[value, onChange]
|
|
618
957
|
);
|
|
@@ -630,8 +969,7 @@ var TreeEditor = ({
|
|
|
630
969
|
);
|
|
631
970
|
const handleDelete = react.useCallback(
|
|
632
971
|
(path) => {
|
|
633
|
-
|
|
634
|
-
onChange(updated);
|
|
972
|
+
onChange(deleteByPath(value, path));
|
|
635
973
|
},
|
|
636
974
|
[value, onChange]
|
|
637
975
|
);
|
|
@@ -645,8 +983,7 @@ var TreeEditor = ({
|
|
|
645
983
|
object: {},
|
|
646
984
|
array: []
|
|
647
985
|
};
|
|
648
|
-
|
|
649
|
-
onChange(updated);
|
|
986
|
+
onChange(setByPath(value, path, defaults[newType]));
|
|
650
987
|
},
|
|
651
988
|
[value, onChange]
|
|
652
989
|
);
|
|
@@ -659,19 +996,39 @@ var TreeEditor = ({
|
|
|
659
996
|
const obj = value;
|
|
660
997
|
let newKey = "newKey";
|
|
661
998
|
let counter = 1;
|
|
662
|
-
while (newKey in obj) {
|
|
663
|
-
newKey = `newKey${counter++}`;
|
|
664
|
-
}
|
|
999
|
+
while (newKey in obj) newKey = `newKey${counter++}`;
|
|
665
1000
|
onChange({ ...obj, [newKey]: "" });
|
|
666
1001
|
} else if (Array.isArray(value)) {
|
|
667
1002
|
onChange([...value, ""]);
|
|
668
1003
|
}
|
|
669
1004
|
}, [value, onChange]);
|
|
670
1005
|
if (tree === null) {
|
|
671
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
672
|
-
/* @__PURE__ */ jsxRuntime.
|
|
673
|
-
|
|
674
|
-
|
|
1006
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `mjr-tree-editor mjr-tree-editor--empty ${className}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mjr-tree__empty", children: [
|
|
1007
|
+
/* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "40", height: "40", viewBox: "0 0 24 24", fill: "none", opacity: "0.3", children: [
|
|
1008
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1009
|
+
"path",
|
|
1010
|
+
{
|
|
1011
|
+
d: "M3 7V17C3 18.1 3.9 19 5 19H19C20.1 19 21 18.1 21 17V7C21 5.9 20.1 5 19 5H5C3.9 5 3 5.9 3 7Z",
|
|
1012
|
+
stroke: "currentColor",
|
|
1013
|
+
strokeWidth: "1.5"
|
|
1014
|
+
}
|
|
1015
|
+
),
|
|
1016
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1017
|
+
"path",
|
|
1018
|
+
{
|
|
1019
|
+
d: "M7 9H17M7 13H13",
|
|
1020
|
+
stroke: "currentColor",
|
|
1021
|
+
strokeWidth: "1.5",
|
|
1022
|
+
strokeLinecap: "round"
|
|
1023
|
+
}
|
|
1024
|
+
)
|
|
1025
|
+
] }),
|
|
1026
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { children: "No valid JSON to display" }),
|
|
1027
|
+
!readOnly && /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "mjr-tree__add-btn", onClick: () => onChange({}), children: [
|
|
1028
|
+
"+",
|
|
1029
|
+
" Create empty object"
|
|
1030
|
+
] })
|
|
1031
|
+
] }) });
|
|
675
1032
|
}
|
|
676
1033
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
677
1034
|
"div",
|
|
@@ -690,16 +1047,21 @@ var TreeEditor = ({
|
|
|
690
1047
|
onKeyChange: handleKeyChange,
|
|
691
1048
|
onDelete: handleDelete,
|
|
692
1049
|
onTypeChange: handleTypeChange,
|
|
693
|
-
readOnly
|
|
1050
|
+
readOnly,
|
|
1051
|
+
searchQuery,
|
|
1052
|
+
searchCaseSensitive
|
|
694
1053
|
}
|
|
695
1054
|
),
|
|
696
|
-
!readOnly && /* @__PURE__ */ jsxRuntime.
|
|
1055
|
+
!readOnly && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
697
1056
|
"button",
|
|
698
1057
|
{
|
|
699
|
-
className: "mjr-
|
|
1058
|
+
className: "mjr-tree__add-btn mjr-tree__add-btn--root",
|
|
700
1059
|
onClick: handleAddProperty,
|
|
701
1060
|
"data-testid": "add-property",
|
|
702
|
-
children:
|
|
1061
|
+
children: [
|
|
1062
|
+
/* @__PURE__ */ jsxRuntime.jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 2v8M2 6h8", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }) }),
|
|
1063
|
+
"Add property"
|
|
1064
|
+
]
|
|
703
1065
|
}
|
|
704
1066
|
)
|
|
705
1067
|
]
|
|
@@ -727,17 +1089,13 @@ function buildTree(value, path, key, depth, expandedPaths) {
|
|
|
727
1089
|
const keys = Object.keys(obj);
|
|
728
1090
|
node.childCount = keys.length;
|
|
729
1091
|
if (expanded) {
|
|
730
|
-
node.children = keys.map(
|
|
731
|
-
(k) => buildTree(obj[k], `${path}.${k}`, k, depth + 1, expandedPaths)
|
|
732
|
-
).filter(Boolean);
|
|
1092
|
+
node.children = keys.map((k) => buildTree(obj[k], `${path}.${k}`, k, depth + 1, expandedPaths)).filter(Boolean);
|
|
733
1093
|
}
|
|
734
1094
|
} else if (type === "array") {
|
|
735
1095
|
const arr = value;
|
|
736
1096
|
node.childCount = arr.length;
|
|
737
1097
|
if (expanded) {
|
|
738
|
-
node.children = arr.map(
|
|
739
|
-
(item, i) => buildTree(item, `${path}[${i}]`, i, depth + 1, expandedPaths)
|
|
740
|
-
).filter(Boolean);
|
|
1098
|
+
node.children = arr.map((item, i) => buildTree(item, `${path}[${i}]`, i, depth + 1, expandedPaths)).filter(Boolean);
|
|
741
1099
|
}
|
|
742
1100
|
}
|
|
743
1101
|
return node;
|
|
@@ -757,11 +1115,8 @@ function getNestedValue(obj, path) {
|
|
|
757
1115
|
let current = obj;
|
|
758
1116
|
for (const seg of segments) {
|
|
759
1117
|
if (current === null || current === void 0) return void 0;
|
|
760
|
-
if (Array.isArray(current))
|
|
761
|
-
|
|
762
|
-
} else if (typeof current === "object") {
|
|
763
|
-
current = current[seg];
|
|
764
|
-
}
|
|
1118
|
+
if (Array.isArray(current)) current = current[parseInt(seg, 10)];
|
|
1119
|
+
else if (typeof current === "object") current = current[seg];
|
|
765
1120
|
}
|
|
766
1121
|
return current;
|
|
767
1122
|
}
|
|
@@ -975,7 +1330,13 @@ function validateSchema(value, schema, path = "$") {
|
|
|
975
1330
|
const re = new RegExp(schema.pattern);
|
|
976
1331
|
if (!re.test(value)) {
|
|
977
1332
|
errors.push(
|
|
978
|
-
makeError(
|
|
1333
|
+
makeError(
|
|
1334
|
+
`String must match pattern "${schema.pattern}"`,
|
|
1335
|
+
path,
|
|
1336
|
+
"pattern",
|
|
1337
|
+
schema.pattern,
|
|
1338
|
+
value
|
|
1339
|
+
)
|
|
979
1340
|
);
|
|
980
1341
|
}
|
|
981
1342
|
}
|
|
@@ -1015,19 +1376,37 @@ function validateSchema(value, schema, path = "$") {
|
|
|
1015
1376
|
}
|
|
1016
1377
|
if (schema.multipleOf !== void 0 && value % schema.multipleOf !== 0) {
|
|
1017
1378
|
errors.push(
|
|
1018
|
-
makeError(
|
|
1379
|
+
makeError(
|
|
1380
|
+
`Value must be a multiple of ${schema.multipleOf}`,
|
|
1381
|
+
path,
|
|
1382
|
+
"multipleOf",
|
|
1383
|
+
schema.multipleOf,
|
|
1384
|
+
value
|
|
1385
|
+
)
|
|
1019
1386
|
);
|
|
1020
1387
|
}
|
|
1021
1388
|
}
|
|
1022
1389
|
if (Array.isArray(value)) {
|
|
1023
1390
|
if (schema.minItems !== void 0 && value.length < schema.minItems) {
|
|
1024
1391
|
errors.push(
|
|
1025
|
-
makeError(
|
|
1392
|
+
makeError(
|
|
1393
|
+
`Array must have at least ${schema.minItems} items`,
|
|
1394
|
+
path,
|
|
1395
|
+
"minItems",
|
|
1396
|
+
schema.minItems,
|
|
1397
|
+
value
|
|
1398
|
+
)
|
|
1026
1399
|
);
|
|
1027
1400
|
}
|
|
1028
1401
|
if (schema.maxItems !== void 0 && value.length > schema.maxItems) {
|
|
1029
1402
|
errors.push(
|
|
1030
|
-
makeError(
|
|
1403
|
+
makeError(
|
|
1404
|
+
`Array must have at most ${schema.maxItems} items`,
|
|
1405
|
+
path,
|
|
1406
|
+
"maxItems",
|
|
1407
|
+
schema.maxItems,
|
|
1408
|
+
value
|
|
1409
|
+
)
|
|
1031
1410
|
);
|
|
1032
1411
|
}
|
|
1033
1412
|
if (schema.uniqueItems && new Set(value.map((v) => JSON.stringify(v))).size !== value.length) {
|
|
@@ -1326,6 +1705,27 @@ function useSearch(text) {
|
|
|
1326
1705
|
function escapeRegex(str) {
|
|
1327
1706
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1328
1707
|
}
|
|
1708
|
+
function useContainerWidth(ref) {
|
|
1709
|
+
const [width, setWidth] = react.useState(0);
|
|
1710
|
+
const updateWidth = react.useCallback(() => {
|
|
1711
|
+
if (ref.current) {
|
|
1712
|
+
setWidth(ref.current.clientWidth);
|
|
1713
|
+
}
|
|
1714
|
+
}, [ref]);
|
|
1715
|
+
react.useEffect(() => {
|
|
1716
|
+
const el = ref.current;
|
|
1717
|
+
if (!el) return;
|
|
1718
|
+
updateWidth();
|
|
1719
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
1720
|
+
const observer = new ResizeObserver(() => updateWidth());
|
|
1721
|
+
observer.observe(el);
|
|
1722
|
+
return () => observer.disconnect();
|
|
1723
|
+
}
|
|
1724
|
+
window.addEventListener("resize", updateWidth);
|
|
1725
|
+
return () => window.removeEventListener("resize", updateWidth);
|
|
1726
|
+
}, [ref, updateWidth]);
|
|
1727
|
+
return width;
|
|
1728
|
+
}
|
|
1329
1729
|
|
|
1330
1730
|
// src/core/formatter.ts
|
|
1331
1731
|
function formatJson(text, indent = 2) {
|
|
@@ -1447,6 +1847,8 @@ var darkTheme = {
|
|
|
1447
1847
|
treeSelected: "#094771",
|
|
1448
1848
|
typeBadgeBg: "#2d2d30"
|
|
1449
1849
|
};
|
|
1850
|
+
var BREAKPOINT_SM = 480;
|
|
1851
|
+
var BREAKPOINT_MD = 768;
|
|
1450
1852
|
var JsonEditor = ({
|
|
1451
1853
|
value: externalValue,
|
|
1452
1854
|
onChange,
|
|
@@ -1460,13 +1862,13 @@ var JsonEditor = ({
|
|
|
1460
1862
|
height = 400,
|
|
1461
1863
|
readOnly = false,
|
|
1462
1864
|
searchable = true,
|
|
1463
|
-
sortable = true,
|
|
1865
|
+
sortable: _sortable = true,
|
|
1464
1866
|
indentation = 2,
|
|
1465
1867
|
lineNumbers = true,
|
|
1466
1868
|
bracketMatching = true,
|
|
1467
|
-
maxSize,
|
|
1468
|
-
virtualize = "auto",
|
|
1469
|
-
onError,
|
|
1869
|
+
maxSize: _maxSize,
|
|
1870
|
+
virtualize: _virtualize = "auto",
|
|
1871
|
+
onError: _onError,
|
|
1470
1872
|
onFocus,
|
|
1471
1873
|
onBlur,
|
|
1472
1874
|
className = "",
|
|
@@ -1597,11 +1999,18 @@ var JsonEditor = ({
|
|
|
1597
1999
|
document.addEventListener("keydown", handleKeyDown);
|
|
1598
2000
|
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
1599
2001
|
}, [history, parser, search, searchable]);
|
|
2002
|
+
const containerRef = react.useRef(null);
|
|
2003
|
+
const containerWidth = useContainerWidth(containerRef);
|
|
2004
|
+
const isSmall = containerWidth > 0 && containerWidth <= BREAKPOINT_SM;
|
|
2005
|
+
const isMedium = containerWidth > 0 && containerWidth <= BREAKPOINT_MD;
|
|
2006
|
+
const sizeClass = isSmall ? "mjr-editor--sm" : isMedium ? "mjr-editor--md" : "";
|
|
2007
|
+
const shouldStackSplit = mode === "split" && isMedium;
|
|
1600
2008
|
const heightStyle = typeof height === "number" ? `${height}px` : height;
|
|
1601
2009
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1602
2010
|
"div",
|
|
1603
2011
|
{
|
|
1604
|
-
|
|
2012
|
+
ref: containerRef,
|
|
2013
|
+
className: `mjr-editor ${sizeClass} ${className}`.trim(),
|
|
1605
2014
|
style: { ...themeVars, height: heightStyle, ...style },
|
|
1606
2015
|
role: "application",
|
|
1607
2016
|
"aria-label": ariaLabel,
|
|
@@ -1629,30 +2038,53 @@ var JsonEditor = ({
|
|
|
1629
2038
|
readOnly
|
|
1630
2039
|
}
|
|
1631
2040
|
),
|
|
1632
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
2041
|
+
search.isActive && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2042
|
+
SearchBar,
|
|
2043
|
+
{
|
|
2044
|
+
query: search.query,
|
|
2045
|
+
onQueryChange: search.setQuery,
|
|
2046
|
+
matches: search.matches,
|
|
2047
|
+
currentMatchIndex: search.currentMatchIndex,
|
|
2048
|
+
totalMatches: search.totalMatches,
|
|
2049
|
+
onNext: search.goToNext,
|
|
2050
|
+
onPrevious: search.goToPrevious,
|
|
2051
|
+
onClose: search.close,
|
|
2052
|
+
options: search.options,
|
|
2053
|
+
onOptionsChange: search.setOptions
|
|
2054
|
+
}
|
|
2055
|
+
),
|
|
2056
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2057
|
+
"div",
|
|
2058
|
+
{
|
|
2059
|
+
className: `mjr-editor__content ${shouldStackSplit ? "mjr-editor__content--responsive-stack" : ""}`,
|
|
2060
|
+
children: [
|
|
2061
|
+
(mode === "code" || mode === "split") && /* @__PURE__ */ jsxRuntime.jsx("div", { className: `mjr-editor__panel ${mode === "split" ? "mjr-editor__panel--half" : ""}`, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2062
|
+
CodeEditor,
|
|
2063
|
+
{
|
|
2064
|
+
value: parser.text,
|
|
2065
|
+
onChange: handleTextChange,
|
|
2066
|
+
parseError: parser.parseError,
|
|
2067
|
+
readOnly,
|
|
2068
|
+
lineNumbers: isSmall ? false : lineNumbers,
|
|
2069
|
+
bracketMatching,
|
|
2070
|
+
searchMatches: search.matches,
|
|
2071
|
+
currentMatchIndex: search.currentMatchIndex,
|
|
2072
|
+
onCursorChange: setCursor
|
|
2073
|
+
}
|
|
2074
|
+
) }),
|
|
2075
|
+
(mode === "tree" || mode === "split") && /* @__PURE__ */ jsxRuntime.jsx("div", { className: `mjr-editor__panel ${mode === "split" ? "mjr-editor__panel--half" : ""}`, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2076
|
+
TreeEditor,
|
|
2077
|
+
{
|
|
2078
|
+
value: parser.parsedValue,
|
|
2079
|
+
onChange: handleTreeChange,
|
|
2080
|
+
readOnly,
|
|
2081
|
+
searchQuery: search.isActive ? search.query : "",
|
|
2082
|
+
searchCaseSensitive: search.options.caseSensitive
|
|
2083
|
+
}
|
|
2084
|
+
) })
|
|
2085
|
+
]
|
|
2086
|
+
}
|
|
2087
|
+
),
|
|
1656
2088
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1657
2089
|
StatusBar,
|
|
1658
2090
|
{
|
|
@@ -1683,6 +2115,7 @@ exports.runCustomValidators = runCustomValidators;
|
|
|
1683
2115
|
exports.setByPath = setByPath;
|
|
1684
2116
|
exports.sortJsonKeys = sortJsonKeys;
|
|
1685
2117
|
exports.stringifyJson = stringifyJson;
|
|
2118
|
+
exports.useContainerWidth = useContainerWidth;
|
|
1686
2119
|
exports.useJsonParser = useJsonParser;
|
|
1687
2120
|
exports.useSearch = useSearch;
|
|
1688
2121
|
exports.useUndoRedo = useUndoRedo;
|