@mongoosejs/studio 0.5.4 → 0.5.5

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.
@@ -3,23 +3,107 @@
3
3
  Empty array
4
4
  </div>
5
5
 
6
- <div v-else class="mt-2">
6
+ <div v-else>
7
+ <div class="mb-2 flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-3">
8
+ <div class="relative min-w-0 flex-1">
9
+ <input
10
+ v-model="arraySearchQuery"
11
+ type="text"
12
+ autocomplete="off"
13
+ aria-label="Search in this array"
14
+ placeholder="Search in this array…"
15
+ class="w-full rounded-md border border-edge bg-surface py-1.5 pl-2 pr-8 text-xs text-content placeholder:text-content-tertiary focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary"
16
+ />
17
+ <button
18
+ v-if="arraySearchNormalized"
19
+ type="button"
20
+ class="absolute right-1 top-1/2 -translate-y-1/2 rounded px-1.5 py-0.5 text-[11px] text-content-tertiary hover:bg-slate-100 hover:text-content"
21
+ title="Clear search"
22
+ @click="arraySearchQuery = ''">
23
+ Clear
24
+ </button>
25
+ </div>
26
+ <span v-if="arraySearchNormalized" class="shrink-0 text-xs text-content-tertiary">
27
+ {{ filteredArrayRows.length }} of {{ arrayValue.length }} shown
28
+ </span>
29
+ </div>
30
+
7
31
  <div
8
- v-for="(item, index) in arrayValue"
9
- :key="index"
10
- :title="'Index: ' + index"
11
- class="mb-1.5 last:mb-0 py-2.5 px-3 pl-4 bg-transparent border-l-[3px] border-l-blue-500 rounded-none transition-all duration-200 cursor-pointer relative hover:bg-slate-50 hover:border-l-blue-600 hover:shadow-lg hover:-translate-y-0.5">
12
- <div class="absolute -left-3 top-1/2 -translate-y-1/2 w-5 h-5 bg-blue-500 text-white rounded-full flex items-center justify-center text-[10px] font-semibold font-mono z-10 hover:bg-blue-600">{{ index >= 1000 ? '1k+' : index }}</div>
13
- <div v-if="arrayUtils.isObjectItem(item)" class="flex flex-col gap-1 mt-1 px-2">
14
- <div
15
- v-for="key in arrayUtils.getItemKeys(item)"
16
- :key="key"
17
- class="flex items-start gap-2 text-xs font-mono">
18
- <span class="font-semibold text-gray-600 flex-shrink-0 min-w-[80px]">{{ key }}:</span>
19
- <span class="text-gray-800 break-words whitespace-pre-wrap flex-1">{{ arrayUtils.formatItemValue(item, key) }}</span>
32
+ v-if="filteredArrayRows.length === 0"
33
+ class="rounded-md border border-dashed border-edge py-6 text-center text-xs text-content-tertiary">
34
+ No items match "{{ arraySearchQuery.trim() }}".
35
+ </div>
36
+
37
+ <div v-else-if="showAsTable" class="mt-2 overflow-x-auto rounded-md border border-edge">
38
+ <table class="min-w-full text-left text-xs font-mono text-content">
39
+ <thead class="bg-slate-100 text-content-secondary border-b border-edge">
40
+ <tr>
41
+ <th scope="col" class="sticky left-0 z-[1] bg-slate-100 px-2 py-2 font-semibold border-r border-edge w-10 text-center">#</th>
42
+ <th
43
+ v-for="key in tableColumnKeys"
44
+ :key="key"
45
+ scope="col"
46
+ class="px-3 py-2 font-semibold whitespace-nowrap border-r border-edge last:border-r-0">
47
+ {{ key }}
48
+ </th>
49
+ </tr>
50
+ </thead>
51
+ <tbody class="divide-y divide-edge bg-surface">
52
+ <tr v-for="row in displayedArrayRows" :key="row.index" class="hover:bg-slate-50/80">
53
+ <td class="sticky left-0 z-[1] bg-inherit px-2 py-2 text-center text-content-tertiary border-r border-edge">{{ row.index }}</td>
54
+ <td
55
+ v-for="key in tableColumnKeys"
56
+ :key="key"
57
+ class="px-3 py-2 align-top break-words whitespace-pre-wrap max-w-md border-r border-edge last:border-r-0">
58
+ {{ Object.prototype.hasOwnProperty.call(row.item, key) ? arrayUtils.formatItemValue(row.item, key) : '' }}
59
+ </td>
60
+ </tr>
61
+ </tbody>
62
+ </table>
63
+ </div>
64
+
65
+ <div v-else class="mt-2">
66
+ <div
67
+ v-for="row in displayedArrayRows"
68
+ :key="row.index"
69
+ :title="'Index: ' + row.index"
70
+ class="mb-1.5 last:mb-0 py-2.5 px-3 pl-4 bg-transparent border-l-[3px] border-l-slate-100 rounded-none transition-all duration-200 cursor-pointer relative hover:bg-slate-50 hover:border-l-slate-100 hover:shadow-lg hover:-translate-y-0.5">
71
+ <div class="absolute -left-3 top-1/2 -translate-y-1/2 w-5 h-5 bg-slate-100 text-content rounded-full flex items-center justify-center text-[10px] font-semibold font-mono z-10 hover:bg-slate-100">{{ row.index >= 1000 ? '1k+' : row.index }}</div>
72
+ <div v-if="arrayUtils.isObjectItem(row.item)" class="flex flex-col gap-1 mt-1 px-2">
73
+ <div
74
+ v-for="key in arrayUtils.getItemKeys(row.item)"
75
+ :key="key"
76
+ class="flex items-start gap-2 text-xs font-mono">
77
+ <span class="font-semibold text-gray-600 flex-shrink-0 min-w-[80px]">{{ key }}:</span>
78
+ <span class="text-gray-800 break-words whitespace-pre-wrap flex-1">{{ arrayUtils.formatItemValue(row.item, key) }}</span>
79
+ </div>
20
80
  </div>
