lutaml-jsonschema 0.1.10 → 0.1.12

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c2817082f5751466a6e7bae6f77310304d3b58127a392c1e60341d561b65706a
4
- data.tar.gz: 2c6f4b882f26600890a7a21a241e3a3709fa155df21097ca3c484513cef5e4ba
3
+ metadata.gz: 0ad24a6671a3bcf4e811c7588dbd4ca4fff96d589d625a7e6dd2507a74a115d4
4
+ data.tar.gz: 947415a532ef91f5287564a63a422f9c5021e01b5ec43a174d8e68b53e81dcca
5
5
  SHA512:
6
- metadata.gz: 4f1ca6699188d725ac053c230043316a96dbdb1fda2cf1c880a48e848ab31c6d4270e19f37f392c10d3205eafb27f529f5443f00fa39d20ec5f7f06dc32daa64
7
- data.tar.gz: 905835a47ca2348e8bd2cd5b5f2bc526a115782fbd3264c61b212e7c61192e87d6dbd1e9193f0d9598c9f4bf28916f410cf8fcee0919f1b747cf8f6119ac0516
6
+ metadata.gz: d76f7513a5de8fe76529844d0a110f1901f3f9fc4968213f3ae872505370aec7ffd71ab6d349745a9af383b03d6d32ce19f972eccf585c6efd91281cc3d1f6e6
7
+ data.tar.gz: 72a9b35c92992f4ac0fa82f63b477b362bb358b752d1deb773c3613a29e07ea0b4cf144dbaf11fd43b1c1b5eec3b5719ad72e2d3064bea17e112f1dafe55fc0a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.1.12] - 2026-05-10
4
+
5
+ ### Redoc-style UX improvements (round 6)
6
+
7
+ - Property title display in type row when it differs from name (Redoc TypeTitle pattern)
8
+ - Copy fallback using `execCommand('copy')` for HTTP contexts where clipboard API unavailable
9
+ - Dark panel full-height background via BackgroundStub `::before` pattern
10
+ - Search result description snippets in search modal
11
+ - Tooltip with directional CSS arrow on copy buttons
12
+ - DetailPanel properties table: shows title, format with angle brackets, default value, enum count, inline examples
13
+
14
+ ## [0.1.11] - 2026-05-09
15
+
16
+ ### Redoc-style UX improvements (round 5)
17
+
18
+ - Dark JSON preview panel with themed syntax colors in SchemaBuilder
19
+ - Select-on-click for source viewer and builder JSON blocks (double-click to select all)
20
+ - Print stylesheet: hides sidebar, header, controls; content prints full-width
21
+ - Format badges display with angle brackets (`<email>`) and accent color
22
+ - Responsive builder layout: stacks vertically on mobile (<768px)
23
+
3
24
  ## [0.1.10] - 2026-05-09
4
25
 
5
26
  ### Redoc-style UX improvements
@@ -221,18 +221,28 @@
221
221
  <tr v-for="prop in properties" :key="prop.name">
222
222
  <td>
223
223
  <span class="font-mono">{{ prop.name }}</span>
224
+ <span v-if="prop.title && prop.title !== prop.name" class="prop-title">({{ prop.title }})</span>
224
225
  <span v-if="prop.deprecated" class="badge badge-deprecated-sm">deprecated</span>
225
226
  </td>
226
227
  <td>
227
228
  <span class="prop-type">{{ prop.type || 'any' }}</span>
228
- <span v-if="prop.format" class="prop-format">{{ prop.format }}</span>
229
+ <span v-if="prop.format" class="prop-format">&lt;{{ prop.format }}&gt;</span>
229
230
  <span v-if="prop.itemsType" class="prop-format">[{{ prop.itemsType }}]</span>
231
+ <span v-if="prop.default != null" class="prop-default">default: {{ prop.default }}</span>
232
+ <span v-if="prop.enum?.length" class="prop-enum">{{ prop.enum.length }} values</span>
230
233
  </td>
231
234
  <td>
232
235
  <span v-if="prop.required" class="badge badge-required-sm">yes</span>
233
236
  <span v-else class="text-muted">no</span>
234
237
  </td>
