@mongoosejs/studio 0.5.7 → 0.5.9

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.
@@ -60,6 +60,172 @@ module.exports = {
60
60
  };
61
61
 
62
62
 
63
+ /***/ },
64
+
65
+ /***/ "./frontend/src/_util/calendar.js"
66
+ /*!****************************************!*\
67
+ !*** ./frontend/src/_util/calendar.js ***!
68
+ \****************************************/
69
+ (module) {
70
+
71
+ "use strict";
72
+
73
+
74
+ function pad2(n) {
75
+ return String(n).padStart(2, '0');
76
+ }
77
+
78
+ function pad3(n) {
79
+ return String(n).padStart(3, '0');
80
+ }
81
+
82
+ /**
83
+ * @param {number} value
84
+ * @param {number} min
85
+ * @param {number} max
86
+ */
87
+ function clampInt(value, min, max) {
88
+ const n = Math.floor(Number(value));
89
+ if (Number.isNaN(n)) {
90
+ return min;
91
+ }
92
+ return Math.min(max, Math.max(min, n));
93
+ }
94
+
95
+ /**
96
+ * @param {Date} date
97
+ * @returns {string} YYYY-MM-DDTHH:mm:ss.SSS
98
+ */
99
+ function dateToDatetimeLocal(date) {
100
+ if (!(date instanceof Date) || Number.isNaN(date.getTime())) {
101
+ return '';
102
+ }
103
+ return [
104
+ date.getFullYear(),
105
+ '-',
106
+ pad2(date.getMonth() + 1),
107
+ '-',
108
+ pad2(date.getDate()),
109
+ 'T',
110
+ pad2(date.getHours()),
111
+ ':',
112
+ pad2(date.getMinutes()),
113
+ ':',
114
+ pad2(date.getSeconds()),
115
+ '.',
116
+ pad3(date.getMilliseconds())
117
+ ].join('');
118
+ }
119
+
120
+ /**
121
+ * @param {Date} date
122
+ * @returns {string} YYYY-MM-DD
123
+ */
124
+ function dateToDateOnly(date) {
125
+ if (!(date instanceof Date) || Number.isNaN(date.getTime())) {
126
+ return '';
127
+ }
128
+ return [
129
+ date.getFullYear(),
130
+ '-',
131
+ pad2(date.getMonth() + 1),
132
+ '-',
133
+ pad2(date.getDate())
134
+ ].join('');
135
+ }
136
+
137
+ /**
138
+ * @param {unknown} value
139
+ * @returns {Date|null}
140
+ */
141
+ function parseValueToDate(value) {
142
+ if (value == null || value === '') {
143
+ return null;
144
+ }
145
+ const date = new Date(value);
146
+ if (Number.isNaN(date.getTime())) {
147
+ return null;
148
+ }
149
+ return date;
150
+ }
151
+
152
+ /**
153
+ * @param {string} localValue - YYYY-MM-DD or YYYY-MM-DDTHH:mm:ss.SSS
154
+ * @returns {Date|null}
155
+ */
156
+ function localStringToDate(localValue) {
157
+ if (!localValue) {
158
+ return null;
159
+ }
160
+ const date = new Date(localValue);
161
+ if (Number.isNaN(date.getTime())) {
162
+ return null;
163
+ }
164
+ return date;
165
+ }
166
+
167
+ function isSameDay(a, b) {
168
+ if (!a || !b) {
169
+ return false;
170
+ }
171
+ return (
172
+ a.getFullYear() === b.getFullYear()
173
+ && a.getMonth() === b.getMonth()
174
+ && a.getDate() === b.getDate()
175
+ );
176
+ }
177
+
178
+ function isSameMonth(date, year, month) {
179
+ return date.getFullYear() === year && date.getMonth() === month;
180
+ }
181
+
182
+ /**
183
+ * Build a 6-row calendar grid for the given month (local timezone).
184
+ * @param {number} year
185
+ * @param {number} month - 0-indexed
186
+ * @returns {Array<{ key: string, label: number, date: Date, inMonth: boolean }>}
187
+ */
188
+ function buildCalendarDays(year, month) {
189
+ const firstOfMonth = new Date(year, month, 1);
190
+ const startOffset = firstOfMonth.getDay();
191
+ const gridStart = new Date(year, month, 1 - startOffset);
192
+
193
+ const days = [];
194
+ for (let i = 0; i < 42; i++) {
195
+ const date = new Date(gridStart.getFullYear(), gridStart.getMonth(), gridStart.getDate() + i);
196
+ days.push({
197
+ key: `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`,
198
+ label: date.getDate(),
199
+ date,
200
+ inMonth: date.getMonth() === month
201
+ });
202
+ }
203
+ return days;
204
+ }
205
+
206
+ const WEEKDAY_LABELS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
207
+
208
+ const MONTH_LABELS = [
209
+ 'January', 'February', 'March', 'April', 'May', 'June',
210
+ 'July', 'August', 'September', 'October', 'November', 'December'
211
+ ];
212
+
213
+ module.exports = {
214
+ pad2,
215
+ pad3,
216
+ clampInt,
217
+ dateToDatetimeLocal,
218
+ dateToDateOnly,
219
+ parseValueToDate,
220
+ localStringToDate,
221
+ isSameDay,
222
+ isSameMonth,
223
+ buildCalendarDays,
224
+ WEEKDAY_LABELS,
225
+ MONTH_LABELS
226
+ };
227
+
228
+
63
229
  /***/ },
64
230
 
65
231
  /***/ "./frontend/src/_util/dateRange.js"
@@ -228,6 +394,8 @@ module.exports = deepEqual;
228
394
  "use strict";
229
395
 
230
396
 
397
+ const { dateToDatetimeLocal } = __webpack_require__(/*! ./calendar */ "./frontend/src/_util/calendar.js");
398
+
231
399
  const { Trie } = __webpack_require__(/*! ../models/trie */ "./frontend/src/models/trie.js");
232
400
 
233
401
  const QUERY_SELECTORS = [
@@ -277,7 +445,7 @@ function buildAutocompleteTrie(schemaPaths) {
277
445
  trie.bulkInsert(QUERY_SELECTORS, 5, 'operator');
278
446
  trie.bulkInsert(QUERY_SELECTORS, 5, 'fieldName');
279
447
  trie.bulkInsert(VALUE_HELPERS, 5, 'value');
280
-
448
+
281
449
  if (Array.isArray(schemaPaths) && schemaPaths.length > 0) {
282
450
  const paths = schemaPaths
283
451
  .map(path => path?.path)
@@ -289,13 +457,13 @@ function buildAutocompleteTrie(schemaPaths) {
289
457
  }
290
458
  trie.bulkInsert(paths, 10, 'fieldName');
291
459
  }
292
-
460
+
293
461
  return trie;
294
462
  }
295
463
 
296
464
  function getAutocompleteContext(searchText, cursorPos) {
297
465
  const before = searchText.slice(0, cursorPos);
298
-
466
+
299
467
  // Check if we're in a field name context (after { or ,)
300
468
  // This takes precedence over value context to handle cases like { _id: { $gt
301
469
  const fieldMatch = before.match(/(?:\{|,)\s*([^:\s]*)$/);
@@ -307,7 +475,7 @@ function getAutocompleteContext(searchText, cursorPos) {
307
475
  startPos: cursorPos - token.length
308
476
  };
309
477
  }
310
-
478
+
311
479
  // Check if we're in a value context (after a colon)
312
480
  // Match the last colon followed by optional whitespace and capture everything after
313
481
  const valueMatch = before.match(/:\s*(\{?\s*)([^\s,\}\]:]*)$/);
@@ -319,19 +487,19 @@ function getAutocompleteContext(searchText, cursorPos) {
319
487
  startPos: cursorPos - token.length
320
488
  };
321
489
  }
322
-
490
+
323
491
  return null;
324
492
  }
325
493
 