81
+ <div v-else class="text-xs py-1.5 px-2 font-mono text-gray-800 break-words whitespace-pre-wrap mt-1">{{ arrayUtils.formatValue(row.item) }}</div>
82
+ </div>
83
+ </div>
84
+
85
+ <div
86
+ v-if="remainingArrayCount > 0"
87
+ class="mb-1.5 py-2.5 px-3 pl-4 bg-transparent border-none border-l-[3px] border-l-slate-100 rounded-none transition-all duration-200 cursor-pointer relative opacity-70 hover:opacity-100">
88
+ <div class="text-xs py-1.5 px-2 font-mono text-content-tertiary italic break-words whitespace-pre-wrap mt-1">
89
+ ... and {{ remainingArrayCount }} more item{{ remainingArrayCount !== 1 ? 's' : '' }}
21
90
  </div>
22
- <div v-else class="text-xs py-1.5 px-2 font-mono text-gray-800 break-words whitespace-pre-wrap mt-1">{{ arrayUtils.formatValue(item) }}</div>
23
91
  </div>
92
+
93
+ <button
94
+ v-if="canToggleRows"
95
+ @click="toggleExpansion"
96
+ class="mt-2 text-merlot-700 hover:text-merlot-900 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5"
97
+ >
98
+ <svg
99
+ class="w-4 h-4"
100
+ :class="{ 'rotate-180': isExpanded }"
101
+ fill="none"
102
+ stroke="currentColor"
103
+ viewBox="0 0 24 24">
104
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
105
+ </svg>
106
+ {{ expansionToggleLabel }}
107
+ </button>
24
108
  </div>
25
109
  </div>
@@ -1,16 +1,83 @@
1
1
  'use strict';
2
2
 
3
3
  const template = require('./detail-array.html');
4
+ const { isArrayOfObjects, unionKeysForArrayOfObjects, arrayItemMatchesSearch } = require('../array-utils');
4
5
 
