lutaml-openapi 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "lutaml-openapi-docs",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview",
10
+ "test": "vitest run"
11
+ },
12
+ "dependencies": {
13
+ "flexsearch": "^0.7.43",
14
+ "pinia": "^2.1.0",
15
+ "vue": "^3.4.0",
16
+ "vue-router": "^4.3.0"
17
+ },
18
+ "devDependencies": {
19
+ "@vitejs/plugin-vue": "^5.0.0",
20
+ "jsdom": "^29.1.1",
21
+ "terser": "^5.46.2",
22
+ "typescript": "^5.3.0",
23
+ "vite": "^5.4.0",
24
+ "vitest": "^4.1.5",
25
+ "vue-tsc": "^2.0.0"
26
+ }
27
+ }
@@ -0,0 +1,60 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref, provide } from 'vue'
3
+ import OverviewView from './components/OverviewView.vue'
4
+ import PathsView from './components/PathsView.vue'
5
+ import SchemasView from './components/SchemasView.vue'
6
+
7
+ const props = defineProps<{ openapi: Record<string, any> }>()
8
+ provide('openapi', props.openapi)
9
+
10
+ const filter = ref('')
11
+ const view = ref<'overview' | 'paths' | 'schemas'>('overview')
12
+ const views = [
13
+ { id: 'overview', label: 'Overview' },
14
+ { id: 'paths', label: 'Paths' },
15
+ { id: 'schemas', label: 'Schemas' },
16
+ ] as const
17
+
18
+ const info = computed(() => (props.openapi.info as any) || {})
19
+ const schemaCount = computed(
20
+ () => Object.keys(((props.openapi.components as any)?.schemas as any) || {}).length,
21
+ )
22
+ const pathCount = computed(() => Object.keys((props.openapi.paths as any) || {}).length)
23
+ </script>
24
+
25
+ <template>
26
+ <div class="docs">
27
+ <header class="docs-header">
28
+ <h1>{{ info.title || 'OpenAPI document' }}</h1>
29
+ <p v-if="info.version" class="version">
30
+ {{ info.title ? 'version' : '' }} {{ info.version }}
31
+ </p>
32
+ <input
33
+ v-model="filter"
34
+ class="filter"
35
+ type="search"
36
+ placeholder="Filter…"
37
+ aria-label="Filter sections"
38
+ />
39
+ <nav aria-label="Sections">
40
+ <button
41
+ v-for="v in views"
42
+ :key="v.id"
43
+ class="nav-btn"
44
+ :class="{ active: view === v.id }"
45
+ @click="view = v.id"
46
+ >
47
+ {{ v.label }}
48
+ </button>
49
+ </nav>
50
+ </header>
51
+ <main>
52
+ <OverviewView v-if="view === 'overview'" :filter="filter" />
53
+ <PathsView v-else-if="view === 'paths'" :filter="filter" />
54
+ <SchemasView v-else :filter="filter" />
55
+ </main>
56
+ <footer class="docs-footer">
57
+ <span>{{ schemaCount }} schemas · {{ pathCount }} paths</span>
58
+ </footer>
59
+ </div>
60
+ </template>
@@ -0,0 +1,13 @@
1
+ import { createApp } from 'vue'
2
+ import App from './App.vue'
3
+ import './style.css'
4
+
5
+ declare global {
6
+ interface Window {
7
+ OPENAPI_DATA?: Record<string, unknown>
8
+ }
9
+ }
10
+
11
+ const data = window.OPENAPI_DATA || {}
12
+ const app = createApp(App, { openapi: data })
13
+ app.mount('#app')
@@ -0,0 +1,28 @@
1
+ <script setup lang="ts">
2
+ import { inject, computed } from 'vue'
3
+
4
+ const openapi = inject<Record<string, any>>('openapi') || {}
5
+ const props = defineProps<{ filter: string }>()
6
+
7
+ const info = computed(() => (openapi.info as any) || {})
8
+ const servers = computed(() => (openapi.servers as any[]) || [])
9
+ const shown = computed(() => {
10
+ const q = props.filter.toLowerCase()
11
+ if (!q) return servers.value
12
+ return servers.value.filter((s) => JSON.stringify(s).toLowerCase().includes(q))
13
+ })
14
+ </script>
15
+
16
+ <template>
17
+ <section aria-label="Overview">
18
+ <h2>Overview</h2>
19
+ <p v-if="info.description">{{ info.description }}</p>
20
+ <h3 v-if="shown.length">Servers</h3>
21
+ <ul>
22
+ <li v-for="s in shown" :key="s.url">
23
+ <code>{{ s.url }}</code> — {{ s.protocol || 'https' }}
24
+ <span v-if="s.description">: {{ s.description }}</span>
25
+ </li>
26
+ </ul>
27
+ </section>
28
+ </template>
@@ -0,0 +1,46 @@
1
+ <script setup lang="ts">
2
+ import { inject, computed } from 'vue'
3
+
4
+ const openapi = inject<Record<string, any>>('openapi') || {}
5
+ const props = defineProps<{ filter: string }>()
6
+
7
+ const METHODS = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace']
8
+
9
+ const entries = computed(() => {
10
+ const paths = (openapi.paths as any) || {}
11
+ const q = props.filter.toLowerCase()
12
+ return Object.entries(paths)
13
+ .filter(([path, item]) => {
14
+ if (!q) return true
15
+ return (
16
+ path.toLowerCase().includes(q) ||
17
+ JSON.stringify(item).toLowerCase().includes(q)
18
+ )
19
+ })
20
+ .map(([path, item]) => ({
21
+ path,
22
+ operations: Object.entries(item as any)
23
+ .filter(([m, op]) => METHODS.includes(m) && op && typeof op === 'object')
24
+ .map(([m, op]) => ({
25
+ method: m.toUpperCase(),
26
+ summary: (op as any).summary || '',
27
+ description: (op as any).description || '',
28
+ })),
29
+ }))
30
+ })
31
+ </script>
32
+
33
+ <template>
34
+ <section aria-label="Paths">
35
+ <h2>Paths</h2>
36
+ <div v-for="e in entries" :key="e.path" class="path">
37
+ <h3><code>{{ e.path }}</code></h3>
38
+ <div v-for="op in e.operations" :key="op.method" class="op">
39
+ <span class="method" :class="op.method.toLowerCase()">{{ op.method }}</span>
40
+ <strong v-if="op.summary"> {{ op.summary }}</strong>
41
+ <p v-if="op.description">{{ op.description }}</p>
42
+ </div>
43
+ </div>
44
+ <p v-if="!entries.length">No paths match the filter.</p>
45
+ </section>
46
+ </template>
@@ -0,0 +1,54 @@
1
+ <script setup lang="ts">
2
+ import { inject, computed } from 'vue'
3
+
4
+ const openapi = inject<Record<string, any>>('openapi') || {}
5
+ const props = defineProps<{ filter: string }>()
6
+
7
+ const entries = computed(() => {
8
+ const schemas = ((openapi.components as any)?.schemas as any) || {}
9
+ const q = props.filter.toLowerCase()
10
+ return Object.entries(schemas)
11
+ .filter(([name, schema]) => {
12
+ if (!q) return true
13
+ return (
14
+ name.toLowerCase().includes(q) ||
15
+ JSON.stringify(schema).toLowerCase().includes(q)
16
+ )
17
+ })
18
+ .map(([name, schema]) => {
19
+ const s = schema as any
20
+ const properties = Object.entries(s.properties || {}).map(
21
+ ([pname, p]: [string, any]) => ({
22
+ name: pname,
23
+ type: Array.isArray(p.type) ? p.type.join(' | ') : p.type || '',
24
+ description: p.description || '',
25
+ }),
26
+ )
27
+ return { name, description: s.description || '', type: s.type, properties }
28
+ })
29
+ })
30
+ </script>
31
+
32
+ <template>
33
+ <section aria-label="Schemas">
34
+ <h2>Schemas</h2>
35
+ <div v-for="e in entries" :key="e.name" class="schema">
36
+ <h3>{{ e.name }}</h3>
37
+ <p v-if="e.description">{{ e.description }}</p>
38
+ <table v-if="e.properties.length">
39
+ <caption>Properties of {{ e.name }}</caption>
40
+ <thead>
41
+ <tr><th>Name</th><th>Type</th><th>Description</th></tr>
42
+ </thead>
43
+ <tbody>
44
+ <tr v-for="p in e.properties" :key="p.name">
45
+ <td><code>{{ p.name }}</code></td>
46
+ <td><code>{{ p.type }}</code></td>
47
+ <td>{{ p.description }}</td>
48
+ </tr>
49
+ </tbody>
50
+ </table>
51
+ </div>
52
+ <p v-if="!entries.length">No schemas match the filter.</p>
53
+ </section>
54
+ </template>
@@ -0,0 +1,410 @@
1
+ /* Lutaml-JSONSchema SPA - LutaML Branded Design */
2
+
3
+ :root {
4
+ --color-primary: #5b9cd4;
5
+ --color-primary-light: #7db5e2;
6
+ --color-primary-dark: #26489e;
7
+ --color-primary-alpha: rgba(91, 156, 212, 0.12);
8
+ --text-accent: #26489E;
9
+
10
+ --color-accent: #dbab3e;
11
+ --color-accent-light: #f0ca6e;
12
+ --color-accent-alpha: rgba(219, 171, 62, 0.12);
13
+
14
+ --color-teal: #60c3a7;
15
+ --color-teal-alpha: rgba(96, 195, 167, 0.12);
16
+ --color-orange: #ea5624;
17
+ --color-orange-alpha: rgba(234, 86, 36, 0.12);
18
+ --color-red: #b31f24;
19
+ --color-red-alpha: rgba(179, 31, 36, 0.12);
20
+ --color-green: #7cc242;
21
+ --color-green-alpha: rgba(124, 194, 66, 0.12);
22
+
23
+ --bg-primary: #FAFAF9;
24
+ --bg-secondary: #F5F5F4;
25
+ --bg-elevated: #FFFFFF;
26
+ --bg-overlay: rgba(28, 25, 23, 0.5);
27
+ --bg-hover: rgba(28, 25, 23, 0.04);
28
+ --bg-active: rgba(28, 25, 23, 0.06);
29
+
30
+ --text-primary: #1C1917;
31
+ --text-secondary: #57534E;
32
+ --text-muted: #57606A;
33
+
34
+ --border-light: #E7E5E4;
35
+ --border-medium: #D6D3D1;
36
+ --border-focus: #5b9cd4;
37
+
38
+ --badge-schema: #26489e;
39
+ --badge-schema-bg: rgba(38, 72, 158, 0.12);
40
+ --badge-property: #60c3a7;
41
+ --badge-property-bg: rgba(96, 195, 167, 0.15);
42
+ --badge-definition: #dbab3e;
43
+ --badge-definition-bg: rgba(219, 171, 62, 0.15);
44
+ --badge-required: #5b9cd4;
45
+ --badge-required-bg: rgba(91, 156, 212, 0.12);
46
+ --badge-deprecated: #b31f24;
47
+ --badge-deprecated-bg: rgba(179, 31, 36, 0.12);
48
+
49
+ --shadow-sm: 0 1px 2px rgba(28, 25, 23, 0.05);
50
+ --shadow-md: 0 4px 6px -1px rgba(28, 25, 23, 0.07), 0 2px 4px -1px rgba(28, 25, 23, 0.04);
51
+ --shadow-lg: 0 10px 15px -3px rgba(28, 25, 23, 0.08), 0 4px 6px -2px rgba(28, 25, 23, 0.04);
52
+
53
+ --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
54
+ --font-mono: 'JetBrains Mono', 'SF Mono', Monaco, 'Consolas', monospace;
55
+
56
+ --text-xs: 0.75rem;
57
+ --text-sm: 0.875rem;
58
+ --text-base: 1rem;
59
+ --text-lg: 1.125rem;
60
+ --text-xl: 1.25rem;
61
+ --text-2xl: 1.5rem;
62
+
63
+ --leading-tight: 1.25;
64
+ --leading-normal: 1.5;
65
+ --leading-relaxed: 1.75;
66
+
67
+ --space-1: 0.25rem;
68
+ --space-2: 0.5rem;
69
+ --space-3: 0.75rem;
70
+ --space-4: 1rem;
71
+ --space-5: 1.25rem;
72
+ --space-6: 1.5rem;
73
+ --space-8: 2rem;
74
+ --space-10: 2.5rem;
75
+ --space-12: 3rem;
76
+
77
+ --radius-sm: 0.25rem;
78
+ --radius-md: 0.375rem;
79
+ --radius-lg: 0.5rem;
80
+ --radius-xl: 0.75rem;
81
+
82
+ --transition-fast: 100ms ease;
83
+ --transition-normal: 150ms ease;
84
+ --transition-slow: 200ms ease-out;
85
+
86
+ --type-string: #7cc242;
87
+ --type-string-bg: rgba(124, 194, 66, 0.1);
88
+ --type-number: #ea5624;
89
+ --type-number-bg: rgba(234, 86, 36, 0.1);
90
+ --type-integer: #dbab3e;
91
+ --type-integer-bg: rgba(219, 171, 62, 0.1);
92
+ --type-boolean: #5b9cd4;
93
+ --type-boolean-bg: rgba(91, 156, 212, 0.1);
94
+ --type-object: #26489e;
95
+ --type-object-bg: rgba(38, 72, 158, 0.1);
96
+ --type-array: #8b5cf6;
97
+ --type-array-bg: rgba(139, 92, 246, 0.1);
98
+ --type-null: #A8A29E;
99
+ --type-null-bg: rgba(168, 162, 158, 0.1);
100
+
101
+ /* Schema theme — Redoc-inspired */
102
+ --schema-lines: rgba(91, 156, 212, 0.3);
103
+ --schema-nested-bg: #fafafa;
104
+ --schema-type-name: #57534E;
105
+ --schema-require-label: #b31f24;
106
+ --schema-require-bg: rgba(179, 31, 36, 0.08);
107
+ --schema-labels-size: 0.9em;
108
+ --schema-arrow-size: 1.1em;
109
+ --schema-arrow-color: #57534E;
110
+
111
+ --panel-dark-bg: #263238;
112
+ --panel-dark-text: #e8edf4;
113
+ --panel-dark-muted: #8b9db5;
114
+ --panel-dark-key: #80cbc4;
115
+ --panel-dark-string: #a5d6a7;
116
+ --panel-dark-number: #ffcc80;
117
+ --panel-dark-boolean: #90caf9;
118
+ --panel-dark-null: #8b9db5;
119
+ }
120
+
121
+ :root[data-theme="dark"] {
122
+ --bg-primary: #1a1a24;
123
+ --bg-secondary: #232736;
124
+ --bg-elevated: #2d3344;
125
+ --bg-overlay: rgba(0, 0, 0, 0.7);
126
+ --bg-hover: rgba(255, 255, 255, 0.06);
127
+ --bg-active: rgba(255, 255, 255, 0.10);
128
+
129
+ --text-primary: #e8edf4;
130
+ --text-secondary: #a8b4c8;
131
+ --text-muted: #94a3b8;
132
+ --text-accent: #7db5e2;
133
+
134
+ --border-light: #3a4256;
135
+ --border-medium: #4a5572;
136
+ --border-focus: #5b9cd4;
137
+
138
+ --badge-schema: #5b9cd4;
139
+ --badge-schema-bg: rgba(91, 156, 212, 0.2);
140
+ --badge-property: #60c3a7;
141
+ --badge-property-bg: rgba(96, 195, 167, 0.2);
142
+ --badge-definition: #dbab3e;
143
+ --badge-definition-bg: rgba(219, 171, 62, 0.2);
144
+ --badge-required: #5b9cd4;
145
+ --badge-required-bg: rgba(91, 156, 212, 0.2);
146
+ --badge-deprecated: #b31f24;
147
+ --badge-deprecated-bg: rgba(179, 31, 36, 0.2);
148
+
149
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
150
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.5), 0 2px 4px -1px rgba(0, 0, 0, 0.3);
151
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.6), 0 4px 6px -2px rgba(0, 0, 0, 0.4);
152
+
153
+ --type-string: #8bd450;
154
+ --type-string-bg: rgba(139, 212, 80, 0.15);
155
+ --type-number: #f0824a;
156
+ --type-number-bg: rgba(240, 130, 74, 0.15);
157
+ --type-integer: #f0ca6e;
158
+ --type-integer-bg: rgba(240, 202, 110, 0.15);
159
+ --type-boolean: #7db5e2;
160
+ --type-boolean-bg: rgba(125, 181, 226, 0.15);
161
+ --type-object: #5b9cd4;
162
+ --type-object-bg: rgba(91, 156, 212, 0.15);
163
+ --type-array: #a78bfa;
164
+ --type-array-bg: rgba(167, 139, 250, 0.15);
165
+ --type-null: #6b7a8f;
166
+ --type-null-bg: rgba(107, 122, 143, 0.15);
167
+
168
+ --schema-lines: rgba(91, 156, 212, 0.25);
169
+ --schema-nested-bg: #232736;
170
+ --schema-type-name: #a8b4c8;
171
+ --schema-require-label: #e55a5a;
172
+ --schema-require-bg: rgba(229, 90, 90, 0.12);
173
+ --schema-arrow-color: #a8b4c8;
174
+
175
+ --panel-dark-bg: #1e2a35;
176
+ --panel-dark-text: #dce4f0;
177
+ --panel-dark-muted: #7b8fa8;
178
+ --panel-dark-key: #4db6ac;
179
+ --panel-dark-string: #81c784;
180
+ --panel-dark-number: #ffb74d;
181
+ --panel-dark-boolean: #64b5f6;
182
+ --panel-dark-null: #7b8fa8;
183
+ }
184
+
185
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
186
+
187
+ html {
188
+ font-size: 16px;
189
+ -webkit-font-smoothing: antialiased;
190
+ -moz-osx-font-smoothing: grayscale;
191
+ }
192
+
193
+ body {
194
+ font-family: var(--font-sans);
195
+ font-size: var(--text-base);
196
+ line-height: var(--leading-normal);
197
+ color: var(--text-primary);
198
+ background-color: var(--bg-primary);
199
+ }
200
+
201
+ h1, h2, h3, h4, h5, h6 {
202
+ font-weight: 600;
203
+ line-height: var(--leading-tight);
204
+ color: var(--text-primary);
205
+ }
206
+ h1 { font-size: var(--text-2xl); }
207
+ h2 { font-size: var(--text-xl); }
208
+ h3 { font-size: var(--text-lg); }
209
+
210
+ code, pre { font-family: var(--font-mono); font-size: 0.9em; }
211
+ code { background: var(--bg-secondary); padding: 0.125em 0.375em; border-radius: var(--radius-sm); }
212
+ pre { background: var(--bg-secondary); padding: var(--space-4); border-radius: var(--radius-md); overflow-x: auto; }
213
+ pre code { background: none; padding: 0; display: block; }
214
+
215
+ a { color: var(--color-primary); text-decoration: none; transition: color var(--transition-fast); }
216
+ a:hover { color: var(--color-primary-dark); text-decoration: underline; }
217
+
218
+ button { font-family: inherit; font-size: inherit; cursor: pointer; border: none; background: none; }
219
+
220
+ :focus-visible {
221
+ outline: 2px solid var(--color-primary);
222
+ outline-offset: 2px;
223
+ }
224
+
225
+ .btn {
226
+ display: inline-flex; align-items: center; justify-content: center;
227
+ gap: var(--space-2); padding: var(--space-2) var(--space-4);
228
+ font-size: var(--text-sm); font-weight: 500; border-radius: var(--radius-md);
229
+ transition: all var(--transition-fast);
230
+ }
231
+ .btn-primary { background: var(--color-primary); color: white; }
232
+ .btn-primary:hover { background: var(--color-primary-dark); }
233
+ .btn-secondary { background: var(--bg-secondary); color: var(--text-primary); border: 1px solid var(--border-medium); }
234
+ .btn-secondary:hover { background: var(--bg-hover); }
235
+ .btn-ghost { color: var(--text-secondary); }
236
+ .btn-ghost:hover { background: var(--bg-hover); color: var(--text-primary); }
237
+ .btn-outline { border: 1px solid var(--border-medium); color: var(--text-secondary); background: transparent; }
238
+ .btn-outline:hover { border-color: var(--color-primary); color: var(--color-primary); }
239
+ .btn-sm { padding: var(--space-1) var(--space-3); font-size: var(--text-xs); }
240
+
241
+ .badge {
242
+ display: inline-flex; align-items: center; padding: 0.125rem 0.5rem;
243
+ font-size: var(--text-xs); font-weight: 500; border-radius: var(--radius-sm);
244
+ text-transform: uppercase; letter-spacing: 0.025em;
245
+ }
246
+
247
+ .card {
248
+ background: var(--bg-elevated); border: 1px solid var(--border-light);
249
+ border-radius: var(--radius-lg); box-shadow: var(--shadow-sm);
250
+ }
251
+
252
+ .table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
253
+ .table th, .table td { padding: var(--space-2) var(--space-3); text-align: start; border-bottom: 1px solid var(--border-light); }
254
+ .table th { font-weight: 500; color: var(--text-secondary); background: var(--bg-secondary); }
255
+ .table tr:hover td { background: var(--bg-hover); }
256
+
257
+ ::-webkit-scrollbar { width: 8px; height: 8px; }
258
+ ::-webkit-scrollbar-track { background: transparent; }
259
+ ::-webkit-scrollbar-thumb { background: var(--border-medium); border-radius: 4px; }
260
+ ::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
261
+
262
+ @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
263
+ @keyframes slideIn { from { opacity: 0; transform: translateX(10px); } to { opacity: 1; transform: translateX(0); } }
264
+
265
+ @media (prefers-reduced-motion: reduce) {
266
+ *, *::before, *::after {
267
+ animation-duration: 0.01ms !important;
268
+ animation-iteration-count: 1 !important;
269
+ transition-duration: 0.01ms !important;
270
+ }
271
+ }
272
+
273
+ @media print {
274
+ .sidebar, .sidebar-overlay, .app-header,
275
+ .view-toggle, .schema-actions,
276
+ .ctrl-expand, .field-check,
277
+ .source-toolbar, .copy-btn-wrap,
278
+ .section-actions,
279
+ .back-to-top, .detail-panel-overlay,
280
+ .search-modal, .builder-toolbar,
281
+ .toolbar-actions, .heading-anchor { display: none !important; }
282
+ .app-layout { grid-template-columns: 1fr !important; }
283
+ .app-main, .main-content { padding: 0 !important; max-width: 100% !important; }
284
+ .content-area { overflow: visible !important; }
285
+ .card, .def-card { break-inside: avoid; box-shadow: none; border: 1px solid #ccc; }
286
+ .field-row { break-inside: avoid; }
287
+ body { color: #000; background: #fff; }
288
+ .builder-layout { grid-template-columns: 1fr !important; }
289
+ .builder-preview { position: static !important; }
290
+ a { color: #000 !important; text-decoration: underline !important; }
291
+ }
292
+
293
+ /* Markdown fenced code blocks in descriptions */
294
+ :deep(.md-pre) {
295
+ background: var(--bg-secondary);
296
+ border: 1px solid var(--border-light);
297
+ border-radius: var(--radius-sm);
298
+ padding: var(--space-2) var(--space-3);
299
+ font-size: var(--text-xs);
300
+ overflow-x: auto;
301
+ margin: var(--space-2) 0;
302
+ line-height: var(--leading-relaxed);
303
+ }
304
+ :deep(.md-pre code) {
305
+ font-family: var(--font-mono);
306
+ color: var(--text-primary);
307
+ background: none;
308
+ padding: 0;
309
+ border: none;
310
+ }
311
+
312
+ .text-muted { color: var(--text-muted); }
313
+ .text-secondary { color: var(--text-secondary); }
314
+ .font-mono { font-family: var(--font-mono); }
315
+ .truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
316
+ .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
317
+
318
+ .toolbar-field-count {
319
+ font-size: 10px;
320
+ font-weight: 500;
321
+ color: var(--text-muted);
322
+ background: var(--bg-secondary);
323
+ padding: 2px 6px;
324
+ border-radius: 8px;
325
+ }
326
+
327
+ /* Shared collapsible JSON viewer styles (used by source viewer, examples, builder) */
328
+ .jv-viewer ul { list-style: none; padding-inline-start: var(--space-4); margin: 0; }
329
+ .jv-viewer li { margin: 0; }
330
+ .jv-viewer .jv-toggle {
331
+ background: none; border: 1px solid var(--border-medium); border-radius: 2px;
332
+ cursor: pointer; width: 14px; height: 14px; font-size: 9px; line-height: 1;
333
+ display: inline-flex; align-items: center; justify-content: center;
334
+ color: var(--text-muted); padding: 0; margin-inline-end: 4px; vertical-align: middle;
335
+ transition: background var(--transition-fast);
336
+ }
337
+ .jv-viewer .jv-toggle:hover { background: var(--bg-hover); }
338
+ .jv-viewer .jv-toggle[aria-label="expand"]::after { content: '+'; }
339
+ .jv-viewer .jv-toggle[aria-label="collapse"]::after { content: '−'; }
340
+ .jv-viewer .jv-ellipsis { display: none; color: var(--text-muted); font-size: var(--text-xs); font-style: italic; margin-inline-start: 4px; }
341
+ .jv-viewer .jv-children.jv-collapsed { display: none; }
342
+ .jv-viewer .jv-children.jv-collapsed + .jv-ellipsis { display: inline; }
343
+ .jv-viewer .jv-key { color: var(--color-primary-dark); }
344
+ .jv-viewer .jv-punct { color: var(--text-muted); }
345
+ .jv-viewer .jv-string { color: var(--color-green); }
346
+ .jv-viewer .jv-number { color: var(--color-orange); }
347
+ .jv-viewer .jv-boolean { color: var(--color-accent); }
348
+ .jv-viewer .jv-null { color: var(--text-muted); font-style: italic; }
349
+ .jv-viewer .jv-link { color: var(--color-green); text-decoration: underline; text-decoration-style: dotted; }
350
+ .jv-viewer .jv-link:hover { text-decoration-style: solid; }
351
+ .jv-viewer .jv-row:hover { background: var(--bg-hover); border-radius: 2px; }
352
+
353
+ :root[data-theme="dark"] .jv-viewer .jv-key { color: var(--color-primary-light); }
354
+ :root[data-theme="dark"] .jv-viewer .jv-string { color: var(--color-teal); }
355
+ :root[data-theme="dark"] .jv-viewer .jv-number { color: var(--color-accent-light); }
356
+ :root[data-theme="dark"] .jv-viewer .jv-boolean { color: var(--color-primary-light); }
357
+ :root[data-theme="dark"] .jv-viewer .jv-null { color: var(--text-muted); }
358
+ :root[data-theme="dark"] .jv-viewer .jv-toggle { border-color: rgba(255, 255, 255, 0.2); }
359
+ :root[data-theme="dark"] .jv-viewer .jv-punct { color: var(--panel-dark-muted); }
360
+ :root[data-theme="dark"] .jv-viewer .jv-row:hover { background: rgba(255, 255, 255, 0.06); }
361
+
362
+ /* Shared markdown content styles (used by schema desc, def card desc, def body desc) */
363
+ .md-content .md-code {
364
+ font-family: var(--font-mono); font-size: inherit;
365
+ background: var(--bg-secondary); padding: 1px 4px; border-radius: 2px;
366
+ border: 1px solid var(--border-light);
367
+ }
368
+ .md-content .md-link { color: var(--color-primary); text-decoration: none; }
369
+ .md-content .md-link:hover { text-decoration: underline; }
370
+ .md-content .md-heading { font-weight: 600; margin: var(--space-2) 0 var(--space-1); color: var(--text-primary); font-size: inherit; }
371
+ .md-content .md-list { margin: var(--space-1) 0; padding-inline-start: var(--space-5); font-size: inherit; }
372
+ .md-content .md-list li { margin-bottom: 2px; }
373
+ .md-content .md-table {
374
+ width: 100%; border-collapse: collapse; margin: var(--space-2) 0;
375
+ font-size: var(--text-xs); display: block; overflow-x: auto;
376
+ }
377
+ .md-content .md-table th,
378
+ .md-content .md-table td {
379
+ border: 1px solid var(--border-light);
380
+ padding: var(--space-1) var(--space-2);
381
+ text-align: start; vertical-align: top;
382
+ }
383
+ .md-content .md-table th {
384
+ background: var(--bg-secondary); font-weight: 600;
385
+ color: var(--text-primary); white-space: nowrap;
386
+ }
387
+ .md-content .md-align-center { text-align: center; }
388
+ .md-content .md-align-right { text-align: right; }
389
+ .md-content .md-pre {
390
+ background: var(--bg-secondary); border: 1px solid var(--border-light);
391
+ border-radius: var(--radius-sm); padding: var(--space-2); margin: var(--space-2) 0;
392
+ overflow-x: auto; font-size: var(--text-xs);
393
+ }
394
+ .md-content .md-pre code { font-family: var(--font-mono); font-size: inherit; }
395
+
396
+ /* Composition variant tabs (oneOf/anyOf) */
397
+ .variant-tabs { display: flex; flex-wrap: wrap; gap: 6px; }
398
+ .variant-tab {
399
+ border: 1px solid var(--border-light); background: transparent;
400
+ color: var(--text-secondary); border-radius: 4px; padding: 2px 8px;
401
+ font-size: var(--text-xs); cursor: pointer;
402
+ }
403
+ .variant-tab:hover { border-color: var(--border-medium); }
404
+ .variant-tab.active {
405
+ border-color: var(--color-primary); color: var(--text-primary);
406
+ background: var(--color-primary-alpha);
407
+ }
408
+ .variant-tab:focus-visible {
409
+ outline: 2px solid var(--color-primary); outline-offset: 1px;
410
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "jsx": "preserve",
8
+ "resolveJsonModule": true,
9
+ "isolatedModules": true,
10
+ "esModuleInterop": true,
11
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
12
+ "skipLibCheck": true,
13
+ "noEmit": true,
14
+ "paths": {
15
+ "@/*": ["./src/*"]
16
+ }
17
+ },
18
+ "include": ["src/**/*.ts", "src/**/*.vue"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }
@@ -0,0 +1,28 @@
1
+ import { defineConfig } from 'vite'
2
+ import vue from '@vitejs/plugin-vue'
3
+
4
+ export default defineConfig({
5
+ plugins: [vue()],
6
+ define: {
7
+ 'process.env.NODE_ENV': JSON.stringify('production'),
8
+ },
9
+ build: {
10
+ lib: {
11
+ entry: 'src/app.ts',
12
+ name: 'LutamlJsonSchemaDocs',
13
+ formats: ['iife'],
14
+ fileName: () => 'app.iife.js',
15
+ },
16
+ cssCodeSplit: false,
17
+ rollupOptions: {
18
+ output: {
19
+ assetFileNames: 'style.[ext]',
20
+ inlineDynamicImports: true,
21
+ },
22
+ },
23
+ minify: 'terser',
24
+ terserOptions: {
25
+ compress: { drop_console: true },
26
+ },
27
+ },
28
+ })