326
494
  function getAutocompleteSuggestions(trie, searchText, cursorPos, schemaPaths) {
327
495
  const context = getAutocompleteContext(searchText, cursorPos);
328
-
496
+
329
497
  if (!context) {
330
498
  return [];
331
499
  }
332
-
500
+
333
501
  const { token, role } = context;
334
-
502
+
335
503
  // Extract the actual term without quotes
336
504
  const leadingQuoteMatch = token.match(/^["']/);
337
505
  const trailingQuoteMatch = token.length > 1 && /["']$/.test(token)
@@ -341,14 +509,14 @@ function getAutocompleteSuggestions(trie, searchText, cursorPos, schemaPaths) {
341
509
  .replace(/^["']/, '')
342
510
  .replace(trailingQuoteMatch ? new RegExp(`[${trailingQuoteMatch}]$`) : '', '')
343
511
  .trim();
344
-
512
+
345
513
  if (!term) {
346
514
  return [];
347
515
  }
348
-
516
+
349
517
  const primarySuggestions = trie.getSuggestions(term, 10, role);
350
518
  const suggestionsSet = new Set(primarySuggestions);
351
-
519
+
352
520
  // Add schema path suggestions for field names
353
521
  if (role === 'fieldName' && Array.isArray(schemaPaths) && schemaPaths.length > 0) {
354
522
  for (const schemaPath of schemaPaths) {
@@ -365,9 +533,9 @@ function getAutocompleteSuggestions(trie, searchText, cursorPos, schemaPaths) {
365
533
  }
366
534
  }
367
535
  }
368
-
536
+
369
537
  let suggestions = Array.from(suggestionsSet);
370
-
538
+
371
539
  // Preserve quotes if present
372
540
  if (leadingQuoteMatch) {
373
541
  const leadingQuote = leadingQuoteMatch[0];
@@ -378,14 +546,14 @@ function getAutocompleteSuggestions(trie, searchText, cursorPos, schemaPaths) {
378
546
  suggestion.endsWith(trailingQuoteMatch) ? suggestion : `${suggestion}${trailingQuoteMatch}`
379
547
  );
380
548
  }
381
-
549
+
382
550
  return suggestions;
383
551
  }
384
552
 
385
553
  function applySuggestion(searchText, cursorPos, suggestion) {
386
554
  const before = searchText.slice(0, cursorPos);
387
555
  const after = searchText.slice(cursorPos);
388
-
556
+
389
557
  // Check if we're in a value context
390
558
  const valueMatch = before.match(/:\s*(\{?\s*)([^\s,\}\]:]*)$/);
391
559
  if (valueMatch) {
@@ -393,30 +561,30 @@ function applySuggestion(searchText, cursorPos, suggestion) {
393
561
  const start = cursorPos - token.length;
394
562
  let replacement = suggestion;
395
563
  let cursorOffset = replacement.length;
396
-
564
+
397
565
  // Add parentheses for function helpers and position cursor inside
398
566
  if (FUNCTION_HELPERS.has(suggestion)) {
399
567
  replacement = `${suggestion}()`;
400
568
  cursorOffset = suggestion.length + 1; // Position cursor between ()
401
569
  }
402
-
570
+
403
571
  return {
404
572
  text: searchText.slice(0, start) + replacement + after,
405
573
  newCursorPos: start + cursorOffset
406
574
  };
407
575
  }
408
-
576
+
409
577
  // Check if we're in a field name context
410
578
  const fieldMatch = before.match(/(?:\{|,)\s*([^:\s]*)$/);
411
579
  if (fieldMatch) {
412
580
  const token = fieldMatch[1];
413
581
  const start = cursorPos - token.length;
414
582
  let replacement = suggestion;
415
-
583
+
416
584
  const leadingQuote = token.startsWith('"') || token.startsWith('\'') ? token[0] : '';
417
585
  const trailingQuote = token.length > 1 && (token.endsWith('"') || token.endsWith('\'')) ? token[token.length - 1] : '';
418
586
  const colonNeeded = !/^\s*:/.test(after);
419
-
587
+
420
588
  // If suggestion already has quotes, use it as-is
421
589
  const suggestionHasQuotes = (suggestion.startsWith('"') || suggestion.startsWith('\'')) &&
422
590
  (suggestion.endsWith('"') || suggestion.endsWith('\''));
@@ -430,27 +598,107 @@ function applySuggestion(searchText, cursorPos, suggestion) {
430
598
  replacement = `${replacement}${trailingQuote}`;
431
599
  }
432
600
  }
433
-
601
+
434
602
  // Only insert : if we know the user isn't entering in a nested path
435
603
  // If suggestion has full quotes or user typed both quotes, add colon
436
604
  if (colonNeeded && (suggestionHasQuotes || !leadingQuote || trailingQuote)) {
437
605
  replacement = `${replacement}:`;
438
606
  }
439
-
607
+
440
608
  return {
441
609
  text: searchText.slice(0, start) + replacement + after,
442
610
  newCursorPos: start + replacement.length
443
611
  };
444
612
  }
445
-
613
+
446
614
  return null;
447
615
  }
448
616
 
617
+ /**
618
+ * When the cursor is inside a Date(…) or new Date(…) argument, returns the
619
+ * slice indices of that argument so a picker can replace it with a quoted ISO string.
620
+ */
621
+ function getDatePickerInsertionRange(searchText, cursorPos) {
622
+ const before = searchText.slice(0, cursorPos);
623
+ const re = /((?:new\s+)?Date\s*\(\s*)([^)]*)$/i;
624
+ const m = before.match(re);
625
+ if (!m) {
626
+ return null;
627
+ }
628
+ const innerStart = m.index + m[1].length;
629
+ const after = searchText.slice(cursorPos);
630
+ let closeIdx = -1;
631
+ let parenDepth = 0;
632
+ for (let k = 0; k < after.length; k++) {
633
+ const ch = after[k];
634
+ if (ch === '(') {
635
+ parenDepth++;
636
+ } else if (ch === ')') {
637
+ if (parenDepth === 0) {
638
+ closeIdx = cursorPos + k;
639
+ break;
640
+ }
641
+ parenDepth--;
642
+ }
643
+ }
644
+ const innerEnd = closeIdx >= 0 ? closeIdx : cursorPos;
645
+ const needsClosingParen = closeIdx < 0;
646
+ return { innerStart, innerEnd, needsClosingParen };
647
+ }
648
+
649
+ function dateArgumentSliceToDatetimeLocal(slice) {
650
+ const t = slice.trim();
651
+ if (!t) {
652
+ return '';
653
+ }
654
+ const unquoted = (t.startsWith('"') && t.endsWith('"')) || (t.startsWith('\'') && t.endsWith('\''))
655
+ ? t.slice(1, -1)
656
+ : t;
657
+ const d = new Date(unquoted);
658
+ if (Number.isNaN(d.getTime())) {
659
+ return '';
660
+ }
661
+ return dateToDatetimeLocal(d);
662
+ }
663
+
664
+ /**
665
+ * @param {Date} date
666
+ * @param {'quoted'|'timestamp'} format
667
+ */
668
+ function formatDateArgumentValue(date, format) {
669
+ if (format === 'quoted') {
670
+ return JSON.stringify(date.toISOString());
671
+ }
672
+ return String(date.getTime());
673
+ }
674
+
675
+ /**
676
+ * Inserts a Date argument value as timestamp or quoted ISO string.
677
+ * @param {string} searchText
678
+ * @param {{ innerStart: number, innerEnd: number, needsClosingParen?: boolean }} range
679
+ * @param {Date} date
680
+ * @param {'quoted'|'timestamp'} [format] - when omitted, inferred from the existing argument
681
+ */
682
+ function insertDateInDateArgument(searchText, range, date, format) {
683
+ const slice = searchText.slice(range.innerStart, range.innerEnd);
684
+ const insertion = formatDateArgumentValue(date, format);
685
+ const { innerStart, innerEnd } = range;
686
+ const closing = range.needsClosingParen === true ? ')' : '';
687
+ return {
688
+ text: searchText.slice(0, innerStart) + insertion + closing + searchText.slice(innerEnd),
689
+ newCursorPos: innerStart + insertion.length + closing.length
690
+ };
691
+ }
692
+
449
693
  module.exports = {
450
694
  buildAutocompleteTrie,
451
695
  getAutocompleteContext,
452
696
  getAutocompleteSuggestions,
453
697
  applySuggestion,
698
+ getDatePickerInsertionRange,
699
+ dateArgumentSliceToDatetimeLocal,
700
+ formatDateArgumentValue,
701
+ insertDateInDateArgument,
454
702
  QUERY_SELECTORS,
455
703
  VALUE_HELPERS,
456
704
  FUNCTION_HELPERS
@@ -2464,6 +2712,424 @@ module.exports = app => app.component('create-document', {
2464
2712
  });
2465
2713
 
2466
2714
 
2715
+ /***/ },
2716
+
2717
+ /***/ "./frontend/src/custom-date-picker/custom-date-picker.js"
2718
+ /*!***************************************************************!*\
2719
+ !*** ./frontend/src/custom-date-picker/custom-date-picker.js ***!
2720
+ \***************************************************************/
2721
+ (module, __unused_webpack_exports, __webpack_require__) {
2722
+
2723
+ "use strict";
2724
+
2725
+
2726
+ const template = __webpack_require__(/*! ./custom-date-picker.html */ "./frontend/src/custom-date-picker/custom-date-picker.html");
2727
+ const {
2728
+ dateToDatetimeLocal,
2729
+ dateToDateOnly,
2730
+ parseValueToDate,
2731
+ buildCalendarDays,
2732
+ clampInt,
2733
+ isSameDay,
2734
+ pad2,
2735
+ WEEKDAY_LABELS,
2736
+ MONTH_LABELS
2737
+ } = __webpack_require__(/*! ../_util/calendar */ "./frontend/src/_util/calendar.js");
2738
+
2739
+ const WHEEL_PAD = 2;
2740
+ const HOUR_OPTIONS = Array.from({ length: 24 }, (_, i) => i);
2741
+ const MINUTE_OPTIONS = Array.from({ length: 60 }, (_, i) => i);
2742
+ const SECOND_OPTIONS = Array.from({ length: 60 }, (_, i) => i);
2743
+
2744
+ function timePartsFromDate(date) {
2745
+ if (!date) {
2746
+ return { hour: 0, minute: 0, second: 0, millisecond: 0 };
2747
+ }
2748
+ return {
2749
+ hour: date.getHours(),
2750
+ minute: date.getMinutes(),
2751
+ second: date.getSeconds(),
2752
+ millisecond: date.getMilliseconds()
2753
+ };
2754
+ }
2755
+
2756
+ module.exports = app => app.component('custom-date-picker', {
2757
+ template,
2758
+ props: {
2759
+ value: {
2760
+ default: null
2761
+ },
2762
+ mode: {
2763
+ type: String,
2764
+ default: 'datetime' // 'date' | 'datetime'
2765
+ },
2766
+ compact: {
2767
+ type: Boolean,
2768
+ default: false
2769
+ },
2770
+ mini: {
2771
+ type: Boolean,
2772
+ default: false
2773
+ },
2774
+ immediate: {
2775
+ type: Boolean,
2776
+ default: true
2777
+ },
2778
+ horizontal: {
2779
+ type: Boolean,
2780
+ default: false
2781
+ },
2782
+ embedded: {
2783
+ type: Boolean,
2784
+ default: false
2785
+ }
2786
+ },
2787
+ emits: ['input', 'commit'],
2788
+ data() {
2789
+ const selected = parseValueToDate(this.value);
2790
+ const anchor = selected || new Date();
2791
+ const time = timePartsFromDate(anchor);
2792
+ return {
2793
+ viewYear: anchor.getFullYear(),
2794
+ viewMonth: anchor.getMonth(),
2795
+ selectedDate: anchor,
2796
+ hour: time.hour,
2797
+ minute: time.minute,
2798
+ second: time.second,
2799
+ millisecond: time.millisecond,
2800
+ pendingValue: null,
2801
+ wheelScrollLock: false,
2802
+ timeApplyTimer: null,
2803
+ hourOptions: HOUR_OPTIONS,
2804
+ minuteOptions: MINUTE_OPTIONS,
2805
+ secondOptions: SECOND_OPTIONS
2806
+ };
2807
+ },
2808
+ watch: {
2809
+ value() {
2810
+ this.syncFromValue();
2811
+ },
2812
+ horizontal() {
2813
+ this.$nextTick(() => this.scrollWheelsToValues());
2814
+ }
2815
+ },
2816
+ computed: {
2817
+ rootClasses() {
2818
+ const sizeClass = this.mini
2819
+ ? 'p-0.5 text-[10px]'
2820
+ : (this.compact ? 'p-2 text-xs' : 'p-3 text-sm');
2821
+ const chromeClass = this.embedded
2822
+ ? ''
2823
+ : (this.mini ? '' : 'bg-surface border border-edge-strong rounded-md shadow-sm');
2824
+ return [sizeClass, chromeClass];
2825
+ },
2826
+ timeSectionClasses() {
2827
+ if (this.horizontal) {
2828
+ return 'flex-1 min-w-0 border-l border-edge-strong pl-2';
2829
+ }
2830
+ return [
2831
+ 'border-t border-edge-strong',
2832
+ this.mini ? 'mt-1 pt-1' : 'mt-3 pt-3'
2833
+ ];
2834
+ },
2835
+ wheelPadCount() {
2836
+ return 2;
2837
+ },
2838
+ wheelVisibleRows() {
2839
+ return 5;
2840
+ },
2841
+ weekdayLabels() {
2842
+ return WEEKDAY_LABELS;
2843
+ },
2844
+ monthLabel() {
2845
+ return `${MONTH_LABELS[this.viewMonth]} ${this.viewYear}`;
2846
+ },
2847
+ calendarDays() {
2848
+ return buildCalendarDays(this.viewYear, this.viewMonth);
2849
+ },
2850
+ today() {
2851
+ const now = new Date();
2852
+ return new Date(now.getFullYear(), now.getMonth(), now.getDate());
2853
+ },
2854
+ showTime() {
2855
+ return this.mode === 'datetime';
2856
+ },
2857
+ dayCellClass() {
2858
+ if (this.mini) {
2859
+ return 'h-6';
2860
+ }
2861
+ if (this.compact) {
2862
+ return 'h-7';
2863
+ }
2864
+ return 'aspect-square';
2865
+ },
2866
+ calendarSectionClass() {
2867
+ if (this.mini) return 'w-36';
2868
+ if (this.compact) return 'w-44';
2869
+ return 'w-48';
2870
+ },
2871
+ wheelItemPx() {
2872
+ if (this.mini) {
2873
+ return 32;
2874
+ }
2875
+ if (this.compact) {
2876
+ return 36;
2877
+ }
2878
+ return 40;
2879
+ },
2880
+ wheelListClass() {
2881
+ return [
2882
+ 'overflow-y-auto overscroll-y-contain snap-y snap-mandatory',
2883
+ '[-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden'
2884
+ ];
2885
+ },
2886
+ wheelListStyle() {
2887
+ return {
2888
+ height: `${this.wheelItemPx * this.wheelVisibleRows}px`
2889
+ };
2890
+ },
2891
+ wheelItemClass() {
2892
+ return [
2893
+ 'snap-center shrink-0 w-full flex items-center justify-center font-mono transition-colors',
2894
+ this.mini ? 'h-8 text-sm' : (this.compact ? 'h-9 text-sm' : 'h-10 text-base')
2895
+ ];
2896
+ },
2897
+ wheelHighlightClass() {
2898
+ return this.mini ? 'h-8' : (this.compact ? 'h-9' : 'h-10');
2899
+ },
2900
+ wheelColumnClass() {
2901
+ return this.mini ? 'w-11' : (this.compact ? 'w-12' : 'w-14');
2902
+ }
2903
+ },
2904
+ mounted() {
2905
+ this.$nextTick(() => this.scrollWheelsToValues());
2906
+ },
2907
+ beforeUnmount() {
2908
+ if (this.timeApplyTimer) {
2909
+ clearTimeout(this.timeApplyTimer);
2910
+ }
2911
+ },
2912
+ methods: {
2913
+ syncFromValue() {
2914
+ const selected = parseValueToDate(this.value);
2915
+ const anchor = selected || new Date();
2916
+ this.selectedDate = anchor;
2917
+ this.viewYear = anchor.getFullYear();
2918
+ this.viewMonth = anchor.getMonth();
2919
+ const time = timePartsFromDate(anchor);
2920
+ this.hour = time.hour;
2921
+ this.minute = time.minute;
2922
+ this.second = time.second;
2923
+ this.millisecond = time.millisecond;
2924
+ this.$nextTick(() => this.scrollWheelsToValues());
2925
+ },
2926
+ clampedTimeParts() {
2927
+ return {
2928
+ hour: clampInt(this.hour, 0, 23),
2929
+ minute: clampInt(this.minute, 0, 59),
2930
+ second: clampInt(this.second, 0, 59),
2931
+ millisecond: clampInt(this.millisecond, 0, 999)
2932
+ };
2933
+ },
2934
+ formatWheelValue(value) {
2935
+ return pad2(value);
2936
+ },
2937
+ wheelItemClasses(value, selectedValue) {
2938
+ return value === selectedValue
2939
+ ? 'text-content-primary font-semibold'
2940
+ : 'text-content-tertiary hover:text-content-secondary';
2941
+ },
2942
+ wheelIndexFromScroll(el, max) {
2943
+ return clampInt(Math.round(el.scrollTop / this.wheelItemPx), 0, max);
2944
+ },
2945
+ scrollWheelTo(el, index) {
2946
+ if (!el) {
2947
+ return;
2948
+ }
2949
+ el.scrollTop = index * this.wheelItemPx;
2950
+ },
2951
+ scrollWheelsToValues() {
2952
+ this.wheelScrollLock = true;
2953
+ this.scrollWheelTo(this.$refs.hourWheel, this.hour);
2954
+ this.scrollWheelTo(this.$refs.minuteWheel, this.minute);
2955
+ this.scrollWheelTo(this.$refs.secondWheel, this.second);
2956
+ requestAnimationFrame(() => {
2957
+ this.wheelScrollLock = false;
2958
+ });
2959
+ },
2960
+ scheduleTimeApply() {
2961
+ if (this.timeApplyTimer) {
2962
+ clearTimeout(this.timeApplyTimer);
2963
+ }
2964
+ this.timeApplyTimer = setTimeout(() => {
2965
+ this.timeApplyTimer = null;
2966
+ this.applyTimeToSelection();
2967
+ }, 120);
2968
+ },
2969
+ onHourWheelScroll(event) {
2970
+ if (this.wheelScrollLock) {
2971
+ return;
2972
+ }
2973
+ const hour = this.wheelIndexFromScroll(event.target, 23);
2974
+ if (hour === this.hour) {
2975
+ return;
2976
+ }
2977
+ this.hour = hour;
2978
+ this.scheduleTimeApply();
2979
+ },
2980
+ onMinuteWheelScroll(event) {
2981
+ if (this.wheelScrollLock) {
2982
+ return;
2983
+ }
2984
+ const minute = this.wheelIndexFromScroll(event.target, 59);
2985
+ if (minute === this.minute) {
2986
+ return;
2987
+ }
2988
+ this.minute = minute;
2989
+ this.scheduleTimeApply();
2990
+ },
2991
+ onSecondWheelScroll(event) {
2992
+ if (this.wheelScrollLock) {
2993
+ return;
2994
+ }
2995
+ const second = this.wheelIndexFromScroll(event.target, 59);
2996
+ if (second === this.second) {
2997
+ return;
2998
+ }
2999
+ this.second = second;
3000
+ this.scheduleTimeApply();
3001
+ },
3002
+ selectHour(hour) {
3003
+ this.hour = hour;
3004
+ this.scrollWheelTo(this.$refs.hourWheel, hour);
3005
+ this.applyTimeToSelection();
3006
+ },
3007
+ selectMinute(minute) {
3008
+ this.minute = minute;
3009
+ this.scrollWheelTo(this.$refs.minuteWheel, minute);
3010
+ this.applyTimeToSelection();
3011
+ },
3012
+ selectSecond(second) {
3013
+ this.second = second;
3014
+ this.scrollWheelTo(this.$refs.secondWheel, second);
3015
+ this.applyTimeToSelection();
3016
+ },
3017
+ dayClasses(day) {
3018
+ const selected = this.selectedDate;
3019
+ const isSelected = selected && isSameDay(day.date, selected);
3020
+ const isToday = isSameDay(day.date, this.today);
3021
+ return {
3022
+ 'text-content-tertiary': !day.inMonth,
3023
+ 'bg-merlot-700 text-white hover:bg-merlot-800': isSelected,
3024
+ 'hover:bg-surface-hover': day.inMonth && !isSelected,
3025
+ 'ring-1 ring-merlot-700 ring-inset': isToday && !isSelected && day.inMonth,
3026
+ 'font-medium': day.inMonth
3027
+ };
3028
+ },
3029
+ prevMonth() {
3030
+ if (this.viewMonth === 0) {
3031
+ this.viewMonth = 11;
3032
+ this.viewYear -= 1;
3033
+ } else {
3034
+ this.viewMonth -= 1;
3035
+ }
3036
+ },
3037
+ nextMonth() {
3038
+ if (this.viewMonth === 11) {
3039
+ this.viewMonth = 0;
3040
+ this.viewYear += 1;
3041
+ } else {
3042
+ this.viewMonth += 1;
3043
+ }
3044
+ },
3045
+ goToToday() {
3046
+ const now = new Date();
3047
+ this.viewYear = now.getFullYear();
3048
+ this.viewMonth = now.getMonth();
3049
+ this.selectDay({
3050
+ date: new Date(now.getFullYear(), now.getMonth(), now.getDate()),
3051
+ inMonth: true
3052
+ });
3053
+ },
3054
+ selectDay(day) {
3055
+ const { hour, minute, second, millisecond } = this.clampedTimeParts();
3056
+ const next = new Date(day.date);
3057
+ if (this.showTime) {
3058
+ next.setHours(hour, minute, second, millisecond);
3059
+ } else {
3060
+ next.setHours(0, 0, 0, 0);
3061
+ }
3062
+ this.selectedDate = next;
3063
+ if (!day.inMonth) {
3064
+ this.viewYear = next.getFullYear();
3065
+ this.viewMonth = next.getMonth();
3066
+ }
3067
+ this.emitValue(next);
3068
+ },
3069
+ applyTimeToSelection() {
3070
+ const { hour, minute, second, millisecond } = this.clampedTimeParts();
3071
+ this.hour = hour;
3072
+ this.minute = minute;
3073
+ this.second = second;
3074
+ this.millisecond = millisecond;
3075
+
3076
+ if (!this.selectedDate) {
3077
+ const now = new Date();
3078
+ this.selectedDate = new Date(
3079
+ now.getFullYear(),
3080
+ now.getMonth(),
3081
+ now.getDate(),
3082
+ hour,
3083
+ minute,
3084
+ second,
3085
+ millisecond
3086
+ );
3087
+ this.viewYear = this.selectedDate.getFullYear();
3088
+ this.viewMonth = this.selectedDate.getMonth();
3089
+ } else {
3090
+ this.selectedDate = new Date(
3091
+ this.selectedDate.getFullYear(),
3092
+ this.selectedDate.getMonth(),
3093
+ this.selectedDate.getDate(),
3094
+ hour,
3095
+ minute,
3096
+ second,
3097
+ millisecond
3098
+ );
3099
+ }
3100
+ this.emitValue(this.selectedDate);
3101
+ },
3102
+ valuePayload(date) {
3103
+ if (this.mode === 'date') {
3104
+ return dateToDateOnly(date);
3105
+ }
3106
+ return dateToDatetimeLocal(date);
3107
+ },
3108
+ emitValue(date) {
3109
+ const payload = this.valuePayload(date);
3110
+ if (!this.immediate) {
3111
+ this.pendingValue = payload;
3112
+ return;
3113
+ }
3114
+ this.$emit('input', payload);
3115
+ },
3116
+ commit() {
3117
+ const payload = this.pendingValue != null
3118
+ ? this.pendingValue
3119
+ : (this.selectedDate ? this.valuePayload(this.selectedDate) : null);
3120
+ if (payload == null) {
3121
+ return;
3122
+ }
3123
+ this.$emit('commit', payload);
3124
+ this.pendingValue = null;
3125
+ },
3126
+ isSelectedDay(day) {
3127
+ return this.selectedDate && isSameDay(day.date, this.selectedDate);
3128
+ }
3129
+ }
3130
+ });
3131
+
3132
+
2467
3133
  /***/ },
2468
3134
 
2469
3135
  /***/ "./frontend/src/dashboard-result/dashboard-chart/dashboard-chart.js"
@@ -4880,7 +5546,7 @@ const template = __webpack_require__(/*! ./date-view-mode-picker.html */ "./fron
4880
5546
 
4881
5547
  module.exports = app => app.component('date-view-mode-picker', {
4882
5548
  template,
4883
- props: ['viewMode', 'path'],
5549
+ props: ['viewMode', 'path', 'editting'],
4884
5550
  emits: ['update:viewMode'],
4885
5551
  computed: {
4886
5552
  format() {
@@ -4937,20 +5603,12 @@ appendCSS(__webpack_require__(/*! ./document-details.css */ "./frontend/src/docu
4937
5603
 
4938
5604
  module.exports = app => app.component('document-details', {
4939
5605
  template,
4940
- props: ['document', 'schemaPaths', 'virtualPaths', 'editting', 'changes', 'invalid', 'viewMode', 'model'],
5606
+ props: ['document', 'schemaPaths', 'virtualPaths', 'editting', 'changes', 'invalid', 'viewMode'],
4941
5607
  data() {
4942
5608
  return {
4943
5609
  searchQuery: '',
4944
5610
  selectedType: '',
4945
- collapsedVirtuals: new Set(),
4946
- showAddFieldModal: false,
4947
- fieldData: {
4948
- name: '',
4949
- type: '',
4950
- value: ''
4951
- },
4952
- fieldErrors: {},
4953
- isSubmittingField: false
5611
+ collapsedVirtuals: new Set()
4954
5612
  };
4955
5613
  },
4956
5614
  mounted() {
@@ -5020,20 +5678,6 @@ module.exports = app => app.component('document-details', {
5020
5678
 
5021
5679
  return Array.from(types).sort();
5022
5680
  },
5023
- allFieldTypes() {
5024
- const schemaTypes = this.availableTypes;
5025
- const commonTypes = ['String', 'Number', 'Boolean', 'Date', 'Array', 'Object'];
5026
-
5027
- // Combine schema types with common types, avoiding duplicates
5028
- const allTypes = new Set([...schemaTypes, ...commonTypes]);
5029
- return Array.from(allTypes).sort();
5030
- },
5031
- shouldUseAce() {
5032
- return ['Array', 'Object', 'Embedded'].includes(this.fieldData.type);
5033
- },
5034
- shouldUseDatePicker() {
5035
- return this.fieldData.type === 'Date';
5036
- },
5037
5681
  typeFilteredSchemaPaths() {
5038
5682
  let paths = this.schemaPaths || [];
5039
5683
 
@@ -5245,120 +5889,6 @@ module.exports = app => app.component('document-details', {
5245
5889
  isVirtualFieldCollapsed(fieldName) {
5246
5890
  return this.collapsedVirtuals.has(fieldName);
5247
5891
  },
5248
- openAddFieldModal() {
5249
- this.showAddFieldModal = true;
5250
- },
5251
- closeAddFieldModal() {
5252
- this.showAddFieldModal = false;
5253
- this.resetFieldForm();
5254
- },
5255
- async addNewField(fieldData) {
5256
- // Emit event to parent component to handle the field addition
5257
- this.$emit('add-field', fieldData);
5258
- this.closeAddFieldModal();
5259
- },
5260
- validateFieldForm() {
5261
- this.fieldErrors = {};
5262
-
5263
- // Validate field name
5264
- const trimmedName = this.fieldData.name.trim();
5265
- if (!trimmedName) {
5266
- this.fieldErrors.name = 'Field name is required';
5267
- } else {
5268
- const transformedName = this.getTransformedFieldName();
5269
- if (!transformedName) {
5270
- this.fieldErrors.name = 'Field name contains only invalid characters';
5271
- } else if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(transformedName)) {
5272
- this.fieldErrors.name = 'Field name must start with a letter, underscore, or $ and contain only letters, numbers, underscores, and $';
5273
- }
5274
- }
5275
-
5276
- // Validate field type
5277
- if (!this.fieldData.type) {
5278
- this.fieldErrors.type = 'Field type is required';
5279
- }
5280
-
5281
- // Validate field value if provided
5282
- if (this.fieldData.value && this.fieldData.value.trim()) {
5283
- if (['Object', 'Array'].includes(this.fieldData.type)) {
5284
- try {
5285
- JSON.parse(this.fieldData.value);
5286
- } catch (e) {
5287
- this.fieldErrors.value = 'Invalid JSON format for object/array type';
5288
- }
5289
- } else if (this.fieldData.type === 'Number') {
5290
- if (isNaN(Number(this.fieldData.value))) {
5291
- this.fieldErrors.value = 'Invalid number format';
5292
- }
5293
- } else if (this.fieldData.type === 'Boolean') {
5294
- const lowerValue = this.fieldData.value.toLowerCase();
5295
- if (!['true', 'false', '1', '0', 'yes', 'no'].includes(lowerValue)) {
5296
- this.fieldErrors.value = 'Invalid boolean value (use true/false, 1/0, yes/no)';
5297
- }
5298
- } else if (this.fieldData.type === 'Date') {
5299
- // Date picker provides YYYY-MM-DD format, validate it
5300
- const dateValue = new Date(this.fieldData.value);
5301
- if (isNaN(dateValue.getTime())) {
5302
- this.fieldErrors.value = 'Invalid date format';
5303
- }
5304
- }
5305
- }
5306
-
5307
- return Object.keys(this.fieldErrors).length === 0;
5308
- },
5309
- parseFieldValue(value, type) {
5310
- if (!value || !value.trim()) {
5311
- return null;
5312
- }
5313
-
5314
- switch (type) {
5315
- case 'Number':
5316
- return Number(value);
5317
- case 'Boolean':
5318
- const lowerValue = value.toLowerCase();
5319
- return ['true', '1', 'yes'].includes(lowerValue);
5320
- case 'Date':
5321
- // For date picker, value is already in YYYY-MM-DD format
5322
- return new Date(value);
5323
- case 'Object':
5324
- case 'Array':
5325
- return JSON.parse(value);
5326
- default:
5327
- return value;
5328
- }
5329
- },
5330
- async handleAddFieldSubmit() {
5331
- if (!this.validateFieldForm()) {
5332
- return;
5333
- }
5334
-
5335
- this.isSubmittingField = true;
5336
-
5337
- try {
5338
- const fieldData = {
5339
- name: this.fieldData.name,
5340
- type: this.fieldData.type,
5341
- value: this.parseFieldValue(this.fieldData.value, this.fieldData.type)
5342
- };
5343
-
5344
- this.$emit('add-field', fieldData);
5345
- this.closeAddFieldModal();
5346
- } catch (error) {
5347
- console.error('Error adding field:', error);
5348
- this.fieldErrors.value = 'Error processing field value';
5349
- } finally {
5350
- this.isSubmittingField = false;
5351
- }
5352
- },
5353
- resetFieldForm() {
5354
- this.fieldData = {
5355
- name: '',
5356
- type: '',
5357
- value: ''
5358
- };
5359
- this.fieldErrors = {};
5360
- this.isSubmittingField = false;
5361
- },
5362
5892
  getVirtualFieldType(virtual) {
5363
5893
  const value = virtual.value;
5364
5894
  if (value === null || value === undefined) {
@@ -5941,7 +6471,15 @@ module.exports = app => app.component('document', {
5941
6471
  shouldShowDeleteModal: false,
5942
6472
  shouldShowCloneModal: false,
5943
6473
  shouldShowValidationModal: false,
6474
+ showAddFieldModal: false,
5944
6475
  validationResult: null,
6476
+ fieldData: {
6477
+ name: '',
6478
+ type: '',
6479
+ value: ''
6480
+ },
6481
+ fieldErrors: {},
6482
+ isSubmittingField: false,
5945
6483
  previousQuery: null,
5946
6484
  lastUpdatedAt: null,
5947
6485
  isRefreshing: false,
@@ -6003,6 +6541,33 @@ module.exports = app => app.component('document', {
6003
6541
  }
6004
6542
  return shortcuts;
6005
6543
  },
6544
+ allFieldTypes() {
6545
+ const types = new Set(['String', 'Number', 'Boolean', 'Date', 'Array', 'Object']);
6546
+
6547
+ (this.schemaPaths || []).forEach(path => {
6548
+ if (path.instance) {
6549
+ types.add(path.instance);
6550
+ }
6551
+ });
6552
+
6553
+ Object.keys(this.document || {}).forEach(key => {
6554
+ if ((this.schemaPaths || []).some(path => path.path === key)) {
6555
+ return;
6556
+ }
6557
+ const fieldType = this.getFieldType(this.document[key]);
6558
+ if (fieldType && fieldType !== 'unknown' && fieldType !== 'null') {
6559
+ types.add(fieldType);
6560
+ }
6561
+ });
6562
+
6563
+ return Array.from(types).sort();
6564
+ },
6565
+ shouldUseAce() {
6566
+ return ['Array', 'Object', 'Embedded'].includes(this.fieldData.type);
6567
+ },
6568
+ shouldUseDatePicker() {
6569
+ return this.fieldData.type === 'Date';
6570
+ },
6006
6571
  isLambda() {
6007
6572
  return !!window?.MONGOOSE_STUDIO_CONFIG?.isLambda;
6008
6573
  }
@@ -6199,16 +6764,143 @@ module.exports = app => app.component('document', {
6199
6764
  showClonedDocument(doc) {
6200
6765
  this.$router.push({ path: `/model/${this.model}/document/${doc._id}` });
6201
6766
  },
6202
- async addField(fieldData) {
6203
- const { doc } = await api.Model.addField({
6204
- model: this.model,
6205
- _id: this.document._id,
6206
- fieldName: fieldData.name,
6207
- fieldValue: fieldData.value
6208
- });
6209
- this.document = doc;
6767
+ addField() {
6768
+ this.showAddFieldModal = true;
6769
+ },
6770
+ closeAddFieldModal() {
6771
+ this.showAddFieldModal = false;
6772
+ this.resetFieldForm();
6773
+ },
6774
+ resetFieldForm() {
6775
+ this.fieldData = {
6776
+ name: '',
6777
+ type: '',
6778
+ value: ''
6779
+ };
6780
+ this.fieldErrors = {};
6781
+ this.isSubmittingField = false;
6782
+ },
6783
+ getTransformedFieldName() {
6784
+ const trimmedName = this.fieldData.name.trim();
6785
+ return trimmedName
6786
+ .replace(/[^a-zA-Z0-9_$]/g, '_')
6787
+ .replace(/^[^a-zA-Z_$]+/, '');
6788
+ },
6789
+ validateFieldForm() {
6790
+ this.fieldErrors = {};
6210
6791
 
6211
- this.$toast.success(`Field added! Field "${fieldData.name}" has been added to the document`);
6792
+ const trimmedName = this.fieldData.name.trim();
6793
+ if (!trimmedName) {
6794
+ this.fieldErrors.name = 'Field name is required';
6795
+ } else {
6796
+ const transformedName = this.getTransformedFieldName();
6797
+ if (!transformedName) {
6798
+ this.fieldErrors.name = 'Field name contains only invalid characters';
6799
+ } else if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(transformedName)) {
6800
+ this.fieldErrors.name = 'Field name must start with a letter, underscore, or $ and contain only letters, numbers, underscores, and $';
6801
+ }
6802
+ }
6803
+
6804
+ if (!this.fieldData.type) {
6805
+ this.fieldErrors.type = 'Field type is required';
6806
+ }
6807
+
6808
+ if (this.fieldData.value && this.fieldData.value.trim()) {
6809
+ if (['Object', 'Array'].includes(this.fieldData.type)) {
6810
+ try {
6811
+ JSON.parse(this.fieldData.value);
6812
+ } catch (err) {
6813
+ this.fieldErrors.value = 'Invalid JSON format for object/array type';
6814
+ }
6815
+ } else if (this.fieldData.type === 'Number') {
6816
+ if (isNaN(Number(this.fieldData.value))) {
6817
+ this.fieldErrors.value = 'Invalid number format';
6818
+ }
6819
+ } else if (this.fieldData.type === 'Boolean') {
6820
+ const lowerValue = this.fieldData.value.toLowerCase();
6821
+ if (!['true', 'false', '1', '0', 'yes', 'no'].includes(lowerValue)) {
6822
+ this.fieldErrors.value = 'Invalid boolean value (use true/false, 1/0, yes/no)';
6823
+ }
6824
+ } else if (this.fieldData.type === 'Date') {
6825
+ const dateValue = new Date(this.fieldData.value);
6826
+ if (isNaN(dateValue.getTime())) {
6827
+ this.fieldErrors.value = 'Invalid date format';
6828
+ }
6829
+ }
6830
+ }
6831
+
6832
+ return Object.keys(this.fieldErrors).length === 0;
6833
+ },
6834
+ parseFieldValue(value, type) {
6835
+ if (!value || !value.trim()) {
6836
+ return null;
6837
+ }
6838
+
6839
+ switch (type) {
6840
+ case 'Number':
6841
+ return Number(value);
6842
+ case 'Boolean':
6843
+ return ['true', '1', 'yes'].includes(value.toLowerCase());
6844
+ case 'Date':
6845
+ return new Date(value);
6846
+ case 'Object':
6847
+ case 'Array':
6848
+ return JSON.parse(value);
6849
+ default:
6850
+ return value;
6851
+ }
6852
+ },
6853
+ async handleAddFieldSubmit() {
6854
+ if (!this.validateFieldForm()) {
6855
+ return;
6856
+ }
6857
+
6858
+ this.isSubmittingField = true;
6859
+
6860
+ try {
6861
+ const fieldName = this.getTransformedFieldName();
6862
+ const fieldValue = this.parseFieldValue(this.fieldData.value, this.fieldData.type);
6863
+
6864
+ const { doc } = await api.Model.addField({
6865
+ model: this.model,
6866
+ _id: this.document._id,
6867
+ fieldName,
6868
+ fieldValue
6869
+ });
6870
+ this.document = doc;
6871
+ this.closeAddFieldModal();
6872
+
6873
+ this.$toast.success(`Field added! Field "${fieldName}" has been added to the document`);
6874
+ } catch (error) {
6875
+ console.error('Error adding field:', error);
6876
+ this.fieldErrors.value = 'Error adding field';
6877
+ } finally {
6878
+ this.isSubmittingField = false;
6879
+ }
6880
+ },
6881
+ getFieldType(value) {
6882
+ if (value === null || value === undefined) {
6883
+ return 'null';
6884
+ }
6885
+ if (Array.isArray(value)) {
6886
+ return 'Array';
6887
+ }
6888
+ if (value instanceof Date) {
6889
+ return 'Date';
6890
+ }
6891
+ if (typeof value === 'object') {
6892
+ return 'Object';
6893
+ }
6894
+ if (typeof value === 'number') {
6895
+ return 'Number';
6896
+ }
6897
+ if (typeof value === 'boolean') {
6898
+ return 'Boolean';
6899
+ }
6900
+ if (typeof value === 'string') {
6901
+ return 'String';
6902
+ }
6903
+ return 'unknown';
6212
6904
  },
6213
6905
  updateViewMode(mode) {
6214
6906
  this.viewMode = mode;
@@ -6639,9 +7331,6 @@ module.exports = app => app.component('edit-date', {
6639
7331
  template: template,
6640
7332
  props: ['value', 'format'],
6641
7333
  emits: ['input'],
6642
- data: () => ({
6643
- inputType: ''
6644
- }),
6645
7334
  methods: {
6646
7335
  updateFromISO($event) {
6647
7336
  const value = $event.target.value;
@@ -6661,23 +7350,6 @@ module.exports = app => app.component('edit-date', {
6661
7350
  }
6662
7351
  },
6663
7352
  computed: {
6664
- valueAsLocalString() {
6665
- if (this.value == null) {
6666
- return this.value;
6667
- }
6668
- const date = new Date(this.value);
6669
- return [
6670
- date.getFullYear(),
6671
- '-',
6672
- (date.getMonth() + 1).toString().padStart(2, '0'),
6673
- '-',
6674
- date.getDate().toString().padStart(2, '0'),
6675
- 'T',
6676
- date.getHours().toString().padStart(2, '0'),
6677
- ':',
6678
- date.getMinutes().toString().padStart(2, '0')
6679
- ].join('');
6680
- },
6681
7353
  valueAsISOString() {
6682
7354
  if (this.value == null) {
6683
7355
  return '' + this.value;
@@ -7256,7 +7928,7 @@ app.component('app-component', {
7256
7928
  <div v-else-if="!hasAPIKey || user">
7257
7929
  <navbar :user="user" :roles="roles" />
7258
7930
  <div class="view">
7259
- <router-view :key="$route.fullPath" :user="user" :roles="roles" :hasAPIKey="hasAPIKey" />
7931
+ <router-view :key="$route.meta.preserveStateKey || $route.fullPath" :user="user" :roles="roles" :hasAPIKey="hasAPIKey" />
7260
7932
  </div>
7261
7933
  </div>
7262
7934
  </div>
@@ -8350,7 +9022,10 @@ const template = __webpack_require__(/*! ./document-search.html */ "./frontend/s
8350
9022
  const {
8351
9023
  buildAutocompleteTrie,
8352
9024
  getAutocompleteSuggestions,
8353
- applySuggestion
9025
+ applySuggestion,
9026
+ getDatePickerInsertionRange,
9027
+ dateArgumentSliceToDatetimeLocal,
9028
+ insertDateInDateArgument
8354
9029
  } = __webpack_require__(/*! ../../_util/document-search-autocomplete */ "./frontend/src/_util/document-search-autocomplete.js");
8355
9030
 
8356
9031
  module.exports = app => app.component('document-search', {
@@ -8370,7 +9045,11 @@ module.exports = app => app.component('document-search', {
8370
9045
  autocompleteSuggestions: [],
8371
9046
  autocompleteIndex: 0,
8372
9047
  autocompleteTrie: null,
8373
- searchText: this.value || ''
9048
+ searchText: this.value || '',
9049
+ datePickerContext: null,
9050
+ datePickerLocalValue: '',
9051
+ dateInsertFormat: 'quoted', // 'timestamp' | 'quoted'
9052
+ datePickerMinimized: false
8374
9053
  };
8375
9054
  },
8376
9055
  watch: {
@@ -8412,10 +9091,31 @@ module.exports = app => app.component('document-search', {
8412
9091
  });
8413
9092
  }
8414
9093
  },
9094
+ onSearchKeyup(ev) {
9095
+ if (
9096
+ this.autocompleteSuggestions.length > 0 &&
9097
+ (ev.key === 'ArrowUp' || ev.key === 'ArrowDown')
9098
+ ) {
9099
+ return;
9100
+ }
9101
+ this.updateAutocomplete();
9102
+ },
8415
9103
  updateAutocomplete() {
8416
9104
  const input = this.$refs.searchInput;
8417
9105
  const cursorPos = input ? input.selectionStart : 0;
8418
9106
 
9107
+ const dateRange = getDatePickerInsertionRange(this.searchText, cursorPos);
9108
+ if (dateRange && !this.datePickerContext) {
9109
+ this.datePickerMinimized = false;
9110
+ }
9111
+ this.datePickerContext = dateRange;
9112
+ if (dateRange) {
9113
+ const argSlice = this.searchText.slice(dateRange.innerStart, dateRange.innerEnd);
9114
+ this.datePickerLocalValue = dateArgumentSliceToDatetimeLocal(argSlice);
9115
+ } else {
9116
+ this.datePickerLocalValue = '';
9117
+ }
9118
+
8419
9119
  if (this.autocompleteTrie) {
8420
9120
  this.autocompleteSuggestions = getAutocompleteSuggestions(
8421
9121
  this.autocompleteTrie,
@@ -8428,6 +9128,34 @@ module.exports = app => app.component('document-search', {
8428
9128
  this.autocompleteSuggestions = [];
8429
9129
  }
8430
9130
  },
9131
+ applyDateFromPicker(localDateTime) {
9132
+ if (!localDateTime || !this.datePickerContext) {
9133
+ return;
9134
+ }
9135
+ const picked = new Date(localDateTime);
9136
+ if (Number.isNaN(picked.getTime())) {
9137
+ if (this.$toast) {
9138
+ this.$toast.error('Invalid date or time. Enter a valid date and time, then try again.');
9139
+ }
9140
+ return;
9141
+ }
9142
+ const result = insertDateInDateArgument(
9143
+ this.searchText,
9144
+ this.datePickerContext,
9145
+ picked,
9146
+ this.dateInsertFormat
9147
+ );
9148
+ const input = this.$refs.searchInput;
9149
+ this.searchText = result.text;
9150
+ this.autocompleteSuggestions = [];
9151
+ this.$nextTick(() => {
9152
+ if (input) {
9153
+ input.focus();
9154
+ input.setSelectionRange(result.newCursorPos, result.newCursorPos);
9155
+ }
9156
+ this.updateAutocomplete();
9157
+ });
9158
+ },
8431
9159
  handleKeyDown(ev) {
8432
9160
  if (this.autocompleteSuggestions.length === 0) {
8433
9161
  return;
@@ -8462,6 +9190,12 @@ module.exports = app => app.component('document-search', {
8462
9190
  });
8463
9191
  this.autocompleteSuggestions = [];
8464
9192
  },
9193
+ onWrapperFocusOut(ev) {
9194
+ if (!this.$el.contains(ev.relatedTarget)) {
9195
+ this.datePickerContext = null;
9196
+ this.datePickerMinimized = false;
9197
+ }
9198
+ },
8465
9199
  addPathFilter(path) {
8466
9200
  if (this.searchText) {
8467
9201
  if (this.searchText.endsWith('}')) {
@@ -8779,6 +9513,7 @@ module.exports = app => app.component('models', {
8779
9513
  currentModel: null,
8780
9514
  modelDocumentCounts: {},
8781
9515
  documents: [],
9516
+ allSchemaPaths: null,
8782
9517
  schemaPaths: [],
8783
9518
  filteredPaths: [],
8784
9519
  selectedPaths: [],
@@ -8896,8 +9631,9 @@ module.exports = app => app.component('models', {
8896
9631
  this.query = Object.assign({}, this.$route.query);
8897
9632
  // Keep UI mode in sync with the URL on remounts.
8898
9633
  this.isProjectionMenuSelected = this.$route?.query?.[PROJECTION_MODE_QUERY_KEY] === '1';
8899
- const { models, readyState } = await api.Model.listModels();
9634
+ const { models, modelSchemaPaths, readyState } = await api.Model.listModels();
8900
9635
  this.models = models;
9636
+ this.allSchemaPaths = modelSchemaPaths;
8901
9637
  await this.loadModelCounts();
8902
9638
  if (this.currentModel == null && this.models.length > 0) {
8903
9639
  this.currentModel = this.models[0];
@@ -8908,6 +9644,29 @@ module.exports = app => app.component('models', {
8908
9644
  if (readyState === 0) {
8909
9645
  this.error = 'No models found and Mongoose is not connected. Check our documentation for more information.';
8910
9646
  }
9647
+ } else {
9648
+ this._refreshSidebar = setInterval(async() => {
9649
+ try {
9650
+ const { models, modelSchemaPaths, readyState } = await api.Model.listModels();
9651
+ this.models = models;
9652
+ this.allSchemaPaths = modelSchemaPaths;
9653
+ await this.loadModelCounts();
9654
+ if (this.currentModel) {
9655
+ const schemaPaths = this.allSchemaPaths?.[this.currentModel] ?? {};
9656
+ this.schemaPaths = Object.keys(schemaPaths).sort((k1, k2) => {
9657
+ if (k1 === '_id' && k2 !== '_id') {
9658
+ return -1;
9659
+ }
9660
+ if (k1 !== '_id' && k2 === '_id') {
9661
+ return 1;
9662
+ }
9663
+ return 0;
9664
+ }).map(key => schemaPaths[key]);
9665
+ }
9666
+ } catch (e) {
9667
+ console.error(e);
9668
+ }
9669
+ }, 20000);
8911
9670
  }
8912
9671
 
8913
9672
  await this.initSearchFromUrl();
@@ -8918,25 +9677,33 @@ module.exports = app => app.component('models', {
8918
9677
  this.$nextTick(() => {
8919
9678
  const focusIntent = takeModelsRemountFocusIntent();
8920
9679
  if (focusIntent === 'documentSearch') {
8921
- const ds = this.$refs.documentSearch;
8922
- if (ds && typeof ds.focusInput === 'function') {
8923
- ds.focusInput();
8924
- }
9680
+ this.$refs.documentSearch?.focusInput();
8925
9681
  return;
8926
9682
  }
8927
9683
  if (focusIntent === 'projection' && this.isProjectionMenuSelected) {
8928
- const projectionSearch = this.$refs.projectionSearch;
8929
- if (projectionSearch && typeof projectionSearch.focusInput === 'function') {
8930
- projectionSearch.focusInput();
8931
- }
9684
+ this.$refs.projectionSearch?.focusInput();
8932
9685
  }
8933
9686
  });
8934
9687
  },
9688
+ unmounted() {
9689
+ clearInterval(this._refreshSidebar);
9690
+ },
8935
9691
  watch: {
8936
9692
  model(newModel) {
8937
9693
  if (newModel !== this.currentModel) {
8938
9694
  this.currentModel = newModel;
8939
9695
  if (this.currentModel != null) {
9696
+ this.$refs.documentSearch?.focusInput();
9697
+ const schemaPaths = this.allSchemaPaths?.[newModel] ?? {};
9698
+ this.schemaPaths = Object.keys(schemaPaths).sort((k1, k2) => {
9699
+ if (k1 === '_id' && k2 !== '_id') {
9700
+ return -1;
9701
+ }
9702
+ if (k1 !== '_id' && k2 === '_id') {
9703
+ return 1;
9704
+ }
9705
+ return 0;
9706
+ }).map(key => schemaPaths[key]);
8940
9707
  this.initSearchFromUrl();
8941
9708
  }
8942
9709
  }
@@ -9380,6 +10147,8 @@ module.exports = app => app.component('models', {
9380
10147
  this.query.sortKey = sortKey;
9381
10148
  this.query.sortDirection = sortDirection;
9382
10149
  delete this.query.sort;
10150
+ } else {
10151
+ this.sortBy = {};
9383
10152
  }
9384
10153
  if (this.currentModel != null) {
9385
10154
  await this.getDocuments();
@@ -9395,6 +10164,8 @@ module.exports = app => app.component('models', {
9395
10164
  this.selectedPaths = [...this.filteredPaths];
9396
10165
  this.syncProjectionInputFromQueryOrPaths();
9397
10166
  }
10167
+ } else {
10168
+ this.selectedPaths = [];
9398
10169
  }
9399
10170
  this.status = 'loaded';
9400
10171
 
@@ -9676,7 +10447,6 @@ module.exports = app => app.component('models', {
9676
10447
 
9677
10448
  // Clear previous data
9678
10449
  this.documents = [];
9679
- this.schemaPaths = [];
9680
10450
  this.numDocuments = null;
9681
10451
  this.loadedAllDocs = false;
9682
10452
  this.lastSelectedIndex = null;
@@ -10997,7 +11767,8 @@ module.exports = {
10997
11767
  name: 'model',
10998
11768
  component: 'models',
10999
11769
  meta: {
11000
- authorized: false
11770
+ authorized: false,
11771
+ preserveStateKey: 'model'
11001
11772
  }
11002
11773
  },
11003
11774
  {
@@ -12379,6 +13150,8 @@ var map = {
12379
13150
  "./": "./frontend/src/index.js",
12380
13151
  "./_util/baseComponent": "./frontend/src/_util/baseComponent.js",
12381
13152
  "./_util/baseComponent.js": "./frontend/src/_util/baseComponent.js",
13153
+ "./_util/calendar": "./frontend/src/_util/calendar.js",
13154
+ "./_util/calendar.js": "./frontend/src/_util/calendar.js",
12382
13155
  "./_util/dateRange": "./frontend/src/_util/dateRange.js",
12383
13156
  "./_util/dateRange.js": "./frontend/src/_util/dateRange.js",
12384
13157
  "./_util/deepEqual": "./frontend/src/_util/deepEqual.js",
@@ -12421,6 +13194,9 @@ var map = {
12421
13194
  "./create-document/create-document.css": "./frontend/src/create-document/create-document.css",
12422
13195
  "./create-document/create-document.html": "./frontend/src/create-document/create-document.html",
12423
13196
  "./create-document/create-document.js": "./frontend/src/create-document/create-document.js",
13197
+ "./custom-date-picker/custom-date-picker": "./frontend/src/custom-date-picker/custom-date-picker.js",
13198
+ "./custom-date-picker/custom-date-picker.html": "./frontend/src/custom-date-picker/custom-date-picker.html",
13199
+ "./custom-date-picker/custom-date-picker.js": "./frontend/src/custom-date-picker/custom-date-picker.js",
12424
13200
  "./dashboard-result/dashboard-chart/dashboard-chart": "./frontend/src/dashboard-result/dashboard-chart/dashboard-chart.js",
12425
13201
  "./dashboard-result/dashboard-chart/dashboard-chart.html": "./frontend/src/dashboard-result/dashboard-chart/dashboard-chart.html",
12426
13202
  "./dashboard-result/dashboard-chart/dashboard-chart.js": "./frontend/src/dashboard-result/dashboard-chart/dashboard-chart.js",
@@ -52856,6 +53632,17 @@ module.exports = "<div class=\"px-1\">\n <div class=\"mb-4\">\n <label class
52856
53632
 
52857
53633
  /***/ },
52858
53634
 
53635
+ /***/ "./frontend/src/custom-date-picker/custom-date-picker.html"
53636
+ /*!*****************************************************************!*\
53637
+ !*** ./frontend/src/custom-date-picker/custom-date-picker.html ***!
53638
+ \*****************************************************************/
53639
+ (module) {
53640
+
53641
+ "use strict";
53642
+ module.exports = "<div\n class=\"custom-date-picker\"\n :class=\"rootClasses\"\n>\n <div :class=\"horizontal ? 'flex gap-3 items-start' : ''\">\n <div :class=\"[horizontal ? 'shrink-0' : '', calendarSectionClass]\">\n <div class=\"flex items-center justify-between gap-1 mb-1\" :class=\"mini ? 'mb-0.5' : 'mb-2'\">\n <button\n type=\"button\"\n class=\"shrink-0 flex items-center justify-center rounded border border-edge-strong hover:bg-surface-hover text-content-secondary\"\n :class=\"mini ? 'w-5 h-5 text-[10px]' : 'w-7 h-7'\"\n aria-label=\"Previous month\"\n @click=\"prevMonth\"\n >‹</button>\n <span\n class=\"flex-1 min-w-0 text-center font-medium text-content-primary truncate\"\n :class=\"mini ? 'text-[10px]' : ''\"\n >{{ monthLabel }}</span>\n <button\n type=\"button\"\n class=\"shrink-0 flex items-center justify-center rounded border border-edge-strong hover:bg-surface-hover text-content-secondary\"\n :class=\"mini ? 'w-5 h-5 text-[10px]' : 'w-7 h-7'\"\n aria-label=\"Next month\"\n @click=\"nextMonth\"\n >›</button>\n </div>\n\n <div class=\"grid grid-cols-7 gap-px mb-0.5\">\n <div\n v-for=\"label in weekdayLabels\"\n :key=\"label\"\n class=\"flex items-center justify-center text-center text-content-tertiary font-medium min-w-0\"\n :class=\"[\n dayCellClass,\n mini ? 'text-[9px]' : (compact ? 'text-[10px]' : 'text-xs')\n ]\"\n >{{ mini ? label.charAt(0) : label }}</div>\n </div>\n\n <div class=\"grid grid-cols-7 gap-px\">\n <button\n v-for=\"day in calendarDays\"\n :key=\"day.key\"\n type=\"button\"\n class=\"min-w-0 w-full flex items-center justify-center rounded transition-colors\"\n :class=\"[\n dayCellClass,\n mini ? 'text-[10px]' : (compact ? 'text-xs' : 'text-sm'),\n dayClasses(day)\n ]\"\n :aria-label=\"day.label\"\n :aria-pressed=\"isSelectedDay(day)\"\n @click=\"selectDay(day)\"\n >{{ day.label }}</button>\n </div>\n </div>\n\n <div\n v-if=\"showTime\"\n :class=\"timeSectionClasses\"\n @mousedown.stop\n >\n <div\n class=\"text-content-secondary\"\n :class=\"[\n horizontal ? 'mb-1 text-xs' : 'mb-2',\n mini ? 'text-xs' : (horizontal ? '' : 'text-sm')\n ]\"\n >Time</div>\n <div class=\"relative flex items-center justify-center\">\n <div\n class=\"pointer-events-none absolute inset-x-0 top-1/2 -translate-y-1/2 border-y border-edge-strong bg-surface-hover/40\"\n :class=\"wheelHighlightClass\"\n aria-hidden=\"true\"\n ></div>\n <div class=\"relative flex items-center justify-center gap-1\">\n <div\n ref=\"hourWheel\"\n class=\"wheel-column\"\n :class=\"[wheelListClass, wheelColumnClass]\"\n :style=\"wheelListStyle\"\n @scroll=\"onHourWheelScroll\"\n >\n <div\n v-for=\"n in wheelPadCount\"\n :key=\"'hour-pad-top-' + n\"\n :class=\"wheelItemClass\"\n aria-hidden=\"true\"\n ></div>\n <button\n v-for=\"h in hourOptions\"\n :key=\"'hour-' + h\"\n type=\"button\"\n :class=\"[wheelItemClass, wheelItemClasses(h, hour)]\"\n :aria-label=\"'Hour ' + h\"\n :aria-selected=\"hour === h\"\n @click=\"selectHour(h)\"\n >{{ formatWheelValue(h) }}</button>\n <div\n v-for=\"n in wheelPadCount\"\n :key=\"'hour-pad-bottom-' + n\"\n :class=\"wheelItemClass\"\n aria-hidden=\"true\"\n ></div>\n </div>\n <span class=\"text-content-secondary font-medium\" :class=\"mini ? 'text-sm' : 'text-base'\">:</span>\n <div\n ref=\"minuteWheel\"\n class=\"wheel-column\"\n :class=\"[wheelListClass, wheelColumnClass]\"\n :style=\"wheelListStyle\"\n @scroll=\"onMinuteWheelScroll\"\n >\n <div\n v-for=\"n in wheelPadCount\"\n :key=\"'minute-pad-top-' + n\"\n :class=\"wheelItemClass\"\n aria-hidden=\"true\"\n ></div>\n <button\n v-for=\"m in minuteOptions\"\n :key=\"'minute-' + m\"\n type=\"button\"\n :class=\"[wheelItemClass, wheelItemClasses(m, minute)]\"\n :aria-label=\"'Minute ' + m\"\n :aria-selected=\"minute === m\"\n @click=\"selectMinute(m)\"\n >{{ formatWheelValue(m) }}</button>\n <div\n v-for=\"n in wheelPadCount\"\n :key=\"'minute-pad-bottom-' + n\"\n :class=\"wheelItemClass\"\n aria-hidden=\"true\"\n ></div>\n </div>\n <span class=\"text-content-secondary font-medium\" :class=\"mini ? 'text-sm' : 'text-base'\">:</span>\n <div\n ref=\"secondWheel\"\n class=\"wheel-column\"\n :class=\"[wheelListClass, wheelColumnClass]\"\n :style=\"wheelListStyle\"\n @scroll=\"onSecondWheelScroll\"\n >\n <div\n v-for=\"n in wheelPadCount\"\n :key=\"'second-pad-top-' + n\"\n :class=\"wheelItemClass\"\n aria-hidden=\"true\"\n ></div>\n <button\n v-for=\"s in secondOptions\"\n :key=\"'second-' + s\"\n type=\"button\"\n :class=\"[wheelItemClass, wheelItemClasses(s, second)]\"\n :aria-label=\"'Second ' + s\"\n :aria-selected=\"second === s\"\n @click=\"selectSecond(s)\"\n >{{ formatWheelValue(s) }}</button>\n <div\n v-for=\"n in wheelPadCount\"\n :key=\"'second-pad-bottom-' + n\"\n :class=\"wheelItemClass\"\n aria-hidden=\"true\"\n ></div>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <div\n class=\"flex justify-end items-center gap-2\"\n :class=\"mini ? 'mt-1' : 'mt-2'\"\n >\n <button\n v-if=\"!immediate\"\n type=\"button\"\n class=\"text-xs font-medium text-white bg-merlot-700 hover:bg-merlot-800 px-2 py-1 rounded\"\n :class=\"mini ? 'py-0.5 px-1.5' : ''\"\n @click=\"commit\"\n >Apply</button>\n </div>\n</div>\n";
53643
+
53644
+ /***/ },
53645
+
52859
53646
  /***/ "./frontend/src/dashboard-result/dashboard-chart/dashboard-chart.html"
52860
53647
  /*!****************************************************************************!*\
52861
53648
  !*** ./frontend/src/dashboard-result/dashboard-chart/dashboard-chart.html ***!
@@ -53039,7 +53826,7 @@ module.exports = "<pre class=\"w-full whitespace-pre-wrap break-words font-mono
53039
53826
  (module) {
53040
53827
 
53041
53828
  "use strict";
53042
- module.exports = "<div class=\"flex items-center gap-2\" @click.stop>\n <select\n :value=\"format\"\n @input=\"onFormatChange($event.target.value)\"\n class=\"text-xs border border-edge rounded-md bg-surface px-2 py-1\"\n >\n <option value=\"utc_iso\">UTC (ISO)</option>\n <option value=\"local_browser\">Local (Browser)</option>\n <option value=\"unix_ms\">Unix (ms)</option>\n <option value=\"unix_seconds\">Unix (seconds)</option>\n <option value=\"duration_relative\">Duration relative to now</option>\n <option value=\"custom_tz\">Custom TZ...</option>\n </select>\n <input\n v-if=\"format === 'custom_tz'\"\n :value=\"timezone\"\n @input=\"onTimezoneChange($event.target.value.trim())\"\n :list=\"timezoneDatalistId\"\n type=\"text\"\n placeholder=\"America/New_York\"\n class=\"text-xs border border-edge rounded-md bg-surface px-2 py-1 w-40\"\n >\n <datalist v-if=\"format === 'custom_tz'\" :id=\"timezoneDatalistId\">\n <option v-for=\"tz in timezones\" :key=\"tz\" :value=\"tz\"></option>\n </datalist>\n</div>\n";
53829
+ module.exports = "<div class=\"flex items-center gap-2\" @click.stop>\n <select\n :value=\"format\"\n @input=\"onFormatChange($event.target.value)\"\n :disabled=\"editting\"\n class=\"text-xs border border-edge rounded-md bg-surface px-2 py-1\"\n >\n <option value=\"utc_iso\">UTC (ISO)</option>\n <option value=\"local_browser\">Local (Browser)</option>\n <option value=\"unix_ms\">Unix (ms)</option>\n <option value=\"unix_seconds\">Unix (seconds)</option>\n <option value=\"duration_relative\">Duration relative to now</option>\n <option value=\"custom_tz\">Custom TZ...</option>\n </select>\n <input\n v-if=\"format === 'custom_tz'\"\n :value=\"timezone\"\n @input=\"onTimezoneChange($event.target.value.trim())\"\n :list=\"timezoneDatalistId\"\n type=\"text\"\n placeholder=\"America/New_York\"\n class=\"text-xs border border-edge rounded-md bg-surface px-2 py-1 w-40\"\n >\n <datalist v-if=\"format === 'custom_tz'\" :id=\"timezoneDatalistId\">\n <option v-for=\"tz in timezones\" :key=\"tz\" :value=\"tz\"></option>\n </datalist>\n</div>\n";
53043
53830
 
53044
53831
  /***/ },
53045
53832
 
@@ -53050,7 +53837,7 @@ module.exports = "<div class=\"flex items-center gap-2\" @click.stop>\n <select
53050
53837
  (module) {
53051
53838
 
53052
53839
  "use strict";
53053
- module.exports = ".document-details {\n width: 100%;\n}\n\n.document-details .value {\n padding-top: 10px;\n padding-bottom: 10px;\n}\n\n.document-details .path-key {\n background-color: #f0f0f0;\n margin-bottom: 0.5em;\n}\n\n.document-details .path-type {\n color: rgba(0,0,0,.36);\n font-size: 0.8em;\n}\n\n.document-details .date-position {\n float: right;\n margin-top: -7px;\n}\n\n/* Add Field Modal Styles */\n.add-field-modal {\n max-width: 500px;\n width: 100%;\n}\n\n.add-field-modal .modal-exit {\n position: absolute;\n top: 15px;\n right: 20px;\n font-size: 24px;\n cursor: pointer;\n color: #6b7280;\n z-index: 10;\n}\n\n.add-field-modal .modal-exit:hover {\n color: #374151;\n}\n\n.add-field-modal form {\n max-height: 70vh;\n overflow-y: auto;\n}\n\n.add-field-modal input[type=\"text\"],\n.add-field-modal input[type=\"email\"],\n.add-field-modal input[type=\"password\"],\n.add-field-modal select,\n.add-field-modal textarea {\n transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n.add-field-modal input:focus,\n.add-field-modal select:focus,\n.add-field-modal textarea:focus {\n border-color: #3b82f6;\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);\n}\n\n.add-field-modal .border-red-500 {\n border-color: #ef4444 !important;\n}\n\n.add-field-modal .border-red-500:focus {\n box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1) !important;\n}\n\n/* Ace editor styling in modal */\n.add-field-modal .add-field-ace-editor.ace_editor {\n border: 1px solid #d1d5db;\n border-radius: 0.375rem;\n font-size: 14px;\n min-height: 100px;\n}\n\n.add-field-modal .add-field-ace-editor:focus-within.ace_editor,\n.add-field-modal .add-field-ace-editor.ace_focus {\n border-color: #3b82f6;\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);\n}\n\n/* JSON View Styles */\n.json-view {\n width: 100%;\n}\n\n.json-view pre {\n margin: 0;\n max-height: 70vh;\n overflow: auto;\n line-height: 1.5;\n}\n\n.json-view pre::-webkit-scrollbar {\n width: 8px;\n height: 8px;\n}\n\n.json-view pre::-webkit-scrollbar-track {\n background: #f1f1f1;\n border-radius: 4px;\n}\n\n.json-view pre::-webkit-scrollbar-thumb {\n background: #888;\n border-radius: 4px;\n}\n\n.json-view pre::-webkit-scrollbar-thumb:hover {\n background: #555;\n}";
53840
+ module.exports = ".document-details {\n width: 100%;\n}\n\n.document-details .value {\n padding-top: 10px;\n padding-bottom: 10px;\n}\n\n.document-details .path-key {\n background-color: #f0f0f0;\n margin-bottom: 0.5em;\n}\n\n.document-details .path-type {\n color: rgba(0,0,0,.36);\n font-size: 0.8em;\n}\n\n.document-details .date-position {\n float: right;\n margin-top: -7px;\n}\n\n/* JSON View Styles */\n.json-view {\n width: 100%;\n}\n\n.json-view pre {\n margin: 0;\n max-height: 70vh;\n overflow: auto;\n line-height: 1.5;\n}\n\n.json-view pre::-webkit-scrollbar {\n width: 8px;\n height: 8px;\n}\n\n.json-view pre::-webkit-scrollbar-track {\n background: #f1f1f1;\n border-radius: 4px;\n}\n\n.json-view pre::-webkit-scrollbar-thumb {\n background: #888;\n border-radius: 4px;\n}\n\n.json-view pre::-webkit-scrollbar-thumb:hover {\n background: #555;\n}\n";
53054
53841
 
53055
53842
  /***/ },
53056
53843
 
@@ -53061,7 +53848,7 @@ module.exports = ".document-details {\n width: 100%;\n}\n\n.document-details .v
53061
53848
  (module) {
53062
53849
 
53063
53850
  "use strict";
53064
- module.exports = "<div class=\"document-details\">\n <!-- View Toggle and Search/Filter Bar -->\n <div class=\"sticky top-[60px] z-40 bg-surface rounded-md p-4 border-b border-edge shadow-sm\">\n\n <!-- Search and Filter Bar (only show in fields view) -->\n <div v-if=\"viewMode === 'fields'\" class=\"flex md:gap-3\">\n <!-- Search Bar -->\n <div class=\"relative flex-1\">\n <input\n ref=\"searchInput\"\n v-model=\"searchQuery\"\n type=\"text\"\n placeholder=\"Search fields...\"\n class=\"w-full px-4 py-2 pl-10 pr-4 text-sm border border-edge-strong rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n />\n <div class=\"absolute inset-y-0 left-0 flex items-center pl-3\">\n <svg class=\"w-4 h-4 text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"></path>\n </svg>\n </div>\n </div>\n\n <!-- Data Type Filter -->\n <div class=\"relative hidden md:block\">\n <select\n v-model=\"selectedType\"\n class=\"px-4 py-2 pr-8 text-sm border border-edge-strong rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-surface min-w-[140px] appearance-none\"\n >\n <option value=\"\">All Types</option>\n <option v-for=\"type in availableTypes\" :key=\"type\" :value=\"type\">\n {{type}}\n </option>\n </select>\n <div class=\"absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none\">\n <svg class=\"w-4 h-4 text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Fields View -->\n <div v-if=\"viewMode === 'fields'\">\n <!-- Matched Schema Paths (shown first when searching) -->\n <div\n v-for=\"path in matchedSchemaPaths\"\n :key=\"path.path || path\"\n class=\"value\"\n >\n <document-property\n :path=\"path\"\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :highlight=\"true\"\n :searchQuery=\"searchQuery\"\n :invalid=\"invalid\"></document-property>\n </div>\n\n <!-- Matched Virtual Fields (shown after matched schema paths when searching) -->\n <div\n v-for=\"path in matchedVirtuals\"\n :key=\"path.name\"\n class=\"border border-edge rounded-lg mb-2 transition-all duration-200 ease-in-out mt-4\"\n >\n <!-- Virtual Field Header (Always Visible) -->\n <div\n @click=\"toggleVirtualField(path.name)\"\n 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\"\n >\n <div class=\"flex items-center\">\n <svg\n :class=\"isVirtualFieldCollapsed(path.name) ? 'rotate-0' : 'rotate-90'\"\n class=\"w-4 h-4 text-content-tertiary mr-2 transition-transform duration-200\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\"></path>\n </svg>\n <span class=\"font-medium text-content\">\n <span\n v-for=\"(part, index) in getSearchMatchParts(path.name)\"\n :key=\"index\"\n :class=\"{ 'font-bold': part.matched }\">{{ part.text }}</span>\n </span>\n <span v-if=\"path.isVirtual\" class=\"ml-2 text-sm text-purple-600\">(virtual - {{getVirtualFieldType(path)}})</span>\n <span v-else class=\"ml-2 text-sm text-blue-600\">(user-added - {{getVirtualFieldType(path)}})</span>\n </div>\n </div>\n\n <!-- Virtual Field Content (Collapsible) -->\n <div v-if=\"!isVirtualFieldCollapsed(path.name)\" class=\"p-3\">\n <div v-if=\"path.value == null\" class=\"text-sky-800\">\n {{'' + path.value}}\n </div>\n <div v-else>\n {{path.value}}\n </div>\n </div>\n </div>\n\n <!-- Unmatched Schema Paths (shown after matched items when searching, or all when not searching) -->\n <div\n v-for=\"path in unmatchedSchemaPaths\"\n :key=\"path.path || path\"\n class=\"value\"\n >\n <document-property\n :path=\"path\"\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :highlight=\"false\"\n :searchQuery=\"searchQuery\"\n :invalid=\"invalid\"></document-property>\n </div>\n\n <!-- Unmatched Virtual Fields (shown last) -->\n <div\n v-for=\"path in unmatchedVirtuals\"\n :key=\"path.name\"\n class=\"border border-edge rounded-lg mb-2 transition-all duration-200 ease-in-out\"\n >\n <!-- Virtual Field Header (Always Visible) -->\n <div\n @click=\"toggleVirtualField(path.name)\"\n 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\"\n >\n <div class=\"flex items-center\">\n <svg\n :class=\"isVirtualFieldCollapsed(path.name) ? 'rotate-0' : 'rotate-90'\"\n class=\"w-4 h-4 text-content-tertiary mr-2 transition-transform duration-200\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\"></path>\n </svg>\n <span class=\"font-medium text-content\">{{path.name}}</span>\n <span v-if=\"path.isVirtual\" class=\"ml-2 text-sm text-purple-600\">(virtual - {{getVirtualFieldType(path)}})</span>\n <span v-else class=\"ml-2 text-sm text-blue-600\">(user-added - {{getVirtualFieldType(path)}})</span>\n </div>\n </div>\n\n <!-- Virtual Field Content (Collapsible) -->\n <div v-if=\"!isVirtualFieldCollapsed(path.name)\" class=\"p-3\">\n <div v-if=\"path.value == null\" class=\"text-sky-800\">\n {{'' + path.value}}\n </div>\n <div v-else>\n {{path.value}}\n </div>\n </div>\n </div>\n\n <!-- No Results Message -->\n <div v-if=\"searchQuery.trim() && !hasSearchMatches\" class=\"text-center py-8 text-content-tertiary\">\n <svg class=\"w-12 h-12 mx-auto mb-4 text-gray-300\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9.172 16.172a4 4 0 015.656 0M9 12h6m-6-4h6m2 5.291A7.962 7.962 0 0112 15c-2.34 0-4.29-1.009-5.824-2.709M15 6.291A7.962 7.962 0 0012 5c-2.34 0-4.29 1.009-5.824 2.709\"></path>\n </svg>\n <p>No fields found matching \"{{searchQuery}}\"</p>\n </div>\n </div>\n\n <!-- JSON View -->\n <div v-if=\"viewMode === 'json'\" class=\"json-view\">\n <div class=\"border border-edge-strong rounded-lg bg-page p-4 overflow-auto\">\n <pre class=\"text-sm font-mono text-gray-800 whitespace-pre\">{{formattedJson}}</pre>\n </div>\n </div>\n\n <!-- Add Field Modal -->\n <modal v-if=\"showAddFieldModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"closeAddFieldModal\">&times;</div>\n <div class=\"add-field-modal\">\n <div class=\"mb-6\">\n <h2 class=\"text-xl font-semibold text-content mb-2\">Add New Field</h2>\n <p class=\"text-sm text-gray-600\">Create a new field for this document</p>\n </div>\n\n <form @submit.prevent=\"handleAddFieldSubmit\" class=\"space-y-4\">\n <!-- Field Name -->\n <div>\n <label for=\"fieldName\" class=\"block text-sm font-medium text-content-secondary mb-1\">\n Field Name *\n </label>\n <input\n id=\"fieldName\"\n v-model=\"fieldData.name\"\n type=\"text\"\n required\n placeholder=\"Enter field name...\"\n class=\"w-full px-3 py-2 border border-edge-strong rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.name }\"\n />\n <p v-if=\"fieldErrors.name\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.name}}</p>\n <p v-if=\"fieldData.name && getTransformedFieldName() !== fieldData.name.trim()\" class=\"mt-1 text-sm text-blue-600\">\n Field name will be: <code class=\"bg-blue-50 px-1 py-0.5 rounded text-xs\">{{getTransformedFieldName()}}</code>\n </p>\n </div>\n\n <!-- Field Type -->\n <div>\n <label for=\"fieldType\" class=\"block text-sm font-medium text-content-secondary mb-1\">\n Field Type *\n </label>\n <select\n id=\"fieldType\"\n v-model=\"fieldData.type\"\n required\n class=\"w-full px-3 py-2 border border-edge-strong rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.type }\"\n >\n <option value=\"\">Select field type...</option>\n <option v-for=\"type in allFieldTypes\" :key=\"type\" :value=\"type\">\n {{type}}\n </option>\n </select>\n <p v-if=\"fieldErrors.type\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.type}}</p>\n </div>\n\n <!-- Field Value -->\n <div>\n <label for=\"fieldValue\" class=\"block text-sm font-medium text-content-secondary mb-1\">\n Initial Value\n </label>\n\n <!-- Date picker for Date type -->\n <input\n v-if=\"shouldUseDatePicker\"\n v-model=\"fieldData.value\"\n type=\"date\"\n id=\"fieldValue\"\n class=\"w-full px-3 py-2 border border-edge-strong rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.value }\"\n />\n\n <!-- Simple input for basic types -->\n <input\n v-else-if=\"!shouldUseAce\"\n v-model=\"fieldData.value\"\n type=\"text\"\n id=\"fieldValue\"\n placeholder=\"Enter initial value (optional)...\"\n class=\"w-full px-3 py-2 border border-edge-strong rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.value }\"\n />\n\n <!-- Ace editor for complex types (Array/Object/Embedded) -->\n <ace-editor\n v-else\n id=\"fieldValue\"\n v-model=\"fieldData.value\"\n mode=\"json\"\n :line-numbers=\"true\"\n :min-lines=\"3\"\n class=\"add-field-ace-editor w-full min-h-[100px]\"\n :class=\"{ 'border-red-500': fieldErrors.value }\"\n />\n\n <p v-if=\"fieldErrors.value\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.value}}</p>\n <p class=\"mt-1 text-xs text-content-tertiary\">\n <span v-if=\"shouldUseDatePicker\">Select a date or leave empty for null/undefined values.</span>\n <span v-else-if=\"shouldUseAce\">Leave empty for null/undefined values. Use valid JSON format.</span>\n <span v-else>Leave empty for null/undefined values.</span>\n </p>\n </div>\n\n\n <!-- Action Buttons -->\n <div class=\"flex justify-end gap-3 pt-4 border-t border-edge\">\n <button\n type=\"button\"\n @click=\"closeAddFieldModal\"\n class=\"px-4 py-2 text-sm font-medium text-content-secondary bg-surface border border-edge-strong rounded-md hover:bg-page focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n :disabled=\"isSubmittingField\"\n class=\"px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <span v-if=\"isSubmittingField\">Adding...</span>\n <span v-else>Add Field</span>\n </button>\n </div>\n </form>\n </div>\n </template>\n </modal>\n</div>\n";
53851
+ module.exports = "<div class=\"document-details\">\n <!-- View Toggle and Search/Filter Bar -->\n <div class=\"sticky top-[60px] z-40 bg-surface rounded-md p-4 border-b border-edge shadow-sm\">\n\n <!-- Search and Filter Bar (only show in fields view) -->\n <div v-if=\"viewMode === 'fields'\" class=\"flex md:gap-3\">\n <!-- Search Bar -->\n <div class=\"relative flex-1\">\n <input\n ref=\"searchInput\"\n v-model=\"searchQuery\"\n type=\"text\"\n placeholder=\"Search fields...\"\n class=\"w-full px-4 py-2 pl-10 pr-4 text-sm border border-edge-strong rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n />\n <div class=\"absolute inset-y-0 left-0 flex items-center pl-3\">\n <svg class=\"w-4 h-4 text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"></path>\n </svg>\n </div>\n </div>\n\n <!-- Data Type Filter -->\n <div class=\"relative hidden md:block\">\n <select\n v-model=\"selectedType\"\n class=\"px-4 py-2 pr-8 text-sm border border-edge-strong rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-surface min-w-[140px] appearance-none\"\n >\n <option value=\"\">All Types</option>\n <option v-for=\"type in availableTypes\" :key=\"type\" :value=\"type\">\n {{type}}\n </option>\n </select>\n <div class=\"absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none\">\n <svg class=\"w-4 h-4 text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Fields View -->\n <div v-if=\"viewMode === 'fields'\">\n <!-- Matched Schema Paths (shown first when searching) -->\n <div\n v-for=\"path in matchedSchemaPaths\"\n :key=\"path.path || path\"\n class=\"value\"\n >\n <document-property\n :path=\"path\"\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :highlight=\"true\"\n :searchQuery=\"searchQuery\"\n :invalid=\"invalid\"></document-property>\n </div>\n\n <!-- Matched Virtual Fields (shown after matched schema paths when searching) -->\n <div\n v-for=\"path in matchedVirtuals\"\n :key=\"path.name\"\n class=\"border border-edge rounded-lg mb-2 transition-all duration-200 ease-in-out mt-4\"\n >\n <!-- Virtual Field Header (Always Visible) -->\n <div\n @click=\"toggleVirtualField(path.name)\"\n 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\"\n >\n <div class=\"flex items-center\">\n <svg\n :class=\"isVirtualFieldCollapsed(path.name) ? 'rotate-0' : 'rotate-90'\"\n class=\"w-4 h-4 text-content-tertiary mr-2 transition-transform duration-200\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\"></path>\n </svg>\n <span class=\"font-medium text-content\">\n <span\n v-for=\"(part, index) in getSearchMatchParts(path.name)\"\n :key=\"index\"\n :class=\"{ 'font-bold': part.matched }\">{{ part.text }}</span>\n </span>\n <span v-if=\"path.isVirtual\" class=\"ml-2 text-sm text-purple-600\">(virtual - {{getVirtualFieldType(path)}})</span>\n <span v-else class=\"ml-2 text-sm text-blue-600\">(user-added - {{getVirtualFieldType(path)}})</span>\n </div>\n </div>\n\n <!-- Virtual Field Content (Collapsible) -->\n <div v-if=\"!isVirtualFieldCollapsed(path.name)\" class=\"p-3\">\n <div v-if=\"path.value == null\" class=\"text-sky-800\">\n {{'' + path.value}}\n </div>\n <div v-else>\n {{path.value}}\n </div>\n </div>\n </div>\n\n <!-- Unmatched Schema Paths (shown after matched items when searching, or all when not searching) -->\n <div\n v-for=\"path in unmatchedSchemaPaths\"\n :key=\"path.path || path\"\n class=\"value\"\n >\n <document-property\n :path=\"path\"\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :highlight=\"false\"\n :searchQuery=\"searchQuery\"\n :invalid=\"invalid\"></document-property>\n </div>\n\n <!-- Unmatched Virtual Fields (shown last) -->\n <div\n v-for=\"path in unmatchedVirtuals\"\n :key=\"path.name\"\n class=\"border border-edge rounded-lg mb-2 transition-all duration-200 ease-in-out\"\n >\n <!-- Virtual Field Header (Always Visible) -->\n <div\n @click=\"toggleVirtualField(path.name)\"\n 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\"\n >\n <div class=\"flex items-center\">\n <svg\n :class=\"isVirtualFieldCollapsed(path.name) ? 'rotate-0' : 'rotate-90'\"\n class=\"w-4 h-4 text-content-tertiary mr-2 transition-transform duration-200\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\"></path>\n </svg>\n <span class=\"font-medium text-content\">{{path.name}}</span>\n <span v-if=\"path.isVirtual\" class=\"ml-2 text-sm text-purple-600\">(virtual - {{getVirtualFieldType(path)}})</span>\n <span v-else class=\"ml-2 text-sm text-blue-600\">(user-added - {{getVirtualFieldType(path)}})</span>\n </div>\n </div>\n\n <!-- Virtual Field Content (Collapsible) -->\n <div v-if=\"!isVirtualFieldCollapsed(path.name)\" class=\"p-3\">\n <div v-if=\"path.value == null\" class=\"text-sky-800\">\n {{'' + path.value}}\n </div>\n <div v-else>\n {{path.value}}\n </div>\n </div>\n </div>\n\n <!-- No Results Message -->\n <div v-if=\"searchQuery.trim() && !hasSearchMatches\" class=\"text-center py-8 text-content-tertiary\">\n <svg class=\"w-12 h-12 mx-auto mb-4 text-gray-300\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9.172 16.172a4 4 0 015.656 0M9 12h6m-6-4h6m2 5.291A7.962 7.962 0 0112 15c-2.34 0-4.29-1.009-5.824-2.709M15 6.291A7.962 7.962 0 0012 5c-2.34 0-4.29 1.009-5.824 2.709\"></path>\n </svg>\n <p>No fields found matching \"{{searchQuery}}\"</p>\n </div>\n </div>\n\n <!-- JSON View -->\n <div v-if=\"viewMode === 'json'\" class=\"json-view\">\n <div class=\"border border-edge-strong rounded-lg bg-page p-4 overflow-auto\">\n <pre class=\"text-sm font-mono text-gray-800 whitespace-pre\">{{formattedJson}}</pre>\n </div>\n </div>\n</div>\n";
53065
53852
 
53066
53853
  /***/ },
53067
53854
 
@@ -53083,7 +53870,7 @@ module.exports = ".document-details {\n width: 100%;\n }\n \n .document-de
53083
53870
  (module) {
53084
53871
 
53085
53872
  "use strict";
53086
- module.exports = "<div class=\"border border-edge bg-surface rounded-lg mb-2\" style=\"overflow: visible;\">\n <!-- Collapsible Header -->\n <div\n @click=\"toggleCollapse\"\n 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\"\n style=\"overflow: visible; position: relative;\"\n >\n <div class=\"flex items-center\" >\n <svg\n :class=\"isCollapsed ? 'rotate-0' : 'rotate-90'\"\n class=\"w-4 h-4 text-content-tertiary mr-2 transition-transform duration-200\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\"></path>\n </svg>\n <span class=\"font-medium text-content\">\n <span\n v-for=\"(part, index) in pathNameParts\"\n :key=\"index\"\n :class=\"{ 'font-bold': part.matched }\">{{ part.text }}</span>\n </span>\n <span class=\"ml-2 text-sm text-content-tertiary\">({{(path.instance || 'unknown').toLowerCase()}})</span>\n <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\">\n <button\n type=\"button\"\n class=\"rounded-full px-2.5 py-0.5 transition\"\n :class=\"arrayDetailViewMode === 'list' ? 'bg-merlot-700 text-white shadow' : 'text-content-secondary hover:text-content'\"\n @click.stop=\"setArrayDetailViewMode('list')\">\n List\n </button>\n <button\n type=\"button\"\n class=\"rounded-full px-2.5 py-0.5 transition\"\n :class=\"arrayDetailViewMode === 'table' ? 'bg-merlot-700 text-white shadow' : 'text-content-secondary hover:text-content'\"\n @click.stop=\"setArrayDetailViewMode('table')\">\n Table\n </button>\n </div>\n <div v-if=\"isGeoJsonGeometry\" class=\"ml-3 inline-flex items-center gap-2\">\n <div class=\"inline-flex items-center rounded-full bg-gray-200 p-0.5 text-xs font-semibold\">\n <button\n type=\"button\"\n class=\"rounded-full px-2.5 py-0.5 transition\"\n :class=\"detailViewMode === 'text' ? 'bg-blue-600 text-white shadow' : 'text-content-secondary hover:text-content'\"\n :style=\"detailViewMode === 'text' ? 'color: white !important; background-color: #2563eb !important;' : ''\"\n @click.stop=\"setDetailViewMode('text')\">\n Text\n </button>\n <button\n type=\"button\"\n class=\"rounded-full px-2.5 py-0.5 transition\"\n :class=\"detailViewMode === 'map' ? 'bg-blue-600 text-white shadow' : 'text-content-secondary hover:text-content'\"\n :style=\"detailViewMode === 'map' ? 'color: white !important; background-color: #2563eb !important;' : ''\"\n @click.stop=\"setDetailViewMode('map')\">\n Map\n </button>\n </div>\n <!-- Info icon with tooltip -->\n <div v-if=\"editting\" class=\"relative inline-block\" style=\"z-index: 10002;\" @mouseenter=\"showTooltip = true\" @mouseleave=\"showTooltip = false\" @click.stop>\n <svg\n ref=\"infoIcon\"\n class=\"w-6 h-6 text-gray-400 hover:text-gray-600 cursor-help\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"></path>\n </svg>\n <div\n v-show=\"showTooltip\"\n ref=\"tooltip\"\n class=\"absolute left-full top-0 ml-2 w-64 p-3 text-white text-xs rounded-lg shadow-xl\"\n style=\"z-index: 99999; pointer-events: none; white-space: normal; position: fixed; background-color: #111827;\"\n :style=\"getTooltipStyle()\"\n >\n <div class=\"font-semibold mb-2\">Map Controls:</div>\n <div v-if=\"isGeoJsonPoint\" class=\"space-y-1\">\n <div>• Drag pin to move location</div>\n </div>\n <div v-else-if=\"isGeoJsonPolygon\" class=\"space-y-1\">\n <div>• Drag vertices to reshape polygon</div>\n <div v-if=\"isMultiPolygon\">• Right-click edge to add new vertex</div>\n <div>• Right-click vertex to delete</div>\n </div>\n <div class=\"absolute top-2 -left-1 w-0 h-0 border-t-4 border-b-4 border-r-4 border-transparent border-r-gray-900\"></div>\n </div>\n </div>\n </div>\n </div>\n <div class=\"flex items-center gap-2\">\n <date-view-mode-picker\n v-if=\"isDatePath\"\n :viewMode=\"dateViewMode\"\n :path=\"path\"\n @update:viewMode=\"dateViewMode = $event\"\n ></date-view-mode-picker>\n <objectid-view-mode-picker\n v-if=\"isObjectIdPath\"\n :viewMode=\"objectIdViewMode\"\n @update:viewMode=\"objectIdViewMode = $event\"\n ></objectid-view-mode-picker>\n <button\n type=\"button\"\n class=\"flex items-center gap-1 text-sm text-gray-600 hover:text-gray-800 px-2 py-1 rounded-md border border-transparent hover:border-edge-strong bg-surface\"\n @click.stop.prevent=\"copyPropertyValue\"\n title=\"Copy value\"\n aria-label=\"Copy property value\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 7h8m-8 4h8m-8 4h5m-7-9a2 2 0 012-2h7a2 2 0 012 2v10a2 2 0 01-2 2H8l-4-4V7a2 2 0 012-2z\" />\n </svg>\n {{copyButtonLabel}}\n </button>\n <router-link\n v-if=\"path.ref && getValueForPath(path.path)\"\n :to=\"`/model/${path.ref}/document/${getValueForPath(path.path)}`\"\n class=\"bg-primary hover:bg-primary-hover text-primary-text px-2 py-1 text-sm rounded-md\"\n @click.stop\n >View Document\n </router-link>\n </div>\n </div>\n\n <!-- Collapsible Content -->\n <div v-if=\"!isCollapsed\" class=\"p-2\">\n <!-- Date Type Selector (when editing dates) -->\n <div v-if=\"editting && path.instance === 'Date'\" class=\"mb-3 flex gap-1.5\">\n <div\n @click=\"dateType = 'picker'\"\n :class=\"dateType === 'picker' ? 'bg-teal-600' : ''\"\n class=\"self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer\">\n <div\n :class=\"dateType === 'picker' ? 'text-white' : ''\"\n class=\"text-xs font-medium font-['Lato'] capitalize leading-tight\">\n Date Picker\n </div>\n </div>\n <div\n @click=\"dateType = 'iso'\"\n :class=\"dateType === 'iso' ? 'bg-teal-600' : ''\"\n class=\"self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer\">\n <div\n :class=\"dateType === 'iso' ? 'text-white' : ''\"\n class=\"text-xs font-medium font-['Lato'] capitalize leading-tight\">\n ISO String\n </div>\n </div>\n </div>\n\n <!-- Field Content -->\n <div v-if=\"editting && path.path !== '_id'\">\n <!-- Use detail-default with map editing for GeoJSON geometries -->\n <component\n v-if=\"isGeoJsonGeometry\"\n :is=\"getComponentForPath(path)\"\n :value=\"getEditValueForPath(path)\"\n :viewMode=\"detailViewMode\"\n :on-change=\"handleInputChange\"\n >\n </component>\n <!-- Use standard edit components for other types -->\n <component\n v-else\n :is=\"getEditComponentForPath(path)\"\n :value=\"getEditValueForPath(path)\"\n :format=\"dateType\"\n v-bind=\"getEditComponentProps(path)\"\n @input=\"handleInputChange($event)\"\n @error=\"invalid[path.path] = $event;\"\n >\n </component>\n </div>\n <div v-else>\n <!-- Arrays own their truncation and array search behavior in detail-array. -->\n <component\n v-if=\"isArray\"\n :is=\"getComponentForPath(path)\"\n :value=\"rawValue\"\n :viewMode=\"propertyDetailViewMode\"\n :truncate=\"needsTruncation\"\n @updated=\"renderedValue = $event\"></component>\n <!-- Non-array truncated view -->\n <div v-else-if=\"shouldShowTruncated && !isArray\" class=\"relative\">\n <div class=\"text-content-secondary whitespace-pre-wrap break-words font-mono text-sm\">{{truncatedString}}</div>\n <button\n @click=\"toggleValueExpansion\"\n 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\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show more ({{valueAsString.length}} characters)\n </button>\n </div>\n <!-- Expanded view -->\n <div v-else-if=\"needsTruncation && isValueExpanded\" class=\"relative\">\n <component\n :is=\"getComponentForPath(path)\"\n :value=\"rawValue\"\n :viewMode=\"propertyDetailViewMode\"\n @updated=\"renderedValue = $event\"></component>\n <button\n @click=\"toggleValueExpansion\"\n 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\"\n >\n <svg class=\"w-4 h-4 rotate-180\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show less\n </button>\n </div>\n <!-- Full view (no truncation needed) -->\n <div v-else>\n <component\n :is=\"getComponentForPath(path)\"\n :value=\"rawValue\"\n :viewMode=\"propertyDetailViewMode\"\n @updated=\"renderedValue = $event\"></component>\n </div>\n </div>\n </div>\n</div>\n";
53873
+ module.exports = "<div class=\"border border-edge bg-surface rounded-lg mb-2\" style=\"overflow: visible;\">\n <!-- Collapsible Header -->\n <div\n @click=\"toggleCollapse\"\n 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\"\n style=\"overflow: visible; position: relative;\"\n >\n <div class=\"flex items-center\" >\n <svg\n :class=\"isCollapsed ? 'rotate-0' : 'rotate-90'\"\n class=\"w-4 h-4 text-content-tertiary mr-2 transition-transform duration-200\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\"></path>\n </svg>\n <span class=\"font-medium text-content\">\n <span\n v-for=\"(part, index) in pathNameParts\"\n :key=\"index\"\n :class=\"{ 'font-bold': part.matched }\">{{ part.text }}</span>\n </span>\n <span class=\"ml-2 text-sm text-content-tertiary\">({{(path.instance || 'unknown').toLowerCase()}})</span>\n <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\">\n <button\n type=\"button\"\n class=\"rounded-full px-2.5 py-0.5 transition\"\n :class=\"arrayDetailViewMode === 'list' ? 'bg-merlot-700 text-white shadow' : 'text-content-secondary hover:text-content'\"\n @click.stop=\"setArrayDetailViewMode('list')\">\n List\n </button>\n <button\n type=\"button\"\n class=\"rounded-full px-2.5 py-0.5 transition\"\n :class=\"arrayDetailViewMode === 'table' ? 'bg-merlot-700 text-white shadow' : 'text-content-secondary hover:text-content'\"\n @click.stop=\"setArrayDetailViewMode('table')\">\n Table\n </button>\n </div>\n <div v-if=\"isGeoJsonGeometry\" class=\"ml-3 inline-flex items-center gap-2\">\n <div class=\"inline-flex items-center rounded-full bg-gray-200 p-0.5 text-xs font-semibold\">\n <button\n type=\"button\"\n class=\"rounded-full px-2.5 py-0.5 transition\"\n :class=\"detailViewMode === 'text' ? 'bg-blue-600 text-white shadow' : 'text-content-secondary hover:text-content'\"\n :style=\"detailViewMode === 'text' ? 'color: white !important; background-color: #2563eb !important;' : ''\"\n @click.stop=\"setDetailViewMode('text')\">\n Text\n </button>\n <button\n type=\"button\"\n class=\"rounded-full px-2.5 py-0.5 transition\"\n :class=\"detailViewMode === 'map' ? 'bg-blue-600 text-white shadow' : 'text-content-secondary hover:text-content'\"\n :style=\"detailViewMode === 'map' ? 'color: white !important; background-color: #2563eb !important;' : ''\"\n @click.stop=\"setDetailViewMode('map')\">\n Map\n </button>\n </div>\n <!-- Info icon with tooltip -->\n <div v-if=\"editting\" class=\"relative inline-block\" style=\"z-index: 10002;\" @mouseenter=\"showTooltip = true\" @mouseleave=\"showTooltip = false\" @click.stop>\n <svg\n ref=\"infoIcon\"\n class=\"w-6 h-6 text-gray-400 hover:text-gray-600 cursor-help\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"></path>\n </svg>\n <div\n v-show=\"showTooltip\"\n ref=\"tooltip\"\n class=\"absolute left-full top-0 ml-2 w-64 p-3 text-white text-xs rounded-lg shadow-xl\"\n style=\"z-index: 99999; pointer-events: none; white-space: normal; position: fixed; background-color: #111827;\"\n :style=\"getTooltipStyle()\"\n >\n <div class=\"font-semibold mb-2\">Map Controls:</div>\n <div v-if=\"isGeoJsonPoint\" class=\"space-y-1\">\n <div>• Drag pin to move location</div>\n </div>\n <div v-else-if=\"isGeoJsonPolygon\" class=\"space-y-1\">\n <div>• Drag vertices to reshape polygon</div>\n <div v-if=\"isMultiPolygon\">• Right-click edge to add new vertex</div>\n <div>• Right-click vertex to delete</div>\n </div>\n <div class=\"absolute top-2 -left-1 w-0 h-0 border-t-4 border-b-4 border-r-4 border-transparent border-r-gray-900\"></div>\n </div>\n </div>\n </div>\n </div>\n <div class=\"flex items-center gap-2\">\n <date-view-mode-picker\n v-if=\"isDatePath\"\n :viewMode=\"dateViewMode\"\n :path=\"path\"\n :editting=\"editting\"\n @update:viewMode=\"dateViewMode = $event\"\n ></date-view-mode-picker>\n <objectid-view-mode-picker\n v-if=\"isObjectIdPath\"\n :viewMode=\"objectIdViewMode\"\n @update:viewMode=\"objectIdViewMode = $event\"\n ></objectid-view-mode-picker>\n <button\n type=\"button\"\n class=\"flex items-center gap-1 text-sm text-gray-600 hover:text-gray-800 px-2 py-1 rounded-md border border-transparent hover:border-edge-strong bg-surface\"\n @click.stop.prevent=\"copyPropertyValue\"\n title=\"Copy value\"\n aria-label=\"Copy property value\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 7h8m-8 4h8m-8 4h5m-7-9a2 2 0 012-2h7a2 2 0 012 2v10a2 2 0 01-2 2H8l-4-4V7a2 2 0 012-2z\" />\n </svg>\n {{copyButtonLabel}}\n </button>\n <router-link\n v-if=\"path.ref && getValueForPath(path.path)\"\n :to=\"`/model/${path.ref}/document/${getValueForPath(path.path)}`\"\n class=\"bg-primary hover:bg-primary-hover text-primary-text px-2 py-1 text-sm rounded-md\"\n @click.stop\n >View Document\n </router-link>\n </div>\n </div>\n\n <!-- Collapsible Content -->\n <div v-if=\"!isCollapsed\" class=\"p-2\">\n <!-- Date Type Selector (when editing dates) -->\n <div v-if=\"editting && path.instance === 'Date'\" class=\"mb-3 flex gap-1.5\">\n <div\n @click=\"dateType = 'picker'\"\n :class=\"dateType === 'picker' ? 'bg-merlot-700' : ''\"\n class=\"self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer\">\n <div\n :class=\"dateType === 'picker' ? 'text-white' : ''\"\n class=\"text-xs font-medium font-['Lato'] capitalize leading-tight\">\n Date Picker\n </div>\n </div>\n <div\n @click=\"dateType = 'iso'\"\n :class=\"dateType === 'iso' ? 'bg-merlot-700' : ''\"\n class=\"self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer\">\n <div\n :class=\"dateType === 'iso' ? 'text-white' : ''\"\n class=\"text-xs font-medium font-['Lato'] capitalize leading-tight\">\n ISO String\n </div>\n </div>\n </div>\n\n <!-- Field Content -->\n <div v-if=\"editting && path.path !== '_id'\">\n <!-- Use detail-default with map editing for GeoJSON geometries -->\n <component\n v-if=\"isGeoJsonGeometry\"\n :is=\"getComponentForPath(path)\"\n :value=\"getEditValueForPath(path)\"\n :viewMode=\"detailViewMode\"\n :on-change=\"handleInputChange\"\n >\n </component>\n <!-- Use standard edit components for other types -->\n <component\n v-else\n :is=\"getEditComponentForPath(path)\"\n :value=\"getEditValueForPath(path)\"\n :format=\"dateType\"\n v-bind=\"getEditComponentProps(path)\"\n @input=\"handleInputChange($event)\"\n @error=\"invalid[path.path] = $event;\"\n >\n </component>\n </div>\n <div v-else>\n <!-- Arrays own their truncation and array search behavior in detail-array. -->\n <component\n v-if=\"isArray\"\n :is=\"getComponentForPath(path)\"\n :value=\"rawValue\"\n :viewMode=\"propertyDetailViewMode\"\n :truncate=\"needsTruncation\"\n @updated=\"renderedValue = $event\"></component>\n <!-- Non-array truncated view -->\n <div v-else-if=\"shouldShowTruncated && !isArray\" class=\"relative\">\n <div class=\"text-content-secondary whitespace-pre-wrap break-words font-mono text-sm\">{{truncatedString}}</div>\n <button\n @click=\"toggleValueExpansion\"\n 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\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show more ({{valueAsString.length}} characters)\n </button>\n </div>\n <!-- Expanded view -->\n <div v-else-if=\"needsTruncation && isValueExpanded\" class=\"relative\">\n <component\n :is=\"getComponentForPath(path)\"\n :value=\"rawValue\"\n :viewMode=\"propertyDetailViewMode\"\n @updated=\"renderedValue = $event\"></component>\n <button\n @click=\"toggleValueExpansion\"\n 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\"\n >\n <svg class=\"w-4 h-4 rotate-180\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show less\n </button>\n </div>\n <!-- Full view (no truncation needed) -->\n <div v-else>\n <component\n :is=\"getComponentForPath(path)\"\n :value=\"rawValue\"\n :viewMode=\"propertyDetailViewMode\"\n @updated=\"renderedValue = $event\"></component>\n </div>\n </div>\n </div>\n</div>\n";
53087
53874
 
53088
53875
  /***/ },
53089
53876
 
@@ -53127,7 +53914,7 @@ module.exports = "<div>\n <h2>\n Are you sure you want to delete the follo
53127
53914
  (module) {
53128
53915
 
53129
53916
  "use strict";
53130
- module.exports = ".document .document-menu {\n display: flex;\n position: sticky;\n top: 0;\n z-index: 100;\n background-color: var(--studio-bg-surface);\n border-radius: 5px;\n padding: 15px 15px;\n margin: -15px 0 15px 0;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n}\n\n.document .document-menu .left {\n flex-grow: 1;\n}\n\n.document .document-menu .right {\n flex-grow: 1;\n text-align: right;\n}\n\n.document .document-menu .right button:not(:last-child) {\n margin-right: 0.5em;\n}\n\n.document button img {\n height: 1em;\n}\n";
53917
+ module.exports = ".document .document-menu {\n display: flex;\n position: sticky;\n top: 0;\n z-index: 100;\n background-color: var(--studio-bg-surface);\n border-radius: 5px;\n padding: 15px 15px;\n margin: -15px 0 15px 0;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n}\n\n.document .document-menu .left {\n flex-grow: 1;\n}\n\n.document .document-menu .right {\n flex-grow: 1;\n text-align: right;\n}\n\n.document .document-menu .right button:not(:last-child) {\n margin-right: 0.5em;\n}\n\n.document button img {\n height: 1em;\n}\n\n/* Add Field Modal Styles */\n.add-field-modal {\n max-width: 500px;\n width: 100%;\n}\n\n.add-field-modal .modal-exit {\n position: absolute;\n top: 15px;\n right: 20px;\n font-size: 24px;\n cursor: pointer;\n color: #6b7280;\n z-index: 10;\n}\n\n.add-field-modal .modal-exit:hover {\n color: #374151;\n}\n\n.add-field-modal form {\n max-height: 70vh;\n overflow-y: auto;\n}\n\n.add-field-modal input[type=\"text\"],\n.add-field-modal input[type=\"email\"],\n.add-field-modal input[type=\"password\"],\n.add-field-modal select,\n.add-field-modal textarea {\n transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n.add-field-modal input:focus,\n.add-field-modal select:focus,\n.add-field-modal textarea:focus {\n border-color: #3b82f6;\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);\n}\n\n.add-field-modal .border-red-500 {\n border-color: #ef4444 !important;\n}\n\n.add-field-modal .border-red-500:focus {\n box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1) !important;\n}\n\n/* Ace editor styling in modal */\n.add-field-modal .add-field-ace-editor.ace_editor {\n border: 1px solid #d1d5db;\n border-radius: 0.375rem;\n font-size: 14px;\n min-height: 100px;\n}\n\n.add-field-modal .add-field-ace-editor:focus-within.ace_editor,\n.add-field-modal .add-field-ace-editor.ace_focus {\n border-color: #3b82f6;\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);\n}\n";
53131
53918
 
53132
53919
  /***/ },
53133
53920
 
@@ -53138,7 +53925,7 @@ module.exports = ".document .document-menu {\n display: flex;\n position: stic
53138
53925
  (module) {
53139
53926
 
53140
53927
  "use strict";
53141
- module.exports = "<div class=\"document px-1 pt-4 pb-16 md:px-0 bg-slate-50 w-full\">\n <div class=\"max-w-7xl mx-auto\">\n <div class=\"flex gap-4 items-center sticky top-0 z-50 bg-surface p-4 border-b border-edge shadow-sm\">\n <div class=\"font-bold overflow-hidden text-ellipsis\">{{model}}: {{documentId}}</div>\n <div class=\"flex grow\">\n <button\n @click=\"viewMode = 'fields'\"\n :class=\"viewMode === 'fields'\n ? 'bg-primary text-white z-10'\n : 'bg-gray-200 text-content-secondary hover:bg-gray-300'\"\n class=\"px-2 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-edge-strong border-r-0 rounded-l-lg rounded-r-none\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 6h16M4 10h16M4 14h16M4 18h16\"></path>\n </svg>\n Fields\n </button>\n <button\n @click=\"viewMode = 'json'\"\n :class=\"viewMode === 'json'\n ? 'bg-primary text-white z-10'\n : 'bg-gray-200 text-content-secondary hover:bg-gray-300'\"\n class=\"px-2 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-edge-strong rounded-r-lg rounded-l-none\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4\"></path>\n </svg>\n JSON\n </button>\n </div>\n\n <div class=\"hidden md:flex items-center gap-3 text-sm text-slate-600\">\n <div class=\"text-right leading-tight\">\n <div class=\"text-xs uppercase tracking-wide text-slate-400 flex items-center gap-2 justify-end\">\n <span>Loaded at</span>\n <span class=\"inline-flex items-center gap-1 text-[10px] font-semibold\" :class=\"autoRefreshEnabled ? 'text-forest-green-600' : 'text-slate-400'\">\n <span class=\"inline-block h-1.5 w-1.5 rounded-full\" :class=\"autoRefreshEnabled ? 'bg-forest-green-500' : 'bg-slate-300'\"></span>\n </span>\n </div>\n <div class=\"font-medium text-slate-700\">{{lastUpdatedLabel}}</div>\n </div>\n </div>\n\n <div class=\"gap-2 hidden md:flex items-center\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true\"\n :disabled=\"!canEdit\"\n :class=\"{'cursor-not-allowed opacity-50': !canEdit}\"\n type=\"button\"\n class=\"rounded-md bg-primary px-2.5 py-1.5 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n <img src=\"images/edit.svg\" class=\"inline\" /> Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false\"\n type=\"button\"\n class=\"rounded-md bg-slate-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-slate-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-600\">\n &times; Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n @click=\"shouldShowConfirmModal=true;\"\n type=\"button\"\n class=\"rounded-md bg-forest-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\">\n <img src=\"images/save.svg\" class=\"inline\" /> Save\n </button>\n\n <!-- 3-dot menu -->\n <div class=\"relative\">\n <button\n @click=\"desktopMenuOpen = !desktopMenuOpen\"\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded-md bg-gray-200 px-2.5 py-1.5 text-sm font-medium text-content-secondary hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500\"\n aria-expanded=\"desktopMenuOpen\"\n aria-label=\"More options\"\n >\n <svg class=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <circle cx=\"12\" cy=\"5\" r=\"2\"></circle>\n <circle cx=\"12\" cy=\"12\" r=\"2\"></circle>\n <circle cx=\"12\" cy=\"19\" r=\"2\"></circle>\n </svg>\n </button>\n <div\n v-show=\"desktopMenuOpen\"\n @click.away=\"desktopMenuOpen = false\"\n class=\"origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg bg-surface ring-1 ring-black ring-opacity-5 z-50\"\n >\n <div class=\"py-1 flex flex-col\">\n <button\n @click=\"refreshDocument({ force: true, source: 'manual' }); desktopMenuOpen = false\"\n :disabled=\"isRefreshing\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', isRefreshing ? 'cursor-not-allowed opacity-50' : 'hover:bg-slate-100']\"\n type=\"button\"\n >\n {{isRefreshing ? 'Refreshing...' : 'Refresh'}}\n </button>\n <button\n @click=\"toggleAutoRefresh(); desktopMenuOpen = false\"\n :disabled=\"isLambda\"\n type=\"button\"\n :class=\"['flex items-center px-4 py-2 text-sm', isLambda ? 'text-gray-400 cursor-not-allowed' : 'text-content-secondary hover:bg-slate-100']\"\n :title=\"isLambda ? 'Auto-refresh only available on Express deployments' : ''\"\n >\n {{autoRefreshEnabled ? 'Disable Auto-Refresh' : 'Enable Auto-Refresh'}}\n </button>\n <button\n @click=\"addField(); desktopMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-green-100\"\n >\n Add Field\n </button>\n <button\n @click=\"copyDocument(); desktopMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-blue-100\"\n >\n Copy Document\n </button>\n <button\n @click=\"openValidationModal()\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-green-100\"\n >\n Validate\n </button>\n <button\n @click=\"openScriptDrawer()\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-indigo-100\"\n >\n Run Script\n </button>\n <button\n @click=\"shouldShowDeleteModal=true; desktopMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']\"\n type=\"button\"\n >\n Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true; desktopMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']\"\n type=\"button\"\n >\n Clone\n </button>\n </div>\n </div>\n </div>\n </div>\n <div class=\"md:hidden flex items-center\">\n <div class=\"relative\">\n <button\n @click=\"mobileMenuOpen = !mobileMenuOpen\"\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded-md bg-gray-200 px-3 py-2 text-sm font-medium text-content-secondary hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500\"\n aria-expanded=\"mobileMenuOpen\"\n aria-label=\"Open menu\"\n >\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"\n d=\"M4 6h16M4 12h16M4 18h16\"></path>\n </svg>\n </button>\n <div\n v-show=\"mobileMenuOpen\"\n @click.away=\"mobileMenuOpen = false\"\n class=\"origin-top-right absolute right-0 mt-2 w-52 rounded-md shadow-lg bg-surface ring-1 ring-black ring-opacity-5 z-50\"\n >\n <div class=\"py-1 flex flex-col\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true; mobileMenuOpen = false\"\n :disabled=\"!canEdit\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', !canEdit ? 'cursor-not-allowed opacity-50' : 'hover:bg-primary-subtle']\"\n type=\"button\"\n >\n Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false; mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-slate-100\"\n >\n Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-green-100']\"\n @click=\"shouldShowConfirmModal=true; mobileMenuOpen = false\"\n type=\"button\"\n >\n Save\n </button>\n <button\n @click=\"refreshDocument({ force: true, source: 'manual' }); mobileMenuOpen = false\"\n :disabled=\"isRefreshing\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', isRefreshing ? 'cursor-not-allowed opacity-50' : 'hover:bg-slate-100']\"\n type=\"button\"\n >\n {{isRefreshing ? 'Refreshing...' : 'Refresh'}}\n </button>\n <button\n @click=\"toggleAutoRefresh(); mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-slate-100\"\n >\n Auto-Refresh {{autoRefreshEnabled ? 'ON' : 'OFF'}}\n </button>\n <button\n @click=\"addField(); mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-green-100\"\n >\n Add Field\n </button>\n <button\n @click=\"copyDocument(); mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-blue-100\"\n >\n Copy Document\n </button>\n <button\n @click=\"openValidationModal()\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-green-100\"\n >\n Validate\n </button>\n <button\n @click=\"openScriptDrawer()\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-indigo-100\"\n >\n Run Script\n </button>\n <button\n @click=\"shouldShowDeleteModal=true; mobileMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']\"\n type=\"button\"\n >\n Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true; mobileMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']\"\n type=\"button\"\n >\n Clone\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n <div v-if=\"status === 'loaded'\">\n <document-details\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :virtualPaths=\"virtualPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :invalid=\"invalid\"\n :viewMode=\"viewMode\"\n :model=\"model\"\n @add-field=\"addField\"\n @view-mode-change=\"updateViewMode\"></document-details>\n <modal v-if=\"shouldShowConfirmModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowConfirmModal = false;\">&times;</div>\n <confirm-changes @close=\"shouldShowConfirmModal = false;\" @save=\"save\" :value=\"changes\"></confirm-changes>\n </template>\n </modal>\n <modal v-if=\"shouldShowDeleteModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDeleteModal = false;\">&times;</div>\n <confirm-delete @close=\"shouldShowDeleteModal = false;\" @remove=\"remove\" :value=\"document\"></confirm-delete>\n </template>\n </modal>\n <modal v-if=\"shouldShowCloneModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCloneModal = false;\">&times;</div>\n <clone-document :currentModel=\"model\" :doc=\"document\" :schemaPaths=\"schemaPaths\" @close=\"showClonedDocument\"></clone-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowValidationModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowValidationModal = false;\">&times;</div>\n <div class=\"text-xl font-bold mb-2\">Validate Document</div>\n <div v-if=\"!validationResult\" class=\"space-y-3\">\n <div class=\"h-4 w-32 animate-pulse rounded bg-muted\"></div>\n <div class=\"h-20 w-full animate-pulse rounded bg-muted\"></div>\n </div>\n <div v-else class=\"space-y-3\">\n <div\n class=\"rounded px-3 py-2 text-sm font-semibold\"\n :class=\"validationResult.valid ? 'bg-green-50 text-green-700' : 'bg-red-50 text-red-700'\"\n >\n {{ validationResult.valid ? 'Document is valid' : 'Document is invalid' }}\n </div>\n <div v-if=\"!validationResult.valid\" class=\"rounded border border-edge bg-muted p-3 text-sm text-content-secondary\">\n {{ validationResult.error }}\n </div>\n <div v-if=\"validationResult?.errors && Object.keys(validationResult.errors).length > 0\" class=\"rounded border border-edge\">\n <div class=\"border-b border-edge px-3 py-2 text-sm font-semibold text-content-secondary\">Invalid Paths</div>\n <div class=\"max-h-72 overflow-auto\">\n <div\n v-for=\"[path, pathError] in Object.entries(validationResult.errors)\"\n :key=\"path\"\n 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]\"\n >\n <div class=\"font-mono text-content\">{{ path }}</div>\n <div class=\"text-content-secondary\">\n <div>{{ pathError.message }}</div>\n <div v-if=\"pathError.kind\" class=\"mt-1 text-xs text-content-tertiary\">Kind: {{ pathError.kind }}</div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </template>\n </modal>\n </div>\n <execute-script\n :visible=\"scriptDrawerOpen\"\n :model=\"model\"\n :document-id=\"documentId\"\n :editting=\"editting\"\n @close=\"closeScriptDrawer\"\n @refresh=\"handleScriptRefresh\"\n ></execute-script>\n <div\n v-if=\"keyboardShortcuts.length > 0\"\n class=\"fixed bottom-4 left-4 z-40 group\"\n >\n <button\n type=\"button\"\n aria-label=\"Keyboard shortcuts\"\n title=\"Keyboard shortcuts\"\n class=\"rounded-full border border-slate-300 bg-surface p-2 text-slate-700 shadow-sm transition-colors hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" aria-hidden=\"true\">\n <rect x=\"2.5\" y=\"6.5\" width=\"19\" height=\"11\" rx=\"2\" stroke-width=\"1.5\"></rect>\n <path d=\"M6 10.5h1.5M9 10.5h1.5M12 10.5h1.5M15 10.5h1.5M18 10.5h.01M6 13.5h1.5M9 13.5h6M16.5 13.5h1.5\" stroke-width=\"1.5\" stroke-linecap=\"round\"></path>\n </svg>\n </button>\n <div class=\"pointer-events-none absolute left-0 bottom-full mb-2 min-w-[220px] translate-y-1 rounded-lg bg-slate-900 px-3 py-2.5 text-slate-200 opacity-0 shadow-xl transition-all duration-150 group-hover:pointer-events-auto group-hover:translate-y-0 group-hover:opacity-100 group-focus-within:pointer-events-auto group-focus-within:translate-y-0 group-focus-within:opacity-100\">\n <div\n v-for=\"shortcut in keyboardShortcuts\"\n :key=\"shortcut.command + shortcut.description\"\n class=\"flex items-center justify-between gap-3 text-xs\"\n >\n <span class=\"font-mono font-semibold text-slate-50\">{{shortcut.command}}</span>\n <span class=\"text-slate-300\">{{shortcut.description}}</span>\n </div>\n </div>\n </div>\n </div>\n</div>\n";
53928
+ module.exports = "<div class=\"document px-1 pt-4 pb-16 md:px-0 bg-slate-50 w-full\">\n <div class=\"max-w-7xl mx-auto\">\n <div class=\"flex gap-4 items-center sticky top-0 z-50 bg-surface p-4 border-b border-edge shadow-sm\">\n <div class=\"font-bold overflow-hidden text-ellipsis\">{{model}}: {{documentId}}</div>\n <div class=\"flex grow\">\n <button\n @click=\"viewMode = 'fields'\"\n :class=\"viewMode === 'fields'\n ? 'bg-primary text-white z-10'\n : 'bg-gray-200 text-content-secondary hover:bg-gray-300'\"\n class=\"px-2 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-edge-strong border-r-0 rounded-l-lg rounded-r-none\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 6h16M4 10h16M4 14h16M4 18h16\"></path>\n </svg>\n Fields\n </button>\n <button\n @click=\"viewMode = 'json'\"\n :class=\"viewMode === 'json'\n ? 'bg-primary text-white z-10'\n : 'bg-gray-200 text-content-secondary hover:bg-gray-300'\"\n class=\"px-2 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-edge-strong rounded-r-lg rounded-l-none\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4\"></path>\n </svg>\n JSON\n </button>\n </div>\n\n <div class=\"hidden md:flex items-center gap-3 text-sm text-slate-600\">\n <div class=\"text-right leading-tight\">\n <div class=\"text-xs uppercase tracking-wide text-slate-400 flex items-center gap-2 justify-end\">\n <span>Loaded at</span>\n <span class=\"inline-flex items-center gap-1 text-[10px] font-semibold\" :class=\"autoRefreshEnabled ? 'text-forest-green-600' : 'text-slate-400'\">\n <span class=\"inline-block h-1.5 w-1.5 rounded-full\" :class=\"autoRefreshEnabled ? 'bg-forest-green-500' : 'bg-slate-300'\"></span>\n </span>\n </div>\n <div class=\"font-medium text-slate-700\">{{lastUpdatedLabel}}</div>\n </div>\n </div>\n\n <div class=\"gap-2 hidden md:flex items-center\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true\"\n :disabled=\"!canEdit\"\n :class=\"{'cursor-not-allowed opacity-50': !canEdit}\"\n type=\"button\"\n class=\"rounded-md bg-primary px-2.5 py-1.5 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n <img src=\"images/edit.svg\" class=\"inline\" /> Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false\"\n type=\"button\"\n class=\"rounded-md bg-slate-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-slate-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-600\">\n &times; Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n @click=\"shouldShowConfirmModal=true;\"\n type=\"button\"\n class=\"rounded-md bg-forest-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\">\n <img src=\"images/save.svg\" class=\"inline\" /> Save\n </button>\n\n <!-- 3-dot menu -->\n <div class=\"relative\">\n <button\n @click=\"desktopMenuOpen = !desktopMenuOpen\"\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded-md bg-gray-200 px-2.5 py-1.5 text-sm font-medium text-content-secondary hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500\"\n aria-expanded=\"desktopMenuOpen\"\n aria-label=\"More options\"\n >\n <svg class=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <circle cx=\"12\" cy=\"5\" r=\"2\"></circle>\n <circle cx=\"12\" cy=\"12\" r=\"2\"></circle>\n <circle cx=\"12\" cy=\"19\" r=\"2\"></circle>\n </svg>\n </button>\n <div\n v-show=\"desktopMenuOpen\"\n @click.away=\"desktopMenuOpen = false\"\n class=\"origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg bg-surface ring-1 ring-black ring-opacity-5 z-50\"\n >\n <div class=\"py-1 flex flex-col\">\n <button\n @click=\"refreshDocument({ force: true, source: 'manual' }); desktopMenuOpen = false\"\n :disabled=\"isRefreshing\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', isRefreshing ? 'cursor-not-allowed opacity-50' : 'hover:bg-slate-100']\"\n type=\"button\"\n >\n {{isRefreshing ? 'Refreshing...' : 'Refresh'}}\n </button>\n <button\n @click=\"toggleAutoRefresh(); desktopMenuOpen = false\"\n :disabled=\"isLambda\"\n type=\"button\"\n :class=\"['flex items-center px-4 py-2 text-sm', isLambda ? 'text-gray-400 cursor-not-allowed' : 'text-content-secondary hover:bg-slate-100']\"\n :title=\"isLambda ? 'Auto-refresh only available on Express deployments' : ''\"\n >\n {{autoRefreshEnabled ? 'Disable Auto-Refresh' : 'Enable Auto-Refresh'}}\n </button>\n <button\n @click=\"addField(); desktopMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-green-100\"\n >\n Add Field\n </button>\n <button\n @click=\"copyDocument(); desktopMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-blue-100\"\n >\n Copy Document\n </button>\n <button\n @click=\"openValidationModal()\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-green-100\"\n >\n Validate\n </button>\n <button\n @click=\"openScriptDrawer()\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-indigo-100\"\n >\n Run Script\n </button>\n <button\n @click=\"shouldShowDeleteModal=true; desktopMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']\"\n type=\"button\"\n >\n Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true; desktopMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']\"\n type=\"button\"\n >\n Clone\n </button>\n </div>\n </div>\n </div>\n </div>\n <div class=\"md:hidden flex items-center\">\n <div class=\"relative\">\n <button\n @click=\"mobileMenuOpen = !mobileMenuOpen\"\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded-md bg-gray-200 px-3 py-2 text-sm font-medium text-content-secondary hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500\"\n aria-expanded=\"mobileMenuOpen\"\n aria-label=\"Open menu\"\n >\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"\n d=\"M4 6h16M4 12h16M4 18h16\"></path>\n </svg>\n </button>\n <div\n v-show=\"mobileMenuOpen\"\n @click.away=\"mobileMenuOpen = false\"\n class=\"origin-top-right absolute right-0 mt-2 w-52 rounded-md shadow-lg bg-surface ring-1 ring-black ring-opacity-5 z-50\"\n >\n <div class=\"py-1 flex flex-col\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true; mobileMenuOpen = false\"\n :disabled=\"!canEdit\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', !canEdit ? 'cursor-not-allowed opacity-50' : 'hover:bg-primary-subtle']\"\n type=\"button\"\n >\n Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false; mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-slate-100\"\n >\n Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-green-100']\"\n @click=\"shouldShowConfirmModal=true; mobileMenuOpen = false\"\n type=\"button\"\n >\n Save\n </button>\n <button\n @click=\"refreshDocument({ force: true, source: 'manual' }); mobileMenuOpen = false\"\n :disabled=\"isRefreshing\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', isRefreshing ? 'cursor-not-allowed opacity-50' : 'hover:bg-slate-100']\"\n type=\"button\"\n >\n {{isRefreshing ? 'Refreshing...' : 'Refresh'}}\n </button>\n <button\n @click=\"toggleAutoRefresh(); mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-slate-100\"\n >\n Auto-Refresh {{autoRefreshEnabled ? 'ON' : 'OFF'}}\n </button>\n <button\n @click=\"addField(); mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-green-100\"\n >\n Add Field\n </button>\n <button\n @click=\"copyDocument(); mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-blue-100\"\n >\n Copy Document\n </button>\n <button\n @click=\"openValidationModal()\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-green-100\"\n >\n Validate\n </button>\n <button\n @click=\"openScriptDrawer()\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-content-secondary hover:bg-indigo-100\"\n >\n Run Script\n </button>\n <button\n @click=\"shouldShowDeleteModal=true; mobileMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']\"\n type=\"button\"\n >\n Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true; mobileMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-content-secondary', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']\"\n type=\"button\"\n >\n Clone\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n <div v-if=\"status === 'loaded'\">\n <document-details\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :virtualPaths=\"virtualPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :invalid=\"invalid\"\n :viewMode=\"viewMode\"\n @view-mode-change=\"updateViewMode\"></document-details>\n <modal v-if=\"showAddFieldModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"closeAddFieldModal\">&times;</div>\n <div class=\"add-field-modal\">\n <div class=\"mb-6\">\n <h2 class=\"text-xl font-semibold text-content mb-2\">Add New Field</h2>\n <p class=\"text-sm text-gray-600\">Create a new field for this document</p>\n </div>\n\n <form @submit.prevent=\"handleAddFieldSubmit\" class=\"space-y-4\">\n <div>\n <label for=\"fieldName\" class=\"block text-sm font-medium text-content-secondary mb-1\">\n Field Name *\n </label>\n <input\n id=\"fieldName\"\n v-model=\"fieldData.name\"\n type=\"text\"\n required\n placeholder=\"Enter field name...\"\n class=\"w-full px-3 py-2 border border-edge-strong rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.name }\"\n />\n <p v-if=\"fieldErrors.name\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.name}}</p>\n <p v-if=\"fieldData.name && getTransformedFieldName() !== fieldData.name.trim()\" class=\"mt-1 text-sm text-blue-600\">\n Field name will be: <code class=\"bg-blue-50 px-1 py-0.5 rounded text-xs\">{{getTransformedFieldName()}}</code>\n </p>\n </div>\n\n <div>\n <label for=\"fieldType\" class=\"block text-sm font-medium text-content-secondary mb-1\">\n Field Type *\n </label>\n <select\n id=\"fieldType\"\n v-model=\"fieldData.type\"\n required\n class=\"w-full px-3 py-2 border border-edge-strong rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.type }\"\n >\n <option value=\"\">Select field type...</option>\n <option v-for=\"type in allFieldTypes\" :key=\"type\" :value=\"type\">\n {{type}}\n </option>\n </select>\n <p v-if=\"fieldErrors.type\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.type}}</p>\n </div>\n\n <div>\n <label for=\"fieldValue\" class=\"block text-sm font-medium text-content-secondary mb-1\">\n Initial Value\n </label>\n\n <custom-date-picker\n v-if=\"shouldUseDatePicker\"\n id=\"fieldValue\"\n mode=\"date\"\n :value=\"fieldData.value\"\n :class=\"{ 'border-red-500': fieldErrors.value, 'max-w-[400px]': true }\"\n :horizontal=\"true\"\n @input=\"fieldData.value = $event\"\n />\n\n <input\n v-else-if=\"!shouldUseAce\"\n v-model=\"fieldData.value\"\n type=\"text\"\n id=\"fieldValue\"\n placeholder=\"Enter initial value (optional)...\"\n class=\"w-full px-3 py-2 border border-edge-strong rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.value }\"\n />\n\n <ace-editor\n v-else\n id=\"fieldValue\"\n v-model=\"fieldData.value\"\n mode=\"json\"\n :line-numbers=\"true\"\n :min-lines=\"3\"\n class=\"add-field-ace-editor w-full min-h-[100px]\"\n :class=\"{ 'border-red-500': fieldErrors.value }\"\n />\n\n <p v-if=\"fieldErrors.value\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.value}}</p>\n <p class=\"mt-1 text-xs text-content-tertiary\">\n <span v-if=\"shouldUseDatePicker\">Select a date or leave empty for null/undefined values.</span>\n <span v-else-if=\"shouldUseAce\">Leave empty for null/undefined values. Use valid JSON format.</span>\n <span v-else>Leave empty for null/undefined values.</span>\n </p>\n </div>\n\n <div class=\"flex justify-end gap-3 pt-4 border-t border-edge\">\n <button\n type=\"button\"\n @click=\"closeAddFieldModal\"\n class=\"px-4 py-2 text-sm font-medium text-content-secondary bg-surface border border-edge-strong rounded-md hover:bg-page focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n :disabled=\"isSubmittingField\"\n class=\"px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <span v-if=\"isSubmittingField\">Adding...</span>\n <span v-else>Add Field</span>\n </button>\n </div>\n </form>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowConfirmModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowConfirmModal = false;\">&times;</div>\n <confirm-changes @close=\"shouldShowConfirmModal = false;\" @save=\"save\" :value=\"changes\"></confirm-changes>\n </template>\n </modal>\n <modal v-if=\"shouldShowDeleteModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDeleteModal = false;\">&times;</div>\n <confirm-delete @close=\"shouldShowDeleteModal = false;\" @remove=\"remove\" :value=\"document\"></confirm-delete>\n </template>\n </modal>\n <modal v-if=\"shouldShowCloneModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCloneModal = false;\">&times;</div>\n <clone-document :currentModel=\"model\" :doc=\"document\" :schemaPaths=\"schemaPaths\" @close=\"showClonedDocument\"></clone-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowValidationModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowValidationModal = false;\">&times;</div>\n <div class=\"text-xl font-bold mb-2\">Validate Document</div>\n <div v-if=\"!validationResult\" class=\"space-y-3\">\n <div class=\"h-4 w-32 animate-pulse rounded bg-muted\"></div>\n <div class=\"h-20 w-full animate-pulse rounded bg-muted\"></div>\n </div>\n <div v-else class=\"space-y-3\">\n <div\n class=\"rounded px-3 py-2 text-sm font-semibold\"\n :class=\"validationResult.valid ? 'bg-green-50 text-green-700' : 'bg-red-50 text-red-700'\"\n >\n {{ validationResult.valid ? 'Document is valid' : 'Document is invalid' }}\n </div>\n <div v-if=\"!validationResult.valid\" class=\"rounded border border-edge bg-muted p-3 text-sm text-content-secondary\">\n {{ validationResult.error }}\n </div>\n <div v-if=\"validationResult?.errors && Object.keys(validationResult.errors).length > 0\" class=\"rounded border border-edge\">\n <div class=\"border-b border-edge px-3 py-2 text-sm font-semibold text-content-secondary\">Invalid Paths</div>\n <div class=\"max-h-72 overflow-auto\">\n <div\n v-for=\"[path, pathError] in Object.entries(validationResult.errors)\"\n :key=\"path\"\n 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]\"\n >\n <div class=\"font-mono text-content\">{{ path }}</div>\n <div class=\"text-content-secondary\">\n <div>{{ pathError.message }}</div>\n <div v-if=\"pathError.kind\" class=\"mt-1 text-xs text-content-tertiary\">Kind: {{ pathError.kind }}</div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </template>\n </modal>\n </div>\n <execute-script\n :visible=\"scriptDrawerOpen\"\n :model=\"model\"\n :document-id=\"documentId\"\n :editting=\"editting\"\n @close=\"closeScriptDrawer\"\n @refresh=\"handleScriptRefresh\"\n ></execute-script>\n <div\n v-if=\"keyboardShortcuts.length > 0\"\n class=\"fixed bottom-4 left-4 z-40 group\"\n >\n <button\n type=\"button\"\n aria-label=\"Keyboard shortcuts\"\n title=\"Keyboard shortcuts\"\n class=\"rounded-full border border-slate-300 bg-surface p-2 text-slate-700 shadow-sm transition-colors hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" aria-hidden=\"true\">\n <rect x=\"2.5\" y=\"6.5\" width=\"19\" height=\"11\" rx=\"2\" stroke-width=\"1.5\"></rect>\n <path d=\"M6 10.5h1.5M9 10.5h1.5M12 10.5h1.5M15 10.5h1.5M18 10.5h.01M6 13.5h1.5M9 13.5h6M16.5 13.5h1.5\" stroke-width=\"1.5\" stroke-linecap=\"round\"></path>\n </svg>\n </button>\n <div class=\"pointer-events-none absolute left-0 bottom-full mb-2 min-w-[220px] translate-y-1 rounded-lg bg-slate-900 px-3 py-2.5 text-slate-200 opacity-0 shadow-xl transition-all duration-150 group-hover:pointer-events-auto group-hover:translate-y-0 group-hover:opacity-100 group-focus-within:pointer-events-auto group-focus-within:translate-y-0 group-focus-within:opacity-100\">\n <div\n v-for=\"shortcut in keyboardShortcuts\"\n :key=\"shortcut.command + shortcut.description\"\n class=\"flex items-center justify-between gap-3 text-xs\"\n >\n <span class=\"font-mono font-semibold text-slate-50\">{{shortcut.command}}</span>\n <span class=\"text-slate-300\">{{shortcut.description}}</span>\n </div>\n </div>\n </div>\n </div>\n</div>\n";
53142
53929
 
53143
53930
  /***/ },
53144
53931
 
@@ -53193,7 +53980,7 @@ module.exports = "<div class=\"edit-boolean\">\n <div class=\"flex flex-wrap ga
53193
53980
  (module) {
53194
53981
 
53195
53982
  "use strict";
53196
- module.exports = "<div>\n <input\n v-if=\"dateSelection == 'picker'\"\n class=\"w-64 h-8 border border-edge-strong outline-0\"\n type=\"datetime-local\"\n :value=\"valueAsLocalString\"\n @input=\"$emit('input', $event.target.value)\">\n <input\n v-if=\"dateSelection == 'iso'\"\n type=\"text\"\n class=\"w-64 h-8 border border-edge-strong outline-0\"\n :value=\"valueAsISOString\"\n @input=\"updateFromISO\">\n</div>";
53983
+ module.exports = "<div>\n <custom-date-picker\n v-if=\"dateSelection == 'picker'\"\n :value=\"value\"\n mode=\"datetime\"\n :horizontal=\"true\"\n class=\"max-w-[420px]\"\n @input=\"$emit('input', $event)\"\n />\n <input\n v-if=\"dateSelection == 'iso'\"\n type=\"text\"\n class=\"w-64 h-8 border border-edge-strong outline-0\"\n :value=\"valueAsISOString\"\n @input=\"updateFromISO\">\n</div>\n";
53197
53984
 
53198
53985
  /***/ },
53199
53986
 
@@ -53435,7 +54222,7 @@ module.exports = "<div>\n <div class=\"modal-exit\" @click=\"close\">&times;</d
53435
54222
  (module) {
53436
54223
 
53437
54224
  "use strict";
53438
- module.exports = "<form @submit.prevent=\"emitSearch\" class=\"relative flex-grow m-0\">\n <input\n ref=\"searchInput\"\n class=\"w-full font-mono rounded-md p-1 border border-edge-strong outline-edge-strong text-lg focus:ring-1 focus:ring-primary-subtle focus:ring-offset-0 focus:outline-none\"\n type=\"text\"\n placeholder=\"Filter (supports JS, dates, ObjectIds, and more)\"\n v-model=\"searchText\"\n @click=\"initFilter\"\n @input=\"updateAutocomplete\"\n @keydown=\"handleKeyDown\"\n />\n <ul v-if=\"autocompleteSuggestions.length\" class=\"absolute z-[9999] bg-surface border border-edge-strong rounded mt-1 w-full max-h-40 overflow-y-auto shadow\">\n <li\n v-for=\"(suggestion, index) in autocompleteSuggestions\"\n :key=\"suggestion\"\n class=\"px-2 py-1 cursor-pointer\"\n :class=\"{ 'bg-primary-subtle': index === autocompleteIndex }\"\n @mousedown.prevent=\"applySuggestion(index)\"\n >\n {{ suggestion }}\n </li>\n </ul>\n</form>\n";
54225
+ module.exports = "<div class=\"relative flex-grow m-0\" @focusout=\"onWrapperFocusOut\">\n <form @submit.prevent=\"emitSearch\" class=\"w-full mb-0\">\n <input\n ref=\"searchInput\"\n class=\"w-full font-mono rounded-md p-1 border border-edge-strong outline-edge-strong text-lg focus:ring-1 focus:ring-primary-subtle focus:ring-offset-0 focus:outline-none\"\n type=\"text\"\n placeholder=\"Filter (supports JS, dates, ObjectIds, and more)\"\n v-model=\"searchText\"\n @click=\"initFilter($event); updateAutocomplete()\"\n @input=\"updateAutocomplete\"\n @keyup=\"onSearchKeyup\"\n @keydown=\"handleKeyDown\"\n />\n <div\n v-if=\"autocompleteSuggestions.length\"\n ref=\"autocompleteDropdown\"\n class=\"absolute z-[9999] bg-surface border border-edge-strong rounded mt-1 w-full shadow\"\n >\n <ul class=\"max-h-40 overflow-y-auto\">\n <li\n v-for=\"(suggestion, index) in autocompleteSuggestions\"\n :key=\"suggestion\"\n class=\"px-2 py-1 cursor-pointer\"\n :class=\"{ 'bg-primary-subtle': index === autocompleteIndex }\"\n @mousedown.prevent=\"applySuggestion(index)\"\n >\n {{ suggestion }}\n </li>\n </ul>\n </div>\n </form>\n\n <div\n v-if=\"datePickerContext\"\n class=\"absolute top-full left-0 z-[10000] mt-0 w-[400px] max-w-[calc(100vw-2rem)] bg-surface border border-edge-strong rounded-lg shadow-xl\"\n role=\"dialog\"\n aria-label=\"Date picker\"\n @mousedown.stop\n >\n <div class=\"flex items-center justify-between gap-2 px-3 py-2 border-b border-edge-strong\">\n <span class=\"text-sm font-medium text-content-primary\">Insert date into filter</span>\n <div class=\"flex items-center gap-2 shrink-0\">\n <select\n v-if=\"!datePickerMinimized\"\n v-model=\"dateInsertFormat\"\n class=\"text-xs border border-edge rounded-md bg-surface px-2 py-1\"\n @mousedown.stop\n >\n <option value=\"timestamp\">Milliseconds</option>\n <option value=\"quoted\">Date string</option>\n </select>\n <button\n type=\"button\"\n class=\"flex items-center justify-center w-5 h-5 rounded hover:bg-surface-hover text-content-secondary\"\n :aria-label=\"datePickerMinimized ? 'Expand date picker' : 'Minimize date picker'\"\n @click.stop=\"datePickerMinimized = !datePickerMinimized\"\n >\n <svg v-if=\"datePickerMinimized\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" class=\"w-3.5 h-3.5\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m19.5 8.25-7.5 7.5-7.5-7.5\" />\n </svg>\n <svg v-else xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" class=\"w-3.5 h-3.5\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m4.5 15.75 7.5-7.5 7.5 7.5\" />\n </svg>\n </button>\n </div>\n </div>\n <div v-if=\"!datePickerMinimized\" class=\"p-2\">\n <custom-date-picker\n compact\n horizontal\n embedded\n :immediate=\"false\"\n mode=\"datetime\"\n :value=\"datePickerLocalValue\"\n @commit=\"applyDateFromPicker\"\n />\n </div>\n </div>\n</div>\n";
53439
54226
 
53440
54227
  /***/ },
53441
54228
 
@@ -59848,7 +60635,7 @@ class Binary extends BSONValue {
59848
60635
  !Array.isArray(buffer)) {
59849
60636
  throw new BSONError('Binary can only be constructed from Uint8Array or number[]');
59850
60637
  }
59851
- this.sub_type = subType ?? Binary.BSON_BINARY_SUBTYPE_DEFAULT;
60638
+ this.sub_type = (subType ?? Binary.BSON_BINARY_SUBTYPE_DEFAULT) & 0xff;
59852
60639
  if (buffer == null) {
59853
60640
  this.buffer = ByteUtils.allocate(Binary.BUFFER_SIZE);
59854
60641
  this.position = 0;
@@ -59954,7 +60741,7 @@ class Binary extends BSONValue {
59954
60741
  if (this.sub_type === Binary.SUBTYPE_UUID) {
59955
60742
  return new UUID(this.buffer.subarray(0, this.position));
59956
60743
  }
59957
- throw new BSONError(`Binary sub_type "${this.sub_type}" is not supported for converting to UUID. Only "${Binary.SUBTYPE_UUID}" is currently supported.`);
60744
+ throw new BSONError(`Binary sub_type "${this.sub_type}" (${typeof this.sub_type}) is not supported for converting to UUID. Only 0x${Binary.SUBTYPE_UUID.toString(16).padStart(2, '0')} is currently supported.`);
59958
60745
  }
59959
60746
  static createFromHexString(hex, subType) {
59960
60747
  return new Binary(ByteUtils.fromHex(hex), subType);
@@ -61743,13 +62530,24 @@ class MinKey extends BSONValue {
61743
62530
  }
61744
62531
  }
61745
62532
 
61746
- let PROCESS_UNIQUE = null;
61747
62533
  const __idCache = new WeakMap();
61748
62534
  class ObjectId extends BSONValue {
61749
62535
  get _bsontype() {
61750
62536
  return 'ObjectId';
61751
62537
  }
61752
- static index = Math.floor(Math.random() * 0xffffff);
62538
+ static index = 0;
62539
+ static PROCESS_UNIQUE = null;
62540
+ static resetState = () => {
62541
+ this.index = Math.floor(Math.random() * 0x1000000);
62542
+ this.PROCESS_UNIQUE = ByteUtils.randomBytes(5);
62543
+ };
62544
+ static {
62545
+ this.resetState();
62546
+ const { startupSnapshot } = globalThis?.process?.getBuiltinModule('v8') ?? {};
62547
+ if (startupSnapshot?.isBuildingSnapshot()) {
62548
+ startupSnapshot?.addDeserializeCallback(this.resetState);
62549
+ }
62550
+ }
61753
62551
  static cacheHexString;
61754
62552
  buffer;
61755
62553
  constructor(inputId) {
@@ -61826,7 +62624,7 @@ class ObjectId extends BSONValue {
61826
62624
  return hexString;
61827
62625
  }
61828
62626
  static getInc() {
61829
- return (ObjectId.index = (ObjectId.index + 1) % 0xffffff);
62627
+ return (ObjectId.index = (ObjectId.index + 1) % 0x1000000);
61830
62628
  }
61831
62629
  static generate(time) {
61832
62630
  if ('number' !== typeof time) {
@@ -61835,9 +62633,7 @@ class ObjectId extends BSONValue {
61835
62633
  const inc = ObjectId.getInc();
61836
62634
  const buffer = ByteUtils.allocateUnsafe(12);
61837
62635
  NumberUtils.setInt32BE(buffer, 0, time);
61838
- if (PROCESS_UNIQUE === null) {
61839
- PROCESS_UNIQUE = ByteUtils.randomBytes(5);
61840
- }
62636
+ const PROCESS_UNIQUE = this.PROCESS_UNIQUE;
61841
62637
  buffer[4] = PROCESS_UNIQUE[0];
61842
62638
  buffer[5] = PROCESS_UNIQUE[1];
61843
62639
  buffer[6] = PROCESS_UNIQUE[2];
@@ -61955,23 +62751,33 @@ class ObjectId extends BSONValue {
61955
62751
  }
61956
62752
 
61957
62753
  function internalCalculateObjectSize(object, serializeFunctions, ignoreUndefined) {
61958
- let totalLength = 4 + 1;
61959
- if (Array.isArray(object)) {
61960
- for (let i = 0; i < object.length; i++) {
61961
- totalLength += calculateElement(i.toString(), object[i], serializeFunctions, true, ignoreUndefined);
62754
+ const objectStack = [
62755
+ { obj: object, ignoreUndefined: ignoreUndefined ?? false }
62756
+ ];
62757
+ let total = 0;
62758
+ while (objectStack.length > 0) {
62759
+ const { obj, ignoreUndefined: frameIgnoreUndefined } = objectStack.pop();
62760
+ total += 5;
62761
+ const isObjArray = Array.isArray(obj);
62762
+ let target = obj;
62763
+ if (!isObjArray && typeof obj?.toBSON === 'function') {
62764
+ target = obj.toBSON();
61962
62765
  }
61963
- }
61964
- else {
61965
- if (typeof object?.toBSON === 'function') {
61966
- object = object.toBSON();
62766
+ if (isObjArray) {
62767
+ const array = target;
62768
+ for (let i = 0; i < array.length; i++) {
62769
+ total += calculateElementSize(i.toString(), array[i], serializeFunctions, true, frameIgnoreUndefined, objectStack);
62770
+ }
61967
62771
  }
61968
- for (const key of Object.keys(object)) {
61969
- totalLength += calculateElement(key, object[key], serializeFunctions, false, ignoreUndefined);
62772
+ else {
62773
+ for (const key of Object.keys(target)) {
62774
+ total += calculateElementSize(key, target[key], serializeFunctions, false, frameIgnoreUndefined, objectStack);
62775
+ }
61970
62776
  }
61971
62777
  }
61972
- return totalLength;
62778
+ return total;
61973
62779
  }
61974
- function calculateElement(name, value, serializeFunctions = false, isArray = false, ignoreUndefined = false) {
62780
+ function calculateElementSize(name, value, serializeFunctions = false, isArray = false, ignoreUndefined = false, objectStack) {
61975
62781
  if (typeof value?.toBSON === 'function') {
61976
62782
  value = value.toBSON();
61977
62783
  }
@@ -61983,21 +62789,21 @@ function calculateElement(name, value, serializeFunctions = false, isArray = fal
61983
62789
  value >= JS_INT_MIN &&
61984
62790
  value <= JS_INT_MAX) {
61985
62791
  if (value >= BSON_INT32_MIN && value <= BSON_INT32_MAX) {
61986
- return (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + (4 + 1);
62792
+ return ByteUtils.utf8ByteLength(name) + 1 + (4 + 1);
61987
62793
  }
61988
62794
  else {
61989
- return (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + (8 + 1);
62795
+ return ByteUtils.utf8ByteLength(name) + 1 + (8 + 1);
61990
62796
  }
61991
62797
  }
61992
62798
  else {
61993
- return (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + (8 + 1);
62799
+ return ByteUtils.utf8ByteLength(name) + 1 + (8 + 1);
61994
62800
  }
61995
62801
  case 'undefined':
61996
62802
  if (isArray || !ignoreUndefined)
61997
- return (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + 1;
62803
+ return ByteUtils.utf8ByteLength(name) + 1 + 1;
61998
62804
  return 0;
61999
62805
  case 'boolean':
62000
- return (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + (1 + 1);
62806
+ return ByteUtils.utf8ByteLength(name) + 1 + (1 + 1);
62001
62807
  case 'object':
62002
62808
  if (value != null &&
62003
62809
  typeof value._bsontype === 'string' &&
@@ -62005,39 +62811,41 @@ function calculateElement(name, value, serializeFunctions = false, isArray = fal
62005
62811
  throw new BSONVersionError();
62006
62812
  }
62007
62813
  else if (value == null || value._bsontype === 'MinKey' || value._bsontype === 'MaxKey') {
62008
- return (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + 1;
62814
+ return ByteUtils.utf8ByteLength(name) + 1 + 1;
62009
62815
  }
62010
62816
  else if (value._bsontype === 'ObjectId') {
62011
- return (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + (12 + 1);
62817
+ return ByteUtils.utf8ByteLength(name) + 1 + (12 + 1);
62012
62818
  }
62013
62819
  else if (value instanceof Date || isDate(value)) {
62014
- return (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + (8 + 1);
62820
+ return ByteUtils.utf8ByteLength(name) + 1 + (8 + 1);
62015
62821
  }
62016
62822
  else if (ArrayBuffer.isView(value) ||
62017
62823
  value instanceof ArrayBuffer ||
62018
62824
  isAnyArrayBuffer(value)) {
62019
- return ((name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + (1 + 4 + 1) + value.byteLength);
62825
+ return ByteUtils.utf8ByteLength(name) + 1 + (1 + 4 + 1) + value.byteLength;
62020
62826
  }
62021
62827
  else if (value._bsontype === 'Long' ||
62022
62828
  value._bsontype === 'Double' ||
62023
62829
  value._bsontype === 'Timestamp') {
62024
- return (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + (8 + 1);
62830
+ return ByteUtils.utf8ByteLength(name) + 1 + (8 + 1);
62025
62831
  }
62026
62832
  else if (value._bsontype === 'Decimal128') {
62027
- return (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + (16 + 1);
62833
+ return ByteUtils.utf8ByteLength(name) + 1 + (16 + 1);
62028
62834
  }
62029
62835
  else if (value._bsontype === 'Code') {
62030
62836
  if (value.scope != null && Object.keys(value.scope).length > 0) {
62031
- return ((name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) +
62837
+ objectStack.push({ obj: value.scope, ignoreUndefined });
62838
+ return (ByteUtils.utf8ByteLength(name) +
62839
+ 1 +
62032
62840
  1 +
62033
62841
  4 +
62034
62842
  4 +
62035
62843
  ByteUtils.utf8ByteLength(value.code.toString()) +
62036
- 1 +
62037
- internalCalculateObjectSize(value.scope, serializeFunctions, ignoreUndefined));
62844
+ 1);
62038
62845
  }
62039
62846
  else {
62040
- return ((name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) +
62847
+ return (ByteUtils.utf8ByteLength(name) +
62848
+ 1 +
62041
62849
  1 +
62042
62850
  4 +
62043
62851
  ByteUtils.utf8ByteLength(value.code.toString()) +
@@ -62047,19 +62855,14 @@ function calculateElement(name, value, serializeFunctions = false, isArray = fal
62047
62855
  else if (value._bsontype === 'Binary') {
62048
62856
  const binary = value;
62049
62857
  if (binary.sub_type === Binary.SUBTYPE_BYTE_ARRAY) {
62050
- return ((name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) +
62051
- (binary.position + 1 + 4 + 1 + 4));
62858
+ return ByteUtils.utf8ByteLength(name) + 1 + (binary.position + 1 + 4 + 1 + 4);
62052
62859
  }
62053
62860
  else {
62054
- return ((name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + (binary.position + 1 + 4 + 1));
62861
+ return ByteUtils.utf8ByteLength(name) + 1 + (binary.position + 1 + 4 + 1);
62055
62862
  }
62056
62863
  }
62057
62864
  else if (value._bsontype === 'Symbol') {
62058
- return ((name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) +
62059
- ByteUtils.utf8ByteLength(value.value) +
62060
- 4 +
62061
- 1 +
62062
- 1);
62865
+ return (ByteUtils.utf8ByteLength(name) + 1 + ByteUtils.utf8ByteLength(value.value) + 4 + 1 + 1);
62063
62866
  }
62064
62867
  else if (value._bsontype === 'DBRef') {
62065
62868
  const ordered_values = Object.assign({
@@ -62069,12 +62872,12 @@ function calculateElement(name, value, serializeFunctions = false, isArray = fal
62069
62872
  if (value.db != null) {
62070
62873
  ordered_values['$db'] = value.db;
62071
62874
  }
62072
- return ((name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) +
62073
- 1 +
62074
- internalCalculateObjectSize(ordered_values, serializeFunctions, ignoreUndefined));
62875
+ objectStack.push({ obj: ordered_values, ignoreUndefined: true });
62876
+ return ByteUtils.utf8ByteLength(name) + 1 + 1;
62075
62877
  }
62076
62878
  else if (value instanceof RegExp || isRegExp(value)) {
62077
- return ((name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) +
62879
+ return (ByteUtils.utf8ByteLength(name) +
62880
+ 1 +
62078
62881
  1 +
62079
62882
  ByteUtils.utf8ByteLength(value.source) +
62080
62883
  1 +
@@ -62084,7 +62887,8 @@ function calculateElement(name, value, serializeFunctions = false, isArray = fal
62084
62887
  1);
62085
62888
  }
62086
62889
  else if (value._bsontype === 'BSONRegExp') {
62087
- return ((name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) +
62890
+ return (ByteUtils.utf8ByteLength(name) +
62891
+ 1 +
62088
62892
  1 +
62089
62893
  ByteUtils.utf8ByteLength(value.pattern) +
62090
62894
  1 +
@@ -62092,13 +62896,13 @@ function calculateElement(name, value, serializeFunctions = false, isArray = fal
62092
62896
  1);
62093
62897
  }
62094
62898
  else {
62095
- return ((name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) +
62096
- internalCalculateObjectSize(value, serializeFunctions, ignoreUndefined) +
62097
- 1);
62899
+ objectStack.push({ obj: value, ignoreUndefined });
62900
+ return ByteUtils.utf8ByteLength(name) + 1 + 1;
62098
62901
  }
62099
62902
  case 'function':
62100
62903
  if (serializeFunctions) {
62101
- return ((name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) +
62904
+ return (ByteUtils.utf8ByteLength(name) +
62905
+ 1 +
62102
62906
  1 +
62103
62907
  4 +
62104
62908
  ByteUtils.utf8ByteLength(value.toString()) +
@@ -62106,7 +62910,7 @@ function calculateElement(name, value, serializeFunctions = false, isArray = fal
62106
62910
  }
62107
62911
  return 0;
62108
62912
  case 'bigint':
62109
- return (name != null ? ByteUtils.utf8ByteLength(name) + 1 : 0) + (8 + 1);
62913
+ return ByteUtils.utf8ByteLength(name) + 1 + (8 + 1);
62110
62914
  case 'symbol':
62111
62915
  return 0;
62112
62916
  default:
@@ -62322,7 +63126,28 @@ function internalDeserialize(buffer, options, isArray) {
62322
63126
  return deserializeObject(buffer, index, options, isArray);
62323
63127
  }
62324
63128
  const allowedDBRefKeys = /^\$ref$|^\$id$|^\$db$/;
63129
+ function assignValue(dest, name, value) {
63130
+ if (name === '__proto__') {
63131
+ Object.defineProperty(dest, name, {
63132
+ value,
63133
+ writable: true,
63134
+ enumerable: true,
63135
+ configurable: true
63136
+ });
63137
+ }
63138
+ else {
63139
+ dest[name] = value;
63140
+ }
63141
+ }
63142
+ function toPotentialDbRef(doc) {
63143
+ if (isDBRefLike(doc)) {
63144
+ const { $ref, $id, $db, ...fields } = doc;
63145
+ return new DBRef($ref, $id, $db, fields);
63146
+ }
63147
+ return doc;
63148
+ }
62325
63149
  function deserializeObject(buffer, index, options, isArray = false) {
63150
+ options = { ...options };
62326
63151
  const fieldsAsRaw = options['fieldsAsRaw'] == null ? null : options['fieldsAsRaw'];
62327
63152
  const raw = options['raw'] == null ? false : options['raw'];
62328
63153
  const bsonRegExp = typeof options['bsonRegExp'] === 'boolean' ? options['bsonRegExp'] : false;
@@ -62373,31 +63198,87 @@ function deserializeObject(buffer, index, options, isArray = false) {
62373
63198
  index += 4;
62374
63199
  if (size < 5 || size > buffer.length)
62375
63200
  throw new BSONError('corrupt bson message');
62376
- const object = isArray ? [] : {};
63201
+ const rootObject = isArray ? [] : {};
62377
63202
  let arrayIndex = 0;
62378
63203
  let isPossibleDBRef = isArray ? false : null;
63204
+ let currentFrame = null;
63205
+ let currentDest = rootObject;
63206
+ let currentIsArray = isArray;
62379
63207
  while (true) {
62380
63208
  const elementType = buffer[index++];
62381
- if (elementType === 0)
62382
- break;
63209
+ if (elementType === 0) {
63210
+ if (currentFrame) {
63211
+ if (index === currentFrame.lastIndex) {
63212
+ const completedFrame = currentFrame;
63213
+ currentFrame = completedFrame.prev;
63214
+ if (currentFrame === null) {
63215
+ currentDest = rootObject;
63216
+ currentIsArray = isArray;
63217
+ }
63218
+ else {
63219
+ currentDest = currentFrame.holdingDocument;
63220
+ currentIsArray = currentFrame.isArray;
63221
+ }
63222
+ let result = completedFrame.holdingDocument;
63223
+ switch (completedFrame.elementType) {
63224
+ case BSON_DATA_OBJECT:
63225
+ if (completedFrame.isPossibleDBRef) {
63226
+ result = toPotentialDbRef(result);
63227
+ }
63228
+ break;
63229
+ case BSON_DATA_ARRAY:
63230
+ break;
63231
+ case BSON_DATA_CODE_W_SCOPE:
63232
+ result = new Code(completedFrame.functionString, completedFrame.holdingDocument);
63233
+ break;
63234
+ default:
63235
+ throw new BSONError('Unexpected element type in frame stack');
63236
+ }
63237
+ assignValue(currentDest, completedFrame.propertyName, result);
63238
+ continue;
63239
+ }
63240
+ else {
63241
+ if (currentFrame.elementType === BSON_DATA_ARRAY) {
63242
+ throw new BSONError('corrupted array bson');
63243
+ }
63244
+ throw new BSONError('Bad BSON Document: object not properly terminated');
63245
+ }
63246
+ }
63247
+ else {
63248
+ break;
63249
+ }
63250
+ }
62383
63251
  let i = index;
62384
63252
  while (buffer[i] !== 0x00 && i < buffer.length) {
62385
63253
  i++;
62386
63254
  }
62387
63255
  if (i >= buffer.byteLength)
62388
63256
  throw new BSONError('Bad BSON Document: illegal CString');
62389
- const name = isArray ? arrayIndex++ : ByteUtils.toUTF8(buffer, index, i, false);
62390
- let shouldValidateKey = true;
62391
- if (globalUTFValidation || utf8KeysSet?.has(name)) {
63257
+ const name = currentIsArray
63258
+ ? currentFrame !== null
63259
+ ? currentFrame.arrayIndex++
63260
+ : arrayIndex++
63261
+ : ByteUtils.toUTF8(buffer, index, i, false);
63262
+ let shouldValidateKey;
63263
+ if (currentFrame !== null) {
63264
+ shouldValidateKey = currentFrame.validationSetting;
63265
+ }
63266
+ else if (globalUTFValidation || utf8KeysSet?.has(name)) {
62392
63267
  shouldValidateKey = validationSetting;
62393
63268
  }
62394
63269
  else {
62395
63270
  shouldValidateKey = !validationSetting;
62396
63271
  }
62397
- if (isPossibleDBRef !== false && name[0] === '$') {
63272
+ if (currentFrame !== null) {
63273
+ if (currentFrame.isPossibleDBRef !== false && typeof name === 'string' && name[0] === '$') {
63274
+ currentFrame.isPossibleDBRef = allowedDBRefKeys.test(name);
63275
+ }
63276
+ }
63277
+ else if (isPossibleDBRef !== false && name[0] === '$') {
62398
63278
  isPossibleDBRef = allowedDBRefKeys.test(name);
62399
63279
  }
62400
63280
  let value;
63281
+ let isDeferredValue = false;
62401
63282
  index = i + 1;
62402
63283
  if (elementType === BSON_DATA_STRING) {
62403
63284
  const stringSize = NumberUtils.getInt32LE(buffer, index);
@@ -62443,39 +63324,58 @@ function deserializeObject(buffer, index, options, isArray = false) {
62443
63324
  value = buffer[index++] === 1;
62444
63325
  }
62445
63326
  else if (elementType === BSON_DATA_OBJECT) {
62446
- const _index = index;
62447
63327
  const objectSize = NumberUtils.getInt32LE(buffer, index);
62448
- if (objectSize <= 0 || objectSize > buffer.length - index)
63328
+ if (objectSize < 5 || objectSize > buffer.length - index)
62449
63329
  throw new BSONError('bad embedded document length in bson');
62450
- if (raw) {
63330
+ if (raw || (currentFrame?.raw ?? false)) {
62451
63331
  value = buffer.subarray(index, index + objectSize);
63332
+ index = index + objectSize;
62452
63333
  }
62453
63334
  else {
62454
- let objectOptions = options;
62455
- if (!globalUTFValidation) {
62456
- objectOptions = { ...options, validation: { utf8: shouldValidateKey } };
62457
- }
62458
- value = deserializeObject(buffer, _index, objectOptions, false);
63335
+ isDeferredValue = true;
63336
+ const objectFrame = {
63337
+ holdingDocument: {},
63338
+ elementType: BSON_DATA_OBJECT,
63339
+ propertyName: name,
63340
+ functionString: null,
63341
+ lastIndex: index + objectSize,
63342
+ isArray: false,
63343
+ arrayIndex: 0,
63344
+ raw: false,
63345
+ isPossibleDBRef: null,
63346
+ validationSetting: shouldValidateKey,
63347
+ prev: currentFrame
63348
+ };
63349
+ currentFrame = objectFrame;
63350
+ currentDest = objectFrame.holdingDocument;
63351
+ currentIsArray = false;
63352
+ index = index + 4;
62459
63353
  }
62460
- index = index + objectSize;
62461
63354
  }
62462
63355
  else if (elementType === BSON_DATA_ARRAY) {
62463
- const _index = index;
62464
63356
  const objectSize = NumberUtils.getInt32LE(buffer, index);
62465
- let arrayOptions = options;
63357
+ if (objectSize < 5 || objectSize > buffer.length - index)
63358
+ throw new BSONError('bad embedded array length in bson');
62466
63359
  const stopIndex = index + objectSize;
62467
- if (fieldsAsRaw && fieldsAsRaw[name]) {
62468
- arrayOptions = { ...options, raw: true };
62469
- }
62470
- if (!globalUTFValidation) {
62471
- arrayOptions = { ...arrayOptions, validation: { utf8: shouldValidateKey } };
62472
- }
62473
- value = deserializeObject(buffer, _index, arrayOptions, true);
62474
- index = index + objectSize;
62475
- if (buffer[index - 1] !== 0)
62476
- throw new BSONError('invalid array terminator byte');
62477
- if (index !== stopIndex)
62478
- throw new BSONError('corrupted array bson');
63360
+ const arrayRaw = !!(fieldsAsRaw && fieldsAsRaw[name]) || (currentFrame?.raw ?? false);
63361
+ isDeferredValue = true;
63362
+ const arrayFrame = {
63363
+ holdingDocument: [],
63364
+ elementType: BSON_DATA_ARRAY,
63365
+ propertyName: name,
63366
+ functionString: null,
63367
+ lastIndex: stopIndex,
63368
+ isArray: true,
63369
+ arrayIndex: 0,
63370
+ raw: arrayRaw,
63371
+ isPossibleDBRef: false,
63372
+ validationSetting: shouldValidateKey,
63373
+ prev: currentFrame
63374
+ };
63375
+ currentFrame = arrayFrame;
63376
+ currentDest = arrayFrame.holdingDocument;
63377
+ currentIsArray = true;
63378
+ index = index + 4;
62479
63379
  }
62480
63380
  else if (elementType === BSON_DATA_UNDEFINED) {
62481
63381
  value = undefined;
@@ -62647,15 +63547,32 @@ function deserializeObject(buffer, index, options, isArray = false) {
62647
63547
  index = index + stringSize;
62648
63548
  const _index = index;
62649
63549
  const objectSize = NumberUtils.getInt32LE(buffer, index);
62650
- const scopeObject = deserializeObject(buffer, _index, options, false);
62651
- index = index + objectSize;
63550
+ if (objectSize < 5 || objectSize > buffer.length - index)
63551
+ throw new BSONError('bad scope document size in code_w_scope');
62652
63552
  if (totalSize < 4 + 4 + objectSize + stringSize) {
62653
63553
  throw new BSONError('code_w_scope total size is too short, truncating scope');
62654
63554
  }
62655
63555
  if (totalSize > 4 + 4 + objectSize + stringSize) {
62656
63556
  throw new BSONError('code_w_scope total size is too long, clips outer document');
62657
63557
  }
62658
- value = new Code(functionString, scopeObject);
63558
+ isDeferredValue = true;
63559
+ const scopeFrame = {
63560
+ holdingDocument: {},
63561
+ elementType: BSON_DATA_CODE_W_SCOPE,
63562
+ propertyName: name,
63563
+ functionString: functionString,
63564
+ lastIndex: _index + objectSize,
63565
+ isArray: false,
63566
+ arrayIndex: 0,
63567
+ raw: false,
63568
+ isPossibleDBRef: null,
63569
+ validationSetting: shouldValidateKey,
63570
+ prev: currentFrame
63571
+ };
63572
+ currentFrame = scopeFrame;
63573
+ currentDest = scopeFrame.holdingDocument;
63574
+ currentIsArray = false;
63575
+ index = index + 4;
62659
63576
  }
62660
63577
  else if (elementType === BSON_DATA_DBPOINTER) {
62661
63578
  const stringSize = NumberUtils.getInt32LE(buffer, index);
@@ -62676,18 +63593,14 @@ function deserializeObject(buffer, index, options, isArray = false) {
62676
63593
  else {
62677
63594
  throw new BSONError(`Detected unknown BSON type ${elementType.toString(16)} for fieldname "${name}"`);
62678
63595
  }
62679
- if (name === '__proto__') {
62680
- Object.defineProperty(object, name, {
62681
- value,
62682
- writable: true,
62683
- enumerable: true,
62684
- configurable: true
62685
- });
62686
- }
62687
- else {
62688
- object[name] = value;
63596
+ if (!isDeferredValue) {
63597
+ assignValue(currentDest, name, value);
62689
63598
  }
62690
63599
  }
63600
+ if (currentFrame !== null) {
63601
+ throw new BSONError('corrupted bson, more objects expected based on the current document size');
63602
+ }
63603
+ const object = rootObject;
62691
63604
  if (size !== index - startIndex) {
62692
63605
  if (isArray)
62693
63606
  throw new BSONError('corrupt array bson');
@@ -62695,14 +63608,7 @@ function deserializeObject(buffer, index, options, isArray = false) {
62695
63608
  }
62696
63609
  if (!isPossibleDBRef)
62697
63610
  return object;
62698
- if (isDBRefLike(object)) {
62699
- const copy = Object.assign({}, object);
62700
- delete copy.$ref;
62701
- delete copy.$id;
62702
- delete copy.$db;
62703
- return new DBRef(object.$ref, object.$id, object.$db, copy);
62704
- }
62705
- return object;
63611
+ return toPotentialDbRef(object);
62706
63612
  }
62707
63613
 
62708
63614
  const regexp = /\x00/;
@@ -62811,7 +63717,7 @@ function serializeMinMax(buffer, key, value, index) {
62811
63717
  if (value === null) {
62812
63718
  buffer[index++] = BSON_DATA_NULL;
62813
63719
  }
62814
- else if (value._bsontype === 'MinKey') {
63720
+ else if (value[bsonType] === 'MinKey') {
62815
63721
  buffer[index++] = BSON_DATA_MIN_KEY;
62816
63722
  }
62817
63723
  else {
@@ -62848,19 +63754,6 @@ function serializeBuffer(buffer, key, value, index) {
62848
63754
  index = index + size;
62849
63755
  return index;
62850
63756
  }
62851
- function serializeObject(buffer, key, value, index, checkKeys, depth, serializeFunctions, ignoreUndefined, path) {
62852
- if (path.has(value)) {
62853
- throw new BSONError('Cannot convert circular structure to BSON');
62854
- }
62855
- path.add(value);
62856
- buffer[index++] = Array.isArray(value) ? BSON_DATA_ARRAY : BSON_DATA_OBJECT;
62857
- const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
62858
- index = index + numberOfWrittenBytes;
62859
- buffer[index++] = 0;
62860
- const endIndex = serializeInto(buffer, value, checkKeys, index, depth + 1, serializeFunctions, ignoreUndefined, path);
62861
- path.delete(value);
62862
- return endIndex;
62863
- }
62864
63757
  function serializeDecimal128(buffer, key, value, index) {
62865
63758
  buffer[index++] = BSON_DATA_DECIMAL128;
62866
63759
  const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
@@ -62872,7 +63765,7 @@ function serializeDecimal128(buffer, key, value, index) {
62872
63765
  }
62873
63766
  function serializeLong(buffer, key, value, index) {
62874
63767
  buffer[index++] =
62875
- value._bsontype === 'Long' ? BSON_DATA_LONG : BSON_DATA_TIMESTAMP;
63768
+ value[bsonType] === 'Long' ? BSON_DATA_LONG : BSON_DATA_TIMESTAMP;
62876
63769
  const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
62877
63770
  index = index + numberOfWrittenBytes;
62878
63771
  buffer[index++] = 0;
@@ -62911,38 +63804,6 @@ function serializeFunction(buffer, key, value, index) {
62911
63804
  buffer[index++] = 0;
62912
63805
  return index;
62913
63806
  }
62914
- function serializeCode(buffer, key, value, index, checkKeys = false, depth = 0, serializeFunctions = false, ignoreUndefined = true, path) {
62915
- if (value.scope && typeof value.scope === 'object') {
62916
- buffer[index++] = BSON_DATA_CODE_W_SCOPE;
62917
- const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
62918
- index = index + numberOfWrittenBytes;
62919
- buffer[index++] = 0;
62920
- let startIndex = index;
62921
- const functionString = value.code;
62922
- index = index + 4;
62923
- const codeSize = ByteUtils.encodeUTF8Into(buffer, functionString, index + 4) + 1;
62924
- NumberUtils.setInt32LE(buffer, index, codeSize);
62925
- buffer[index + 4 + codeSize - 1] = 0;
62926
- index = index + codeSize + 4;
62927
- const endIndex = serializeInto(buffer, value.scope, checkKeys, index, depth + 1, serializeFunctions, ignoreUndefined, path);
62928
- index = endIndex - 1;
62929
- const totalSize = endIndex - startIndex;
62930
- startIndex += NumberUtils.setInt32LE(buffer, startIndex, totalSize);
62931
- buffer[index++] = 0;
62932
- }
62933
- else {
62934
- buffer[index++] = BSON_DATA_CODE;
62935
- const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
62936
- index = index + numberOfWrittenBytes;
62937
- buffer[index++] = 0;
62938
- const functionString = value.code.toString();
62939
- const size = ByteUtils.encodeUTF8Into(buffer, functionString, index + 4) + 1;
62940
- NumberUtils.setInt32LE(buffer, index, size);
62941
- index = index + 4 + size - 1;
62942
- buffer[index++] = 0;
62943
- }
62944
- return index;
62945
- }
62946
63807
  function serializeBinary(buffer, key, value, index) {
62947
63808
  buffer[index++] = BSON_DATA_BINARY;
62948
63809
  const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
@@ -62982,26 +63843,59 @@ function serializeSymbol(buffer, key, value, index) {
62982
63843
  buffer[index++] = 0;
62983
63844
  return index;
62984
63845
  }
62985
- function serializeDBRef(buffer, key, value, index, depth, serializeFunctions, path) {
62986
- buffer[index++] = BSON_DATA_OBJECT;
62987
- const numberOfWrittenBytes = ByteUtils.encodeUTF8Into(buffer, key, index);
62988
- index = index + numberOfWrittenBytes;
62989
- buffer[index++] = 0;
62990
- let startIndex = index;
62991
- let output = {
62992
- $ref: value.collection || value.namespace,
62993
- $id: value.oid
62994
- };
62995
- if (value.db != null) {
62996
- output.$db = value.db;
63846
+ function makeFrame(sourceObject, objectSizeIndex, codeSizeIndex, prev, checkKeys, ignoreUndefined) {
63847
+ if (Array.isArray(sourceObject)) {
63848
+ return {
63849
+ sourceObject,
63850
+ isArray: true,
63851
+ objectSizeIndex,
63852
+ codeSizeIndex,
63853
+ iterTarget: sourceObject,
63854
+ keys: null,
63855
+ keyIndex: 0,
63856
+ mapIterator: null,
63857
+ prev,
63858
+ checkKeys,
63859
+ ignoreUndefined
63860
+ };
62997
63861
  }
62998
- output = Object.assign(output, value.fields);
62999
- const endIndex = serializeInto(buffer, output, false, index, depth + 1, serializeFunctions, true, path);
63000
- const size = endIndex - startIndex;
63001
- startIndex += NumberUtils.setInt32LE(buffer, index, size);
63002
- return endIndex;
63003
- }
63004
- function serializeInto(buffer, object, checkKeys, startingIndex, depth, serializeFunctions, ignoreUndefined, path) {
63862
+ if (sourceObject instanceof Map || isMap(sourceObject)) {
63863
+ return {
63864
+ sourceObject,
63865
+ isArray: false,
63866
+ objectSizeIndex,
63867
+ codeSizeIndex,
63868
+ iterTarget: sourceObject,
63869
+ keys: null,
63870
+ keyIndex: 0,
63871
+ mapIterator: sourceObject.entries(),
63872
+ prev,
63873
+ checkKeys,
63874
+ ignoreUndefined
63875
+ };
63876
+ }
63877
+ let target = sourceObject;
63878
+ if (typeof target?.toBSON === 'function') {
63879
+ target = target.toBSON();
63880
+ if (target != null && typeof target !== 'object') {
63881
+ throw new BSONError('toBSON function did not return an object');
63882
+ }
63883
+ }
63884
+ return {
63885
+ sourceObject,
63886
+ isArray: false,
63887
+ objectSizeIndex,
63888
+ codeSizeIndex,
63889
+ iterTarget: target,
63890
+ keys: Object.keys(target),
63891
+ keyIndex: 0,
63892
+ mapIterator: null,
63893
+ prev,
63894
+ checkKeys,
63895
+ ignoreUndefined
63896
+ };
63897
+ }
63898
+ function serializeInto(buffer, object, checkKeys, startingIndex, serializeFunctions, ignoreUndefined, path) {
63005
63899
  if (path == null) {
63006
63900
  if (object == null) {
63007
63901
  buffer[0] = 0x05;
@@ -63029,308 +63923,200 @@ function serializeInto(buffer, object, checkKeys, startingIndex, depth, serializ
63029
63923
  path = new Set();
63030
63924
  }
63031
63925
  path.add(object);
63926
+ let currentFrame = makeFrame(object, startingIndex, null, null, checkKeys, ignoreUndefined);
63032
63927
  let index = startingIndex + 4;
63033
- if (Array.isArray(object)) {
63034
- for (let i = 0; i < object.length; i++) {
63035
- const key = `${i}`;
63036
- let value = object[i];
63037
- if (typeof value?.toBSON === 'function') {
63038
- value = value.toBSON();
63039
- }
63040
- const type = typeof value;
63041
- if (value === undefined) {
63042
- index = serializeNull(buffer, key, value, index);
63043
- }
63044
- else if (value === null) {
63045
- index = serializeNull(buffer, key, value, index);
63046
- }
63047
- else if (type === 'string') {
63048
- index = serializeString(buffer, key, value, index);
63049
- }
63050
- else if (type === 'number') {
63051
- index = serializeNumber(buffer, key, value, index);
63052
- }
63053
- else if (type === 'bigint') {
63054
- index = serializeBigInt(buffer, key, value, index);
63055
- }
63056
- else if (type === 'boolean') {
63057
- index = serializeBoolean(buffer, key, value, index);
63058
- }
63059
- else if (type === 'object' && value._bsontype == null) {
63060
- if (value instanceof Date || isDate(value)) {
63061
- index = serializeDate(buffer, key, value, index);
63062
- }
63063
- else if (value instanceof Uint8Array || isUint8Array(value)) {
63064
- index = serializeBuffer(buffer, key, value, index);
63065
- }
63066
- else if (value instanceof RegExp || isRegExp(value)) {
63067
- index = serializeRegExp(buffer, key, value, index);
63068
- }
63069
- else {
63070
- index = serializeObject(buffer, key, value, index, checkKeys, depth, serializeFunctions, ignoreUndefined, path);
63928
+ while (currentFrame !== null) {
63929
+ const frame = currentFrame;
63930
+ let key;
63931
+ let value;
63932
+ if (frame.mapIterator !== null) {
63933
+ const next = frame.mapIterator.next();
63934
+ if (next.done) {
63935
+ buffer[index++] = 0x00;
63936
+ NumberUtils.setInt32LE(buffer, frame.objectSizeIndex, index - frame.objectSizeIndex);
63937
+ if (frame.codeSizeIndex !== null) {
63938
+ NumberUtils.setInt32LE(buffer, frame.codeSizeIndex, index - frame.codeSizeIndex);
63071
63939
  }
63940
+ path.delete(frame.sourceObject);
63941
+ currentFrame = frame.prev;
63942
+ continue;
63072
63943
  }
63073
- else if (type === 'object') {
63074
- if (value[BSON_VERSION_SYMBOL] !== BSON_MAJOR_VERSION) {
63075
- throw new BSONVersionError();
63076
- }
63077
- else if (value._bsontype === 'ObjectId') {
63078
- index = serializeObjectId(buffer, key, value, index);
63079
- }
63080
- else if (value._bsontype === 'Decimal128') {
63081
- index = serializeDecimal128(buffer, key, value, index);
63082
- }
63083
- else if (value._bsontype === 'Long' || value._bsontype === 'Timestamp') {
63084
- index = serializeLong(buffer, key, value, index);
63085
- }
63086
- else if (value._bsontype === 'Double') {
63087
- index = serializeDouble(buffer, key, value, index);
63088
- }
63089
- else if (value._bsontype === 'Code') {
63090
- index = serializeCode(buffer, key, value, index, checkKeys, depth, serializeFunctions, ignoreUndefined, path);
63091
- }
63092
- else if (value._bsontype === 'Binary') {
63093
- index = serializeBinary(buffer, key, value, index);
63094
- }
63095
- else if (value._bsontype === 'BSONSymbol') {
63096
- index = serializeSymbol(buffer, key, value, index);
63097
- }
63098
- else if (value._bsontype === 'DBRef') {
63099
- index = serializeDBRef(buffer, key, value, index, depth, serializeFunctions, path);
63100
- }
63101
- else if (value._bsontype === 'BSONRegExp') {
63102
- index = serializeBSONRegExp(buffer, key, value, index);
63103
- }
63104
- else if (value._bsontype === 'Int32') {
63105
- index = serializeInt32(buffer, key, value, index);
63106
- }
63107
- else if (value._bsontype === 'MinKey' || value._bsontype === 'MaxKey') {
63108
- index = serializeMinMax(buffer, key, value, index);
63109
- }
63110
- else if (typeof value._bsontype !== 'undefined') {
63111
- throw new BSONError(`Unrecognized or invalid _bsontype: ${String(value._bsontype)}`);
63944
+ key = next.value[0];
63945
+ value = next.value[1];
63946
+ }
63947
+ else if (frame.keys !== null) {
63948
+ if (frame.keyIndex >= frame.keys.length) {
63949
+ buffer[index++] = 0x00;
63950
+ NumberUtils.setInt32LE(buffer, frame.objectSizeIndex, index - frame.objectSizeIndex);
63951
+ if (frame.codeSizeIndex !== null) {
63952
+ NumberUtils.setInt32LE(buffer, frame.codeSizeIndex, index - frame.codeSizeIndex);
63112
63953
  }
63954
+ path.delete(frame.sourceObject);
63955
+ currentFrame = frame.prev;
63956
+ continue;
63113
63957
  }
63114
- else if (type === 'function' && serializeFunctions) {
63115
- index = serializeFunction(buffer, key, value, index);
63116
- }
63958
+ key = frame.keys[frame.keyIndex++];
63959
+ value = frame.iterTarget[key];
63117
63960
  }
63118
- }
63119
- else if (object instanceof Map || isMap(object)) {
63120
- const iterator = object.entries();
63121
- let done = false;
63122
- while (!done) {
63123
- const entry = iterator.next();
63124
- done = !!entry.done;
63125
- if (done)
63126
- continue;
63127
- const key = entry.value ? entry.value[0] : undefined;
63128
- let value = entry.value ? entry.value[1] : undefined;
63129
- if (typeof value?.toBSON === 'function') {
63130
- value = value.toBSON();
63131
- }
63132
- const type = typeof value;
63133
- if (typeof key === 'string' && !ignoreKeys.has(key)) {
63134
- if (key.match(regexp) != null) {
63135
- throw new BSONError('key ' + key + ' must not contain null bytes');
63136
- }
63137
- if (checkKeys) {
63138
- if ('$' === key[0]) {
63139
- throw new BSONError('key ' + key + " must not start with '$'");
63140
- }
63141
- else if (key.includes('.')) {
63142
- throw new BSONError('key ' + key + " must not contain '.'");
63143
- }
63961
+ else {
63962
+ const arr = frame.iterTarget;
63963
+ if (frame.keyIndex >= arr.length) {
63964
+ buffer[index++] = 0x00;
63965
+ NumberUtils.setInt32LE(buffer, frame.objectSizeIndex, index - frame.objectSizeIndex);
63966
+ if (frame.codeSizeIndex !== null) {
63967
+ NumberUtils.setInt32LE(buffer, frame.codeSizeIndex, index - frame.codeSizeIndex);
63144
63968
  }
63969
+ path.delete(frame.sourceObject);
63970
+ currentFrame = frame.prev;
63971
+ continue;
63145
63972
  }
63146
- if (value === undefined) {
63147
- if (ignoreUndefined === false)
63148
- index = serializeNull(buffer, key, value, index);
63149
- }
63150
- else if (value === null) {
63151
- index = serializeNull(buffer, key, value, index);
63973
+ const i = frame.keyIndex++;
63974
+ key = String(i);
63975
+ value = arr[i];
63976
+ }
63977
+ if (typeof value?.toBSON === 'function') {
63978
+ value = value.toBSON();
63979
+ }
63980
+ if (!frame.isArray && typeof key === 'string' && !(key[0] === '$' && ignoreKeys.has(key))) {
63981
+ if (regexp.test(key)) {
63982
+ throw new BSONError('key ' + key + ' must not contain null bytes');
63152
63983
  }
63153
- else if (type === 'string') {
63154
- index = serializeString(buffer, key, value, index);
63984
+ if (frame.checkKeys) {
63985
+ if ('$' === key[0]) {
63986
+ throw new BSONError('key ' + key + " must not start with '$'");
63987
+ }
63988
+ else if (key.includes('.')) {
63989
+ throw new BSONError('key ' + key + " must not contain '.'");
63990
+ }
63155
63991
  }
63156
- else if (type === 'number') {
63157
- index = serializeNumber(buffer, key, value, index);
63992
+ }
63993
+ const type = typeof value;
63994
+ if (value === undefined) {
63995
+ if (frame.isArray || frame.ignoreUndefined === false) {
63996
+ index = serializeNull(buffer, key, value, index);
63158
63997
  }
63159
- else if (type === 'bigint') {
63160
- index = serializeBigInt(buffer, key, value, index);
63998
+ }
63999
+ else if (value === null) {
64000
+ index = serializeNull(buffer, key, value, index);
64001
+ }
64002
+ else if (type === 'string') {
64003
+ index = serializeString(buffer, key, value, index);
64004
+ }
64005
+ else if (type === 'number') {
64006
+ index = serializeNumber(buffer, key, value, index);
64007
+ }
64008
+ else if (type === 'bigint') {
64009
+ index = serializeBigInt(buffer, key, value, index);
64010
+ }
64011
+ else if (type === 'boolean') {
64012
+ index = serializeBoolean(buffer, key, value, index);
64013
+ }
64014
+ else if (type === 'object' && value._bsontype == null) {
64015
+ if (value instanceof Date || isDate(value)) {
64016
+ index = serializeDate(buffer, key, value, index);
63161
64017
  }
63162
- else if (type === 'boolean') {
63163
- index = serializeBoolean(buffer, key, value, index);
64018
+ else if (value instanceof Uint8Array || isUint8Array(value)) {
64019
+ index = serializeBuffer(buffer, key, value, index);
63164
64020
  }
63165
- else if (type === 'object' && value._bsontype == null) {
63166
- if (value instanceof Date || isDate(value)) {
63167
- index = serializeDate(buffer, key, value, index);
63168
- }
63169
- else if (value instanceof Uint8Array || isUint8Array(value)) {
63170
- index = serializeBuffer(buffer, key, value, index);
63171
- }
63172
- else if (value instanceof RegExp || isRegExp(value)) {
63173
- index = serializeRegExp(buffer, key, value, index);
63174
- }
63175
- else {
63176
- index = serializeObject(buffer, key, value, index, checkKeys, depth, serializeFunctions, ignoreUndefined, path);
63177
- }
64021
+ else if (value instanceof RegExp || isRegExp(value)) {
64022
+ index = serializeRegExp(buffer, key, value, index);
63178
64023
  }
63179
- else if (type === 'object') {
63180
- if (value[BSON_VERSION_SYMBOL] !== BSON_MAJOR_VERSION) {
63181
- throw new BSONVersionError();
63182
- }
63183
- else if (value._bsontype === 'ObjectId') {
63184
- index = serializeObjectId(buffer, key, value, index);
63185
- }
63186
- else if (value._bsontype === 'Decimal128') {
63187
- index = serializeDecimal128(buffer, key, value, index);
63188
- }
63189
- else if (value._bsontype === 'Long' || value._bsontype === 'Timestamp') {
63190
- index = serializeLong(buffer, key, value, index);
63191
- }
63192
- else if (value._bsontype === 'Double') {
63193
- index = serializeDouble(buffer, key, value, index);
63194
- }
63195
- else if (value._bsontype === 'Code') {
63196
- index = serializeCode(buffer, key, value, index, checkKeys, depth, serializeFunctions, ignoreUndefined, path);
63197
- }
63198
- else if (value._bsontype === 'Binary') {
63199
- index = serializeBinary(buffer, key, value, index);
63200
- }
63201
- else if (value._bsontype === 'BSONSymbol') {
63202
- index = serializeSymbol(buffer, key, value, index);
63203
- }
63204
- else if (value._bsontype === 'DBRef') {
63205
- index = serializeDBRef(buffer, key, value, index, depth, serializeFunctions, path);
63206
- }
63207
- else if (value._bsontype === 'BSONRegExp') {
63208
- index = serializeBSONRegExp(buffer, key, value, index);
63209
- }
63210
- else if (value._bsontype === 'Int32') {
63211
- index = serializeInt32(buffer, key, value, index);
63212
- }
63213
- else if (value._bsontype === 'MinKey' || value._bsontype === 'MaxKey') {
63214
- index = serializeMinMax(buffer, key, value, index);
63215
- }
63216
- else if (typeof value._bsontype !== 'undefined') {
63217
- throw new BSONError(`Unrecognized or invalid _bsontype: ${String(value._bsontype)}`);
64024
+ else {
64025
+ if (path.has(value)) {
64026
+ throw new BSONError('Cannot convert circular structure to BSON');
63218
64027
  }
63219
- }
63220
- else if (type === 'function' && serializeFunctions) {
63221
- index = serializeFunction(buffer, key, value, index);
63222
- }
63223
- }
63224
- }
63225
- else {
63226
- if (typeof object?.toBSON === 'function') {
63227
- object = object.toBSON();
63228
- if (object != null && typeof object !== 'object') {
63229
- throw new BSONError('toBSON function did not return an object');
64028
+ const nestedIsArray = Array.isArray(value);
64029
+ buffer[index++] = nestedIsArray ? BSON_DATA_ARRAY : BSON_DATA_OBJECT;
64030
+ index += ByteUtils.encodeUTF8Into(buffer, key, index);
64031
+ buffer[index++] = 0x00;
64032
+ const nestedStartIndex = index;
64033
+ path.add(value);
64034
+ currentFrame = makeFrame(value, nestedStartIndex, null, frame, frame.checkKeys, frame.ignoreUndefined);
64035
+ index += 4;
63230
64036
  }
63231
64037
  }
63232
- for (const key of Object.keys(object)) {
63233
- let value = object[key];
63234
- if (typeof value?.toBSON === 'function') {
63235
- value = value.toBSON();
64038
+ else if (type === 'object') {
64039
+ if (value[BSON_VERSION_SYMBOL] !== BSON_MAJOR_VERSION) {
64040
+ throw new BSONVersionError();
63236
64041
  }
63237
- const type = typeof value;
63238
- if (typeof key === 'string' && !ignoreKeys.has(key)) {
63239
- if (key.match(regexp) != null) {
63240
- throw new BSONError('key ' + key + ' must not contain null bytes');
63241
- }
63242
- if (checkKeys) {
63243
- if ('$' === key[0]) {
63244
- throw new BSONError('key ' + key + " must not start with '$'");
63245
- }
63246
- else if (key.includes('.')) {
63247
- throw new BSONError('key ' + key + " must not contain '.'");
64042
+ const tag = value[bsonType];
64043
+ if (tag === 'ObjectId') {
64044
+ index = serializeObjectId(buffer, key, value, index);
64045
+ }
64046
+ else if (tag === 'Decimal128') {
64047
+ index = serializeDecimal128(buffer, key, value, index);
64048
+ }
64049
+ else if (tag === 'Long' || tag === 'Timestamp') {
64050
+ index = serializeLong(buffer, key, value, index);
64051
+ }
64052
+ else if (tag === 'Double') {
64053
+ index = serializeDouble(buffer, key, value, index);
64054
+ }
64055
+ else if (tag === 'Code') {
64056
+ const codeValue = value;
64057
+ if (codeValue.scope && typeof codeValue.scope === 'object') {
64058
+ buffer[index++] = BSON_DATA_CODE_W_SCOPE;
64059
+ index += ByteUtils.encodeUTF8Into(buffer, key, index);
64060
+ buffer[index++] = 0x00;
64061
+ const codeTotalSizeIndex = index;
64062
+ index += 4;
64063
+ const functionString = codeValue.code;
64064
+ const codeSize = ByteUtils.encodeUTF8Into(buffer, functionString, index + 4) + 1;
64065
+ NumberUtils.setInt32LE(buffer, index, codeSize);
64066
+ buffer[index + 4 + codeSize - 1] = 0;
64067
+ index = index + codeSize + 4;
64068
+ const scope = codeValue.scope;
64069
+ if (path.has(scope)) {
64070
+ throw new BSONError('Cannot convert circular structure to BSON');
63248
64071
  }
64072
+ path.add(scope);
64073
+ currentFrame = makeFrame(scope, index, codeTotalSizeIndex, frame, frame.checkKeys, frame.ignoreUndefined);
64074
+ index += 4;
64075
+ }
64076
+ else {
64077
+ buffer[index++] = BSON_DATA_CODE;
64078
+ index += ByteUtils.encodeUTF8Into(buffer, key, index);
64079
+ buffer[index++] = 0x00;
64080
+ const functionString = codeValue.code.toString();
64081
+ const size = ByteUtils.encodeUTF8Into(buffer, functionString, index + 4) + 1;
64082
+ NumberUtils.setInt32LE(buffer, index, size);
64083
+ index = index + 4 + size - 1;
64084
+ buffer[index++] = 0;
63249
64085
  }
63250
64086
  }
63251
- if (value === undefined) {
63252
- if (ignoreUndefined === false)
63253
- index = serializeNull(buffer, key, value, index);
63254
- }
63255
- else if (value === null) {
63256
- index = serializeNull(buffer, key, value, index);
63257
- }
63258
- else if (type === 'string') {
63259
- index = serializeString(buffer, key, value, index);
64087
+ else if (tag === 'Binary') {
64088
+ index = serializeBinary(buffer, key, value, index);
63260
64089
  }
63261
- else if (type === 'number') {
63262
- index = serializeNumber(buffer, key, value, index);
64090
+ else if (tag === 'BSONSymbol') {
64091
+ index = serializeSymbol(buffer, key, value, index);
63263
64092
  }
63264
- else if (type === 'bigint') {
63265
- index = serializeBigInt(buffer, key, value, index);
64093
+ else if (tag === 'DBRef') {
64094
+ const dbref = value;
64095
+ const orderedValues = Object.assign({ $ref: dbref.collection, $id: dbref.oid }, dbref.db != null ? { $db: dbref.db } : null, dbref.fields);
64096
+ buffer[index++] = BSON_DATA_OBJECT;
64097
+ index += ByteUtils.encodeUTF8Into(buffer, key, index);
64098
+ buffer[index++] = 0x00;
64099
+ path.add(orderedValues);
64100
+ currentFrame = makeFrame(orderedValues, index, null, frame, false, true);
64101
+ index += 4;
63266
64102
  }
63267
- else if (type === 'boolean') {
63268
- index = serializeBoolean(buffer, key, value, index);
64103
+ else if (tag === 'BSONRegExp') {
64104
+ index = serializeBSONRegExp(buffer, key, value, index);
63269
64105
  }
63270
- else if (type === 'object' && value._bsontype == null) {
63271
- if (value instanceof Date || isDate(value)) {
63272
- index = serializeDate(buffer, key, value, index);
63273
- }
63274
- else if (value instanceof Uint8Array || isUint8Array(value)) {
63275
- index = serializeBuffer(buffer, key, value, index);
63276
- }
63277
- else if (value instanceof RegExp || isRegExp(value)) {
63278
- index = serializeRegExp(buffer, key, value, index);
63279
- }
63280
- else {
63281
- index = serializeObject(buffer, key, value, index, checkKeys, depth, serializeFunctions, ignoreUndefined, path);
63282
- }
64106
+ else if (tag === 'Int32') {
64107
+ index = serializeInt32(buffer, key, value, index);
63283
64108
  }
63284
- else if (type === 'object') {
63285
- if (value[BSON_VERSION_SYMBOL] !== BSON_MAJOR_VERSION) {
63286
- throw new BSONVersionError();
63287
- }
63288
- else if (value._bsontype === 'ObjectId') {
63289
- index = serializeObjectId(buffer, key, value, index);
63290
- }
63291
- else if (value._bsontype === 'Decimal128') {
63292
- index = serializeDecimal128(buffer, key, value, index);
63293
- }
63294
- else if (value._bsontype === 'Long' || value._bsontype === 'Timestamp') {
63295
- index = serializeLong(buffer, key, value, index);
63296
- }
63297
- else if (value._bsontype === 'Double') {
63298
- index = serializeDouble(buffer, key, value, index);
63299
- }
63300
- else if (value._bsontype === 'Code') {
63301
- index = serializeCode(buffer, key, value, index, checkKeys, depth, serializeFunctions, ignoreUndefined, path);
63302
- }
63303
- else if (value._bsontype === 'Binary') {
63304
- index = serializeBinary(buffer, key, value, index);
63305
- }
63306
- else if (value._bsontype === 'BSONSymbol') {
63307
- index = serializeSymbol(buffer, key, value, index);
63308
- }
63309
- else if (value._bsontype === 'DBRef') {
63310
- index = serializeDBRef(buffer, key, value, index, depth, serializeFunctions, path);
63311
- }
63312
- else if (value._bsontype === 'BSONRegExp') {
63313
- index = serializeBSONRegExp(buffer, key, value, index);
63314
- }
63315
- else if (value._bsontype === 'Int32') {
63316
- index = serializeInt32(buffer, key, value, index);
63317
- }
63318
- else if (value._bsontype === 'MinKey' || value._bsontype === 'MaxKey') {
63319
- index = serializeMinMax(buffer, key, value, index);
63320
- }
63321
- else if (typeof value._bsontype !== 'undefined') {
63322
- throw new BSONError(`Unrecognized or invalid _bsontype: ${String(value._bsontype)}`);
63323
- }
64109
+ else if (tag === 'MinKey' || tag === 'MaxKey') {
64110
+ index = serializeMinMax(buffer, key, value, index);
63324
64111
  }
63325
- else if (type === 'function' && serializeFunctions) {
63326
- index = serializeFunction(buffer, key, value, index);
64112
+ else if (typeof value._bsontype !== 'undefined') {
64113
+ throw new BSONError(`Unrecognized or invalid _bsontype: ${String(value._bsontype)}`);
63327
64114
  }
63328
64115
  }
64116
+ else if (type === 'function' && serializeFunctions) {
64117
+ index = serializeFunction(buffer, key, value, index);
64118
+ }
63329
64119
  }
63330
- path.delete(object);
63331
- buffer[index++] = 0x00;
63332
- const size = index - startingIndex;
63333
- startingIndex += NumberUtils.setInt32LE(buffer, startingIndex, size);
63334
64120
  return index;
63335
64121
  }
63336
64122
 
@@ -63613,21 +64399,22 @@ function parse(text, options) {
63613
64399
  return deserializeValue(value, ejsonOptions);
63614
64400
  });
63615
64401
  }
63616
- function stringify(value, replacer, space, options) {
63617
- if (space != null && typeof space === 'object') {
63618
- options = space;
63619
- space = 0;
64402
+ function stringify(value, replacerOrOptions, spaceOrOptions, options) {
64403
+ if (spaceOrOptions != null && typeof spaceOrOptions === 'object') {
64404
+ options = spaceOrOptions;
64405
+ spaceOrOptions = undefined;
63620
64406
  }
63621
- if (replacer != null && typeof replacer === 'object' && !Array.isArray(replacer)) {
63622
- options = replacer;
63623
- replacer = undefined;
63624
- space = 0;
64407
+ if (replacerOrOptions != null &&
64408
+ typeof replacerOrOptions === 'object' &&
64409
+ !Array.isArray(replacerOrOptions)) {
64410
+ options = replacerOrOptions;
64411
+ replacerOrOptions = undefined;
63625
64412
  }
63626
64413
  const serializeOptions = Object.assign({ relaxed: true, legacy: false }, options, {
63627
64414
  seenObjects: [{ propertyName: '(root)', obj: null }]
63628
64415
  });
63629
64416
  const doc = serializeValue(value, serializeOptions);
63630
- return JSON.stringify(doc, replacer, space);
64417
+ return JSON.stringify(doc, replacerOrOptions, spaceOrOptions);
63631
64418
  }
63632
64419
  function EJSONserialize(value, options) {
63633
64420
  options = options || {};
@@ -63789,7 +64576,7 @@ function serialize(object, options = {}) {
63789
64576
  if (buffer.length < minInternalBufferSize) {
63790
64577
  buffer = ByteUtils.allocate(minInternalBufferSize);
63791
64578
  }
63792
- const serializationIndex = serializeInto(buffer, object, checkKeys, 0, 0, serializeFunctions, ignoreUndefined, null);
64579
+ const serializationIndex = serializeInto(buffer, object, checkKeys, 0, serializeFunctions, ignoreUndefined, null);
63793
64580
  const finishedBuffer = ByteUtils.allocateUnsafe(serializationIndex);
63794
64581
  finishedBuffer.set(buffer.subarray(0, serializationIndex), 0);
63795
64582
  return finishedBuffer;
@@ -63799,7 +64586,7 @@ function serializeWithBufferAndIndex(object, finalBuffer, options = {}) {
63799
64586
  const serializeFunctions = typeof options.serializeFunctions === 'boolean' ? options.serializeFunctions : false;
63800
64587
  const ignoreUndefined = typeof options.ignoreUndefined === 'boolean' ? options.ignoreUndefined : true;
63801
64588
  const startIndex = typeof options.index === 'number' ? options.index : 0;
63802
- const serializationIndex = serializeInto(buffer, object, checkKeys, 0, 0, serializeFunctions, ignoreUndefined, null);
64589
+ const serializationIndex = serializeInto(buffer, object, checkKeys, 0, serializeFunctions, ignoreUndefined, null);
63803
64590
  finalBuffer.set(buffer.subarray(0, serializationIndex), startIndex);
63804
64591
  return startIndex + serializationIndex - 1;
63805
64592
  }
@@ -64975,7 +65762,7 @@ var src_default = VueToastificationPlugin;
64975
65762
  (module) {
64976
65763
 
64977
65764
  "use strict";
64978
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.5.7","description":"AI-Powered Data Workspace for MongoDB: Turn your MongoDB data into dashboards, maps, and interactive workflows - powered by your Mongoose models and AI.","homepage":"https://mongoosestudio.app/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"license":"Apache-2.0","dependencies":{"@ai-sdk/anthropic":"2.x","@ai-sdk/google":"2.x","@ai-sdk/openai":"2.x","ace-builds":"^1.43.6","ai":"5.x","archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.2.0","marked":"15.0.12","mpath":"^0.9.0","node-inspect-extracted":"3.x","regexp.escape":"^2.0.1","tailwindcss":"3.4.0","time-commando":"1.0.1","typescript":"^6.0.2","vue":"3.x","vue-toastification":"^2.0.0-rc.5","webpack":"5.x","xss":"^1.0.15"},"peerDependencies":{"@nestjs/common":"10.x || 11.x","mongoose":"7.x || 8.x || ^9.0.0"},"peerDependenciesMeta":{"@nestjs/common":{"optional":true}},"optionalPeerDependencies":{"@mongoosejs/task":"0.5.x || 0.6.x"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","@nestjs/common":"^11.1.9","express":"4.x","mocha":"10.2.0","mongodb-memory-server":"^11.0.1","mongoose":"9.x","sinon":"^21.0.1"},"scripts":{"lint":"eslint .","seed":"node seed/index.js","start":"node ./local.js","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js","test:frontend":"mocha test/frontend/*.test.js","eval:agent":"node eval/agent/run.js"}}');
65765
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.5.9","description":"AI-Powered Data Workspace for MongoDB: Turn your MongoDB data into dashboards, maps, and interactive workflows - powered by your Mongoose models and AI.","homepage":"https://mongoosestudio.app/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"license":"Apache-2.0","dependencies":{"@ai-sdk/anthropic":"2.x","@ai-sdk/google":"2.x","@ai-sdk/openai":"2.x","ace-builds":"^1.43.6","ai":"5.x","archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.2.0","marked":"15.0.12","mpath":"^0.9.0","node-inspect-extracted":"3.x","regexp.escape":"^2.0.1","tailwindcss":"3.4.0","time-commando":"1.0.1","typescript":"^6.0.2","vue":"3.x","vue-toastification":"^2.0.0-rc.5","webpack":"5.x","xss":"^1.0.15"},"peerDependencies":{"@nestjs/common":"10.x || 11.x","mongoose":"7.x || 8.x || ^9.0.0"},"peerDependenciesMeta":{"@nestjs/common":{"optional":true}},"optionalPeerDependencies":{"@mongoosejs/task":"0.5.x || 0.6.x"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","@nestjs/common":"^11.1.9","express":"4.x","mocha":"10.2.0","mongodb-memory-server":"^11.0.1","mongoose":"9.x","sinon":"^21.0.1"},"scripts":{"lint":"eslint .","seed":"node seed/index.js","start":"node ./local.js","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js","test:frontend":"mocha test/frontend/*.test.js","eval:agent":"node eval/agent/run.js"}}');
64979
65766
 
64980
65767
  /***/ }
64981
65768