5
6
  module.exports = app => app.component('detail-array', {
6
7
  template: template,
7
- props: ['value'],
8
+ props: {
9
+ value: {},
10
+ /** 'list' | 'table' — table applies to arrays of plain objects */
11
+ viewMode: {
12
+ type: String,
13
+ default: 'list'
14
+ },
15
+ truncate: {
16
+ type: Boolean,
17
+ default: false
18
+ },
19
+ initialLimit: {
20
+ type: Number,
21
+ default: 2
22
+ }
23
+ },
8
24
  data() {
9
25
  return {
10
- arrayValue: []
26
+ arrayValue: [],
27
+ arraySearchQuery: '',
28
+ isExpanded: false
11
29
  };
12
30
  },
31
+ computed: {
32
+ effectiveViewMode() {
33
+ return this.viewMode === 'table' ? 'table' : 'list';
34
+ },
35
+ showAsTable() {
36
+ return this.effectiveViewMode === 'table' && isArrayOfObjects(this.arrayValue);
37
+ },
38
+ tableColumnKeys() {
39
+ return unionKeysForArrayOfObjects(this.arrayValue);
40
+ },
41
+ arraySearchNormalized() {
42
+ return (this.arraySearchQuery || '').trim().toLowerCase();
43
+ },
44
+ filteredArrayRows() {
45
+ if (!Array.isArray(this.arrayValue)) {
46
+ return [];
47
+ }
48
+ return this.arrayValue
49
+ .map((item, index) => ({ item, index }))
50
+ .filter(({ item }) => arrayItemMatchesSearch(item, this.arraySearchNormalized));
51
+ },
52
+ displayedArrayRows() {
53
+ if (!this.shouldLimitRows) {
54
+ return this.filteredArrayRows;
55
+ }
56
+ return this.filteredArrayRows.slice(0, this.initialLimit);
57
+ },
58
+ shouldLimitRows() {
59
+ return this.truncate
60
+ && !this.isExpanded
61
+ && !this.arraySearchNormalized
62
+ && this.filteredArrayRows.length > this.initialLimit;
63
+ },
64
+ canToggleRows() {
65
+ return this.truncate && !this.arraySearchNormalized && this.arrayValue.length > this.initialLimit;
66
+ },
67
+ expansionToggleLabel() {
68
+ return this.isExpanded ? 'Show less' : `Show all ${this.arrayValue.length} items`;
69
+ },
70
+ remainingArrayCount() {
71
+ if (!this.shouldLimitRows) {
72
+ return 0;
73
+ }
74
+ return this.filteredArrayRows.length - this.displayedArrayRows.length;
75
+ }
76
+ },
13
77
  methods: {
78
+ toggleExpansion() {
79
+ this.isExpanded = !this.isExpanded;
80
+ },
14
81
  initializeArray() {
15
82
  if (this.value == null) {
16
83
  this.arrayValue = [];
@@ -28,9 +95,12 @@ module.exports = app => app.component('detail-array', {
28
95
  value: {
29
96
  handler(newValue) {
30
97
  this.initializeArray();
98
+ if (this.arrayValue.length <= this.initialLimit) {
99
+ this.isExpanded = false;
100
+ }
31
101
  },
32
102
  deep: true,
33
103
  immediate: true
34
104
  }
35
105
  }
36
- });
106
+ });
@@ -120,6 +120,13 @@
120
120
  >
121
121
  Copy Document
122
122
  </button>
123
+ <button
124
+ @click="openValidationModal()"
125
+ type="button"
126
+ class="flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-green-100"
127
+ >
128
+ Validate
129
+ </button>
123
130
  <button
124
131
  @click="openScriptDrawer()"
125
132
  type="button"
@@ -222,6 +229,13 @@
222
229
  >
223
230
  Copy Document
224
231
  </button>
232
+ <button
233
+ @click="openValidationModal()"
234
+ type="button"
235
+ class="flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-green-100"
236
+ >
237
+ Validate
238
+ </button>
225
239
  <button
226
240
  @click="openScriptDrawer()"
227
241
  type="button"
@@ -280,6 +294,43 @@
280
294
  <clone-document :currentModel="model" :doc="document" :schemaPaths="schemaPaths" @close="showClonedDocument"></clone-document>
281
295
  </template>
282
296
  </modal>
297
+ <modal v-if="shouldShowValidationModal">
298
+ <template v-slot:body>
299
+ <div class="modal-exit" @click="shouldShowValidationModal = false;">&times;</div>
300
+ <div class="text-xl font-bold mb-2">Validate Document</div>
301
+ <div v-if="!validationResult" class="space-y-3">
302
+ <div class="h-4 w-32 animate-pulse rounded bg-muted"></div>
303
+ <div class="h-20 w-full animate-pulse rounded bg-muted"></div>
304
+ </div>
305
+ <div v-else class="space-y-3">
306
+ <div
307
+ class="rounded px-3 py-2 text-sm font-semibold"
308
+ :class="validationResult.valid ? 'bg-green-50 text-green-700' : 'bg-red-50 text-red-700'"
309
+ >
310
+ {{ validationResult.valid ? 'Document is valid' : 'Document is invalid' }}
311
+ </div>
312
+ <div v-if="!validationResult.valid" class="rounded border border-edge bg-muted p-3 text-sm text-content-secondary">
313
+ {{ validationResult.error }}
314
+ </div>
315
+ <div v-if="validationResult?.errors && Object.keys(validationResult.errors).length > 0" class="rounded border border-edge">
316
+ <div class="border-b border-edge px-3 py-2 text-sm font-semibold text-content-secondary">Invalid Paths</div>
317
+ <div class="max-h-72 overflow-auto">
318
+ <div
319
+ v-for="[path, pathError] in Object.entries(validationResult.errors)"
320
+ :key="path"
321
+ class="grid gap-2 border-b border-edge px-3 py-2 text-sm last:border-b-0 md:grid-cols-[minmax(160px,220px)_1fr]"
322
+ >
323
+ <div class="font-mono text-content">{{ path }}</div>
324
+ <div class="text-content-secondary">
325
+ <div>{{ pathError.message }}</div>
326
+ <div v-if="pathError.kind" class="mt-1 text-xs text-content-tertiary">Kind: {{ pathError.kind }}</div>
327
+ </div>
328
+ </div>
329
+ </div>
330
+ </div>
331
+ </div>
332
+ </template>
333
+ </modal>
283
334
  </div>
284
335
  <execute-script
285
336
  :visible="scriptDrawerOpen"
@@ -26,6 +26,8 @@ module.exports = app => app.component('document', {
26
26
  shouldShowConfirmModal: false,
27
27
  shouldShowDeleteModal: false,
28
28
  shouldShowCloneModal: false,
29
+ shouldShowValidationModal: false,
30
+ validationResult: null,
29
31
  previousQuery: null,
30
32
  lastUpdatedAt: null,
31
33
  isRefreshing: false,
@@ -351,6 +353,17 @@ module.exports = app => app.component('document', {
351
353
  this.desktopMenuOpen = false;
352
354
  this.mobileMenuOpen = false;
353
355
  },
356
+ async openValidationModal() {
357
+ this.desktopMenuOpen = false;
358
+ this.mobileMenuOpen = false;
359
+ this.shouldShowValidationModal = true;
360
+ this.validationResult = null;
361
+ const { result } = await api.Model.validateDocument({
362
+ model: this.model,
363
+ documentId: this.documentId
364
+ });
365
+ this.validationResult = result;
366
+ },
354
367
  closeScriptDrawer() {
355
368
  this.scriptDrawerOpen = false;
356
369
  },
@@ -55,6 +55,7 @@
55
55
  :editting="editting"
56
56
  :changes="changes"
57
57
  :highlight="true"
58
+ :searchQuery="searchQuery"
58
59
  :invalid="invalid"></document-property>
59
60
  </div>
60
61
 
@@ -67,7 +68,7 @@
67
68
  <!-- Virtual Field Header (Always Visible) -->
68
69
  <div
69
70
  @click="toggleVirtualField(path.name)"
70
- class="p-3 bg-amber-100 hover:bg-amber-200 cursor-pointer flex items-center justify-between border-b border-edge transition-colors duration-200 ease-in-out"
71
+ class="p-3 bg-slate-100 hover:bg-muted cursor-pointer flex items-center justify-between border-b border-edge transition-colors duration-200 ease-in-out"
71
72
  >
72
73
  <div class="flex items-center">
73
74
  <svg
@@ -79,7 +80,12 @@
79
80
  >
80
81
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
81
82
  </svg>
82
- <span class="font-medium text-content">{{path.name}}</span>
83
+ <span class="font-medium text-content">
84
+ <span
85
+ v-for="(part, index) in getSearchMatchParts(path.name)"
86
+ :key="index"
87
+ :class="{ 'font-bold': part.matched }">{{ part.text }}</span>
88
+ </span>
83
89
  <span v-if="path.isVirtual" class="ml-2 text-sm text-purple-600">(virtual - {{getVirtualFieldType(path)}})</span>
84
90
  <span v-else class="ml-2 text-sm text-blue-600">(user-added - {{getVirtualFieldType(path)}})</span>
85
91
  </div>
@@ -109,6 +115,7 @@
109
115
  :editting="editting"
110
116
  :changes="changes"
111
117
  :highlight="false"
118
+ :searchQuery="searchQuery"
112
119
  :invalid="invalid"></document-property>
113
120
  </div>
114
121
 
@@ -121,7 +128,7 @@
121
128
  <!-- Virtual Field Header (Always Visible) -->
122
129
  <div
123
130
  @click="toggleVirtualField(path.name)"
124
- class="p-3 bg-page hover:bg-muted cursor-pointer flex items-center justify-between border-b border-edge transition-colors duration-200 ease-in-out"
131
+ class="p-3 bg-slate-100 hover:bg-muted cursor-pointer flex items-center justify-between border-b border-edge transition-colors duration-200 ease-in-out"
125
132
  >
126
133
  <div class="flex items-center">
127
134
  <svg
@@ -286,6 +286,34 @@ module.exports = app => app.component('document-details', {
286
286
 
287
287
  return this.virtualSearchMatchSet.has(virtual.name);
288
288
  },
289
+ getSearchMatchParts(text) {
290
+ const normalizedQuery = typeof this.searchQuery === 'string' ? this.searchQuery.trim() : '';
291
+ if (!normalizedQuery) {
292
+ return [{ text, matched: false }];
293
+ }
294
+
295
+ const lowerText = String(text).toLowerCase();
296
+ const lowerQuery = normalizedQuery.toLowerCase();
297
+ const parts = [];
298
+ let position = 0;
299
+ let matchIndex = lowerText.indexOf(lowerQuery, position);
300
+
301
+ while (matchIndex !== -1) {
302
+ if (matchIndex > position) {
303
+ parts.push({ text: text.slice(position, matchIndex), matched: false });
304
+ }
305
+ const matchEnd = matchIndex + normalizedQuery.length;
306
+ parts.push({ text: text.slice(matchIndex, matchEnd), matched: true });
307
+ position = matchEnd;
308
+ matchIndex = lowerText.indexOf(lowerQuery, position);
309
+ }
310
+
311
+ if (position < text.length) {
312
+ parts.push({ text: text.slice(position), matched: false });
313
+ }
314
+
315
+ return parts.length > 0 ? parts : [{ text, matched: false }];
316
+ },
289
317
  isVirtualFieldCollapsed(fieldName) {
290
318
  return this.collapsedVirtuals.has(fieldName);
291
319
  },
@@ -2,8 +2,7 @@
2
2
  <!-- Collapsible Header -->
3
3
  <div
4
4
  @click="toggleCollapse"
5
- class="p-1 cursor-pointer flex items-center justify-between border-b border-edge transition-colors duration-200 ease-in-out"
6
- :class="{ 'bg-amber-100 hover:bg-amber-200': highlight, 'bg-slate-100 hover:bg-muted': !highlight }"
5
+ class="p-1 cursor-pointer flex items-center justify-between border-b border-edge bg-slate-100 hover:bg-muted transition-colors duration-200 ease-in-out"
7
6
  style="overflow: visible; position: relative;"
8
7
  >
9
8
  <div class="flex items-center" >
@@ -16,11 +15,32 @@
16
15
  >
17
16
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
18
17
  </svg>
19
- <span class="font-medium text-content">{{path.path}}</span>
18
+ <span class="font-medium text-content">
19
+ <span
20
+ v-for="(part, index) in pathNameParts"
21
+ :key="index"
22
+ :class="{ 'font-bold': part.matched }">{{ part.text }}</span>
23
+ </span>
20
24
  <span class="ml-2 text-sm text-content-tertiary">({{(path.instance || 'unknown').toLowerCase()}})</span>
25
+ <div v-if="path.instance === 'Array' && canUseArrayTableView" class="ml-3 inline-flex items-center rounded-full bg-gray-200 p-0.5 text-xs font-semibold">
26
+ <button
27
+ type="button"
28
+ class="rounded-full px-2.5 py-0.5 transition"
29
+ :class="arrayDetailViewMode === 'list' ? 'bg-merlot-700 text-white shadow' : 'text-content-secondary hover:text-content'"
30
+ @click.stop="setArrayDetailViewMode('list')">
31
+ List
32
+ </button>
33
+ <button
34
+ type="button"
35
+ class="rounded-full px-2.5 py-0.5 transition"
36
+ :class="arrayDetailViewMode === 'table' ? 'bg-merlot-700 text-white shadow' : 'text-content-secondary hover:text-content'"
37
+ @click.stop="setArrayDetailViewMode('table')">
38
+ Table
39
+ </button>
40
+ </div>
21
41
  <div v-if="isGeoJsonGeometry" class="ml-3 inline-flex items-center gap-2">
22
42
  <div class="inline-flex items-center rounded-full bg-gray-200 p-0.5 text-xs font-semibold">
23
- <button
43
+ <button
24
44
  type="button"
25
45
  class="rounded-full px-2.5 py-0.5 transition"
26
46
  :class="detailViewMode === 'text' ? 'bg-blue-600 text-white shadow' : 'text-content-secondary hover:text-content'"
@@ -153,48 +173,20 @@
153
173
  </component>
154
174
  </div>
155
175
  <div v-else>
156
- <!-- Show truncated or full value based on needsTruncation and isValueExpanded -->
157
- <!-- Special handling for truncated arrays -->
158
- <div v-if="isArray && shouldShowTruncated" class="w-full">
159
- <div class="mt-2">
160
- <div
161
- v-for="(item, index) in truncatedArrayItems"
162
- :key="index"
163
- class="mb-1.5 py-2.5 px-3 pl-4 bg-transparent border-l-[3px] border-l-blue-500 rounded-none transition-all duration-200 cursor-pointer relative hover:bg-slate-50 hover:border-l-blue-600">
164
- <div class="absolute -left-2 top-1/2 -translate-y-1/2 w-5 h-5 bg-blue-500 text-white rounded-full flex items-center justify-center text-[10px] font-semibold font-mono z-10 hover:bg-blue-600">{{ index }}</div>
165
- <div v-if="arrayUtils.isObjectItem(item)" class="flex flex-col gap-1 mt-1 px-2">
166
- <div
167
- v-for="key in arrayUtils.getItemKeys(item)"
168
- :key="key"
169
- class="flex items-start gap-2 text-xs font-mono">
170
- <span class="font-semibold text-gray-600 flex-shrink-0 min-w-[80px]">{{ key }}:</span>
171
- <span class="text-gray-800 break-words whitespace-pre-wrap flex-1">{{ arrayUtils.formatItemValue(item, key) }}</span>
172
- </div>
173
- </div>
174
- <div v-else class="text-xs py-1.5 px-2 font-mono text-gray-800 break-words whitespace-pre-wrap mt-1">{{ arrayUtils.formatValue(item) }}</div>
175
- </div>
176
- <div class="mb-1.5 py-2.5 px-3 pl-4 bg-transparent border-none border-l-[3px] border-l-blue-500 rounded-none transition-all duration-200 cursor-pointer relative opacity-70 hover:opacity-100">
177
- <div class="text-xs py-1.5 px-2 font-mono text-content-tertiary italic break-words whitespace-pre-wrap mt-1">
178
- ... and {{ remainingArrayCount }} more item{{ remainingArrayCount !== 1 ? 's' : '' }}
179
- </div>
180
- </div>
181
- </div>
182
- <button
183
- @click="toggleValueExpansion"
184
- class="mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5"
185
- >
186
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
187
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
188
- </svg>
189
- Show all {{ arrayValue.length }} items
190
- </button>
191
- </div>
176
+ <!-- Arrays own their truncation and array search behavior in detail-array. -->
177
+ <component
178
+ v-if="isArray"
179
+ :is="getComponentForPath(path)"
180
+ :value="rawValue"
181
+ :viewMode="propertyDetailViewMode"
182
+ :truncate="needsTruncation"
183
+ @updated="renderedValue = $event"></component>
192
184
  <!-- Non-array truncated view -->
193
185
  <div v-else-if="shouldShowTruncated && !isArray" class="relative">
194
186
  <div class="text-content-secondary whitespace-pre-wrap break-words font-mono text-sm">{{truncatedString}}</div>
195
187
  <button
196
188
  @click="toggleValueExpansion"
197
- class="mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5"
189
+ class="mt-2 text-merlot-700 hover:text-merlot-900 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5"
198
190
  >
199
191
  <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
200
192
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
@@ -207,11 +199,11 @@
207
199
  <component
208
200
  :is="getComponentForPath(path)"
209
201
  :value="rawValue"
210
- :viewMode="isDatePath ? dateViewMode : (isObjectIdPath ? objectIdViewMode : detailViewMode)"
202
+ :viewMode="propertyDetailViewMode"
211
203
  @updated="renderedValue = $event"></component>
212
204
  <button
213
205
  @click="toggleValueExpansion"
214
- class="mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5"
206
+ class="mt-2 text-merlot-700 hover:text-merlot-900 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5"
215
207
  >
216
208
  <svg class="w-4 h-4 rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24">
217
209
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
@@ -224,7 +216,7 @@
224
216
  <component
225
217
  :is="getComponentForPath(path)"
226
218
  :value="rawValue"
227
- :viewMode="isDatePath ? dateViewMode : (isObjectIdPath ? objectIdViewMode : detailViewMode)"
219
+ :viewMode="propertyDetailViewMode"
228
220
  @updated="renderedValue = $event"></component>
229
221
  </div>
230
222
  </div>