235
- <td class="text-secondary">{{ prop.description || '—' }}</td>
238
+ <td>
239
+ <span class="text-secondary">{{ prop.description || '—' }}</span>
240
+ <div v-if="prop.examples?.length" class="prop-examples">
241
+ <span class="prop-examples-label">Examples:</span>
242
+ <span v-for="(ex, i) in prop.examples.slice(0, 3)" :key="i" class="prop-example-chip">{{ typeof ex === 'object' ? JSON.stringify(ex) : ex }}</span>
243
+ <span v-if="prop.examples.length > 3" class="text-muted">+{{ prop.examples.length - 3 }} more</span>
244
+ </div>
245
+ </td>
236
246
  </tr>
237
247
  </tbody>
238
248
  </table>
@@ -564,8 +574,57 @@ const definitionItem = computed(() => {
564
574
 
565
575
  .prop-format {
566
576
  font-size: var(--text-xs);
567
- color: var(--text-muted);
577
+ color: var(--color-accent);
568
578
  margin-left: var(--space-1);
579
+ font-family: var(--font-mono);
580
+ }
581
+
582
+ .prop-title {
583
+ display: block;
584
+ font-size: var(--text-xs);
585
+ color: var(--text-secondary);
586
+ font-style: italic;
587
+ }
588
+
589
+ .prop-default {
590
+ display: block;
591
+ font-size: 10px;
592
+ color: var(--text-muted);
593
+ font-family: var(--font-mono);
594
+ }
595
+
596
+ .prop-enum {
597
+ display: block;
598
+ font-size: 10px;
599
+ color: var(--color-teal);
600
+ background: var(--color-teal-alpha);
601
+ padding: 1px 4px;
602
+ border-radius: 2px;
603
+ display: inline-block;
604
+ margin-top: 2px;
605
+ }
606
+
607
+ .prop-examples {
608
+ margin-top: var(--space-1);
609
+ display: flex;
610
+ flex-wrap: wrap;
611
+ gap: 4px;
612
+ align-items: center;
613
+ }
614
+
615
+ .prop-examples-label {
616
+ font-size: 10px;
617
+ color: var(--text-muted);
618
+ font-weight: 500;
619
+ }
620
+
621
+ .prop-example-chip {
622
+ font-size: 10px;
623
+ font-family: var(--font-mono);
624
+ background: var(--bg-secondary);
625
+ padding: 1px 4px;
626
+ border-radius: 2px;
627
+ color: var(--text-secondary);
569
628
  }
570
629
 
571
630
  .badge-type {
@@ -16,7 +16,8 @@
16
16
  <span class="font-mono">{{ field.prop.name }}</span>
17
17
  </button>
18
18
  <span class="field-type-badge" :class="typeBadgeClass(field.prop)">{{ displayType(field.prop, field.resolvedDef?.title || field.resolvedDef?.name) }}</span>
19
- <span v-if="field.prop.format" class="field-format-badge">{{ field.prop.format }}</span>
19
+ <span v-if="field.prop.title && field.prop.title !== field.prop.name" class="field-title-badge">{{ field.prop.title }}</span>
20
+ <span v-if="field.prop.format" class="field-format-badge">&lt;{{ field.prop.format }}&gt;</span>
20
21
  <span v-if="field.prop.contentMediaType" class="field-format-badge">content-type: {{ field.prop.contentMediaType }}</span>
21
22
  <span v-if="field.prop.contentEncoding" class="field-format-badge">encoding: {{ field.prop.contentEncoding }}</span>
22
23
  <span v-if="field.prop.const != null" class="const-badge font-mono">const: {{ field.prop.const }}</span>
@@ -206,17 +207,17 @@
206
207
  <div class="builder-preview">
207
208
  <div class="preview-inner">
208
209
  <div class="preview-toolbar">
209
- <span class="toolbar-label text-muted">JSON Preview</span>
210
+ <span class="toolbar-label">JSON Preview</span>
210
211
  <div class="toolbar-actions">
211
- <button class="btn btn-ghost btn-sm" @click="expandAllJson">Expand all</button>
212
- <button class="btn btn-ghost btn-sm" @click="collapseAllJson">Collapse all</button>
213
- <button class="btn btn-ghost btn-sm copy-btn-wrap" @click="copyJson">
212
+ <button class="btn btn-sm btn-dark-panel" @click="expandAllJson">Expand all</button>
213
+ <button class="btn btn-sm btn-dark-panel" @click="collapseAllJson">Collapse all</button>
214
+ <button class="btn btn-sm btn-dark-panel copy-btn-wrap" @click="copyJson">
214
215
  Copy
215
216
  <span v-if="copied" class="copy-tooltip">Copied!</span>
216
217
  </button>
217
218
  </div>
218
219
  </div>
219
- <pre ref="jsonBlockRef" class="json-block" @click="handleJsonClick" v-html="highlightedJson"></pre>
220
+ <pre ref="jsonBlockRef" class="json-block" @click="handleJsonClick" @dblclick="selectJsonBlock" v-html="highlightedJson"></pre>
220
221
  </div>
221
222
  </div>
222
223
  </div>
@@ -246,6 +247,7 @@ import {
246
247
  import { renderInlineMarkdown } from '../composables/useMarkdownLite'
247
248
  import { jsonToCollapsibleHtml } from '../composables/useJsonViewer'
248
249
  import type { BuilderField } from '../composables/useBuilderField'
250
+ import { copyToClipboard } from '../composables/useClipboard'
249
251
 
250
252
  const schemaStore = useSchemaStore()
251
253
  const uiStore = useUiStore()
@@ -391,6 +393,18 @@ function handleJsonClick(event: MouseEvent) {
391
393
  }
392
394
  }
393
395
 
396
+ function selectJsonBlock() {
397
+ const el = jsonBlockRef.value
398
+ if (!el) return
399
+ const range = document.createRange()
400
+ range.selectNodeContents(el)
401
+ const selection = window.getSelection()
402
+ if (selection) {
403
+ selection.removeAllRanges()
404
+ selection.addRange(range)
405
+ }
406
+ }
407
+
394
408
  const MAX_PATTERN_LEN = 45
395
409
 
396
410
  function typeBadgeClass(prop: SpaProperty): string {
@@ -425,11 +439,11 @@ function togglePattern(pattern: string) {
425
439
  }
426
440
 
427
441
  async function copyJson() {
428
- try {
429
- await navigator.clipboard.writeText(outputJson.value)
442
+ const ok = await copyToClipboard(outputJson.value)
443
+ if (ok) {
430
444
  copied.value = true
431
445
  setTimeout(() => { copied.value = false }, 2000)
432
- } catch { /* noop */ }
446
+ }
433
447
  }
434
448
  </script>
435
449
 
@@ -551,11 +565,19 @@ async function copyJson() {
551
565
 
552
566
  .field-format-badge {
553
567
  font-size: 10px;
554
- color: var(--text-muted);
555
- background: var(--bg-secondary);
568
+ color: var(--color-accent);
569
+ background: var(--color-accent-alpha);
556
570
  padding: 1px 5px;
557
571
  border-radius: var(--radius-sm);
558
572
  font-family: var(--font-mono);
573
+ font-weight: 500;
574
+ }
575
+
576
+ .field-title-badge {
577
+ font-size: 10px;
578
+ font-style: italic;
579
+ color: var(--text-secondary);
580
+ flex-shrink: 0;
559
581
  }
560
582
 
561
583
  .deprecated-badge {
@@ -988,6 +1010,16 @@ async function copyJson() {
988
1010
  .builder-preview {
989
1011
  position: sticky;
990
1012
  top: var(--space-4);
1013
+ z-index: 0;
1014
+ }
1015
+
1016
+ .builder-preview::before {
1017
+ content: '';
1018
+ position: absolute;
1019
+ inset: 0;
1020
+ background: var(--panel-dark-bg);
1021
+ border-radius: var(--radius-md);
1022
+ z-index: -1;
991
1023
  }
992
1024
 
993
1025
  .preview-inner {
@@ -1000,6 +1032,7 @@ async function copyJson() {
1000
1032
  display: flex;
1001
1033
  align-items: center;
1002
1034
  justify-content: space-between;
1035
+ color: var(--panel-dark-muted);
1003
1036
  }
1004
1037
 
1005
1038
  .toolbar-label {
@@ -1011,9 +1044,18 @@ async function copyJson() {
1011
1044
  padding: var(--space-1) var(--space-2);
1012
1045
  }
1013
1046
 
1047
+ .btn-dark-panel {
1048
+ color: var(--panel-dark-muted);
1049
+ }
1050
+
1051
+ .btn-dark-panel:hover {
1052
+ color: var(--panel-dark-text);
1053
+ background: rgba(255, 255, 255, 0.08);
1054
+ }
1055
+
1014
1056
  .json-block {
1015
- background: var(--bg-secondary);
1016
- border: 1px solid var(--border-light);
1057
+ background: var(--panel-dark-bg);
1058
+ border: 1px solid var(--panel-dark-bg);
1017
1059
  border-radius: var(--radius-md);
1018
1060
  padding: var(--space-4);
1019
1061
  overflow-x: auto;
@@ -1023,7 +1065,7 @@ async function copyJson() {
1023
1065
  max-height: 70vh;
1024
1066
  overflow-y: auto;
1025
1067
  font-family: var(--font-mono);
1026
- color: var(--text-primary);
1068
+ color: var(--panel-dark-text);
1027
1069
  }
1028
1070
 
1029
1071
  .json-block :deep(ul) {
@@ -1038,7 +1080,7 @@ async function copyJson() {
1038
1080
 
1039
1081
  .json-block :deep(.jv-toggle) {
1040
1082
  background: none;
1041
- border: 1px solid var(--border-light);
1083
+ border: 1px solid var(--panel-dark-muted);
1042
1084
  border-radius: 2px;
1043
1085
  cursor: pointer;
1044
1086
  width: 14px;
@@ -1064,7 +1106,7 @@ async function copyJson() {
1064
1106
 
1065
1107
  .json-block :deep(.jv-ellipsis) {
1066
1108
  display: none;
1067
- color: var(--text-muted);
1109
+ color: var(--panel-dark-muted);
1068
1110
  font-size: var(--text-xs);
1069
1111
  font-style: italic;
1070
1112
  margin-left: 4px;
@@ -1079,43 +1121,40 @@ async function copyJson() {
1079
1121
  }
1080
1122
 
1081
1123
  .json-block :deep(.jv-key) {
1082
- color: var(--color-primary-dark);
1124
+ color: var(--panel-dark-key);
1083
1125
  }
1084
1126
 
1085
1127
  .json-block :deep(.jv-punct) {
1086
- color: var(--text-muted);
1128
+ color: var(--panel-dark-muted);
1087
1129
  }
1088
1130
 
1089
1131
  .json-block :deep(.jv-string) {
1090
- color: var(--color-green);
1132
+ color: var(--panel-dark-string);
1091
1133
  }
1092
1134
 
1093
1135
  .json-block :deep(.jv-number) {
1094
- color: var(--color-orange);
1136
+ color: var(--panel-dark-number);
1095
1137
  }
1096
1138
 
1097
1139
  .json-block :deep(.jv-boolean) {
1098
- color: var(--color-accent);
1140
+ color: var(--panel-dark-boolean);
1099
1141
  }
1100
1142
 
1101
1143
  .json-block :deep(.jv-null) {
1102
- color: var(--text-muted);
1144
+ color: var(--panel-dark-null);
1103
1145
  font-style: italic;
1104
1146
  }
1105
1147
 
1106
1148
  .json-block :deep(.jv-link) {
1107
- color: var(--color-primary);
1149
+ color: var(--panel-dark-string);
1108
1150
  text-decoration: underline;
1109
1151
  }
1110
1152
 
1111
1153
  .json-block :deep(.jv-row:hover) {
1112
- background: var(--bg-hover);
1154
+ background: rgba(255, 255, 255, 0.06);
1113
1155
  border-radius: 2px;
1114
1156
  }
1115
1157
 
1116
- :root[data-theme="dark"] .json-block :deep(.jv-key) { color: var(--color-primary-light); }
1117
- :root[data-theme="dark"] .json-block :deep(.jv-string) { color: var(--color-teal); }
1118
-
1119
1158
  .toolbar-actions {
1120
1159
  display: flex;
1121
1160
  gap: var(--space-1);
@@ -1128,7 +1167,7 @@ async function copyJson() {
1128
1167
 
1129
1168
  .copy-tooltip {
1130
1169
  position: absolute;
1131
- bottom: calc(100% + 4px);
1170
+ bottom: calc(100% + 8px);
1132
1171
  left: 50%;
1133
1172
  transform: translateX(-50%);
1134
1173
  background: var(--bg-elevated);
@@ -1143,6 +1182,16 @@ async function copyJson() {
1143
1182
  animation: tooltipFade var(--transition-slow);
1144
1183
  }
1145
1184
 
1185
+ .copy-tooltip::after {
1186
+ content: '';
1187
+ position: absolute;
1188
+ top: 100%;
1189
+ left: 50%;
1190
+ margin-left: -4px;
1191
+ border: 4px solid transparent;
1192
+ border-top-color: var(--bg-elevated);
1193
+ }
1194
+
1146
1195
  @keyframes tooltipFade {
1147
1196
  from { opacity: 0; transform: translateX(-50%) translateY(2px); }
1148
1197
  to { opacity: 1; transform: translateX(-50%) translateY(0); }
@@ -52,6 +52,7 @@
52
52
  <div class="result-content">
53
53
  <span class="result-name" v-html="highlightMatch(result.name)"></span>
54
54
  <span class="result-schema">{{ result.schemaName }}</span>
55
+ <span v-if="result.description" class="result-desc">{{ result.description }}</span>
55
56
  </div>
56
57
  <span :class="['badge', `badge-${result.type}`]">{{ result.type }}</span>
57
58
  </div>
@@ -235,6 +236,15 @@ function highlightMatch(text: string): string {
235
236
  color: var(--text-muted);
236
237
  }
237
238
 
239
+ .result-desc {
240
+ font-size: var(--text-xs);
241
+ color: var(--text-muted);
242
+ display: -webkit-box;
243
+ -webkit-line-clamp: 1;
244
+ -webkit-box-orient: vertical;
245
+ overflow: hidden;
246
+ }
247
+
238
248
  .badge-schema {
239
249
  background: var(--badge-schema-bg);
240
250
  color: var(--badge-schema);
@@ -0,0 +1,21 @@
1
+ function fallbackCopy(text: string): boolean {
2
+ const ta = document.createElement('textarea')
3
+ ta.value = text
4
+ ta.style.cssText = 'position:fixed;top:0;left:0;width:2em;height:2em;padding:0;border:none;outline:none;box-shadow:none;background:transparent'
5
+ document.body.appendChild(ta)
6
+ ta.select()
7
+ let ok = false
8
+ try { ok = document.execCommand('copy') } catch { /* noop */ }
9
+ document.body.removeChild(ta)
10
+ return ok
11
+ }
12
+
13
+ export async function copyToClipboard(text: string): Promise<boolean> {
14
+ if (navigator.clipboard?.writeText) {
15
+ try {
16
+ await navigator.clipboard.writeText(text)
17
+ return true
18
+ } catch { /* fall through */ }
19
+ }
20
+ return fallbackCopy(text)
21
+ }
@@ -96,6 +96,15 @@
96
96
  --type-array-bg: rgba(139, 92, 246, 0.1);
97
97
  --type-null: #A8A29E;
98
98
  --type-null-bg: rgba(168, 162, 158, 0.1);
99
+
100
+ --panel-dark-bg: #263238;
101
+ --panel-dark-text: #e8edf4;
102
+ --panel-dark-muted: #8b9db5;
103
+ --panel-dark-key: #80cbc4;
104
+ --panel-dark-string: #a5d6a7;
105
+ --panel-dark-number: #ffcc80;
106
+ --panel-dark-boolean: #90caf9;
107
+ --panel-dark-null: #8b9db5;
99
108
  }
100
109
 
101
110
  :root[data-theme="dark"] {
@@ -143,6 +152,15 @@
143
152
  --type-array-bg: rgba(167, 139, 250, 0.15);
144
153
  --type-null: #6b7a8f;
145
154
  --type-null-bg: rgba(107, 122, 143, 0.15);
155
+
156
+ --panel-dark-bg: #1e2a35;
157
+ --panel-dark-text: #dce4f0;
158
+ --panel-dark-muted: #7b8fa8;
159
+ --panel-dark-key: #4db6ac;
160
+ --panel-dark-string: #81c784;
161
+ --panel-dark-number: #ffb74d;
162
+ --panel-dark-boolean: #64b5f6;
163
+ --panel-dark-null: #7b8fa8;
146
164
  }
147
165
 
148
166
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
@@ -233,6 +251,19 @@ button { font-family: inherit; font-size: inherit; cursor: pointer; border: none
233
251
  }
234
252
  }
235
253
 
254
+ @media print {
255
+ .sidebar, .sidebar-overlay, .app-header,
256
+ .view-toggle, .schema-actions,
257
+ .ctrl-expand, .field-check,
258
+ .source-toolbar, .copy-btn-wrap,
259
+ .section-actions { display: none !important; }
260
+ .app-layout { grid-template-columns: 1fr !important; }
261
+ .app-main { padding: 0 !important; max-width: 100% !important; }
262
+ .card, .def-card { break-inside: avoid; box-shadow: none; border: 1px solid #ccc; }
263
+ .field-row { break-inside: avoid; }
264
+ body { color: #000; background: #fff; }
265
+ }
266
+
236
267
  .text-muted { color: var(--text-muted); }
237
268
  .text-secondary { color: var(--text-secondary); }
238
269
  .font-mono { font-family: var(--font-mono); }
@@ -143,7 +143,7 @@
143
143
  </div>
144
144
  <div class="source-code-wrapper">
145
145
  <div class="source-lines" aria-hidden="true"><span v-for="n in sourceLineCount" :key="n">{{ n }}</span></div>
146
- <pre class="source-pre"><code v-html="highlightedSource"></code></pre>
146
+ <pre class="source-pre" @dblclick="selectSourceBlock"><code v-html="highlightedSource"></code></pre>
147
147
  </div>
148
148
  </div>
149
149
  <div v-else class="source-empty">
@@ -221,6 +221,7 @@ import { useSchemaStore } from '../stores/schemaStore'
221
221
  import SchemaBuilder from '../components/SchemaBuilder.vue'
222
222
  import { downloadFile } from '../composables/useDownload'
223
223
  import { renderInlineMarkdown } from '../composables/useMarkdownLite'
224
+ import { copyToClipboard } from '../composables/useClipboard'
224
225
  import type { SpaSchema } from '../types'
225
226
 
226
227
  const schemaStore = useSchemaStore()
@@ -264,9 +265,11 @@ const sourceLineCount = computed(() => {
264
265
  function copySource() {
265
266
  const src = schemaStore.selectedSchema?.sourceJson
266
267
  if (!src) return
267
- navigator.clipboard.writeText(src).then(() => {
268
- sourceCopied.value = true
269
- setTimeout(() => { sourceCopied.value = false }, 2000)
268
+ copyToClipboard(src).then(ok => {
269
+ if (ok) {
270
+ sourceCopied.value = true
271
+ setTimeout(() => { sourceCopied.value = false }, 2000)
272
+ }
270
273
  })
271
274
  }
272
275
 
@@ -285,6 +288,18 @@ function syntaxHighlight(json: string): string {
285
288
  })
286
289
  }
287
290
 
291
+ function selectSourceBlock() {
292
+ const el = document.querySelector('.source-pre') as HTMLElement | null
293
+ if (!el) return
294
+ const range = document.createRange()
295
+ range.selectNodeContents(el)
296
+ const selection = window.getSelection()
297
+ if (selection) {
298
+ selection.removeAllRanges()
299
+ selection.addRange(range)
300
+ }
301
+ }
302
+
288
303
  function selectSchema(name: string) {
289
304
  schemaStore.selectSchema(name)
290
305
  }
@@ -600,7 +615,7 @@ watch(() => schemaStore.selectedItemKey, (key) => {
600
615
 
601
616
  .copy-tooltip {
602
617
  position: absolute;
603
- bottom: calc(100% + 4px);
618
+ bottom: calc(100% + 8px);
604
619
  left: 50%;
605
620
  transform: translateX(-50%);
606
621
  background: var(--bg-elevated);
@@ -615,6 +630,16 @@ watch(() => schemaStore.selectedItemKey, (key) => {
615
630
  animation: tooltipFade var(--transition-slow);
616
631
  }
617
632
 
633
+ .copy-tooltip::after {
634
+ content: '';
635
+ position: absolute;
636
+ top: 100%;
637
+ left: 50%;
638
+ margin-left: -4px;
639
+ border: 4px solid transparent;
640
+ border-top-color: var(--bg-elevated);
641
+ }
642
+
618
643
  @keyframes tooltipFade {
619
644
  from { opacity: 0; transform: translateX(-50%) translateY(2px); }
620
645
  to { opacity: 1; transform: translateX(-50%) translateY(0); }
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Jsonschema
5
- VERSION = "0.1.10"
5
+ VERSION = "0.1.12"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lutaml-jsonschema
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.10
4
+ version: 0.1.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-05-09 00:00:00.000000000 Z
11
+ date: 2026-05-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -106,6 +106,7 @@ files:
106
106
  - frontend/src/components/SchemaBuilder.vue
107
107
  - frontend/src/components/SearchModal.vue
108
108
  - frontend/src/composables/useBuilderField.ts
109
+ - frontend/src/composables/useClipboard.ts
109
110
  - frontend/src/composables/useDefinitionResolver.ts
110
111
  - frontend/src/composables/useDownload.ts
111
112
  - frontend/src/composables/useJsonViewer.ts