@cwygoda/service-catalog-ui 0.17.1

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.
Files changed (68) hide show
  1. package/dist/__mocks__/app-environment.d.ts +4 -0
  2. package/dist/__mocks__/app-environment.js +4 -0
  3. package/dist/__mocks__/app-state.d.ts +12 -0
  4. package/dist/__mocks__/app-state.js +10 -0
  5. package/dist/adapters/index.d.ts +1 -0
  6. package/dist/adapters/index.js +1 -0
  7. package/dist/adapters/static-json.adapter.d.ts +2 -0
  8. package/dist/adapters/static-json.adapter.js +34 -0
  9. package/dist/components/BpmnDiagram.svelte +496 -0
  10. package/dist/components/BpmnDiagram.svelte.d.ts +18 -0
  11. package/dist/components/Breadcrumbs.svelte +32 -0
  12. package/dist/components/Breadcrumbs.svelte.d.ts +11 -0
  13. package/dist/components/DataStoreCard.svelte +26 -0
  14. package/dist/components/DataStoreCard.svelte.d.ts +7 -0
  15. package/dist/components/DataStoreShield.svelte +67 -0
  16. package/dist/components/DataStoreShield.svelte.d.ts +9 -0
  17. package/dist/components/DomainCard.svelte +34 -0
  18. package/dist/components/DomainCard.svelte.d.ts +9 -0
  19. package/dist/components/DomainCard.test.d.ts +1 -0
  20. package/dist/components/DomainCard.test.js +45 -0
  21. package/dist/components/Header.svelte +144 -0
  22. package/dist/components/Header.svelte.d.ts +3 -0
  23. package/dist/components/NavModeToggle.svelte +43 -0
  24. package/dist/components/NavModeToggle.svelte.d.ts +18 -0
  25. package/dist/components/NavTree.svelte +245 -0
  26. package/dist/components/NavTree.svelte.d.ts +10 -0
  27. package/dist/components/SearchModal.svelte +288 -0
  28. package/dist/components/SearchModal.svelte.d.ts +3 -0
  29. package/dist/components/ServiceCard.svelte +36 -0
  30. package/dist/components/ServiceCard.svelte.d.ts +7 -0
  31. package/dist/components/ServiceCard.test.d.ts +1 -0
  32. package/dist/components/ServiceCard.test.js +43 -0
  33. package/dist/components/ServiceGraph.svelte +437 -0
  34. package/dist/components/ServiceGraph.svelte.d.ts +10 -0
  35. package/dist/components/ServiceTypeShield.svelte +32 -0
  36. package/dist/components/ServiceTypeShield.svelte.d.ts +8 -0
  37. package/dist/components/Shield.svelte +118 -0
  38. package/dist/components/Shield.svelte.d.ts +7 -0
  39. package/dist/components/ThemeToggle.svelte +44 -0
  40. package/dist/components/ThemeToggle.svelte.d.ts +18 -0
  41. package/dist/components/ThemeToggle.test.d.ts +1 -0
  42. package/dist/components/ThemeToggle.test.js +52 -0
  43. package/dist/components/UseCaseCard.svelte +61 -0
  44. package/dist/components/UseCaseCard.svelte.d.ts +7 -0
  45. package/dist/components/UseCaseCard.test.d.ts +1 -0
  46. package/dist/components/UseCaseCard.test.js +87 -0
  47. package/dist/components/index.d.ts +15 -0
  48. package/dist/components/index.js +15 -0
  49. package/dist/index.d.ts +5 -0
  50. package/dist/index.js +5 -0
  51. package/dist/ports/catalog.port.d.ts +5 -0
  52. package/dist/ports/catalog.port.js +1 -0
  53. package/dist/ports/index.d.ts +1 -0
  54. package/dist/ports/index.js +1 -0
  55. package/dist/source.css +1 -0
  56. package/dist/stores/index.d.ts +4 -0
  57. package/dist/stores/index.js +3 -0
  58. package/dist/stores/nav-mode.svelte.d.ts +7 -0
  59. package/dist/stores/nav-mode.svelte.js +32 -0
  60. package/dist/stores/search.svelte.d.ts +9 -0
  61. package/dist/stores/search.svelte.js +14 -0
  62. package/dist/stores/theme.svelte.d.ts +8 -0
  63. package/dist/stores/theme.svelte.js +61 -0
  64. package/dist/utils/fetch-catalog.d.ts +6 -0
  65. package/dist/utils/fetch-catalog.js +26 -0
  66. package/dist/utils/index.d.ts +1 -0
  67. package/dist/utils/index.js +1 -0
  68. package/package.json +63 -0
@@ -0,0 +1,4 @@
1
+ export declare const browser = true;
2
+ export declare const dev = true;
3
+ export declare const building = false;
4
+ export declare const version = "test";
@@ -0,0 +1,4 @@
1
+ export const browser = true;
2
+ export const dev = true;
3
+ export const building = false;
4
+ export const version = 'test';
@@ -0,0 +1,12 @@
1
+ export declare const page: {
2
+ url: URL;
3
+ params: {};
4
+ route: {
5
+ id: string;
6
+ };
7
+ status: number;
8
+ error: null;
9
+ data: {};
10
+ state: {};
11
+ form: null;
12
+ };
@@ -0,0 +1,10 @@
1
+ export const page = {
2
+ url: new URL('http://localhost:5173/'),
3
+ params: {},
4
+ route: { id: '/' },
5
+ status: 200,
6
+ error: null,
7
+ data: {},
8
+ state: {},
9
+ form: null,
10
+ };
@@ -0,0 +1 @@
1
+ export { createStaticJsonAdapter } from './static-json.adapter.js';
@@ -0,0 +1 @@
1
+ export { createStaticJsonAdapter } from './static-json.adapter.js';
@@ -0,0 +1,2 @@
1
+ import type { CatalogPort } from '../ports/catalog.port.js';
2
+ export declare function createStaticJsonAdapter(baseUrl?: string): CatalogPort;
@@ -0,0 +1,34 @@
1
+ import { Value } from '@sinclair/typebox/value';
2
+ import { findService } from '@cwygoda/service-catalog-core/domain';
3
+ import { CatalogSchema } from '@cwygoda/service-catalog-core/schemas';
4
+ const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
5
+ export function createStaticJsonAdapter(baseUrl = '') {
6
+ let cache = null;
7
+ async function fetchCatalog() {
8
+ const now = Date.now();
9
+ if (cache && now - cache.timestamp < CACHE_TTL_MS) {
10
+ return cache.data;
11
+ }
12
+ const response = await fetch(`${baseUrl}/catalog.json`);
13
+ if (!response.ok) {
14
+ throw new Error(`Failed to fetch catalog: ${String(response.status)}`);
15
+ }
16
+ const data = await response.json();
17
+ if (!Value.Check(CatalogSchema, data)) {
18
+ const errors = [...Value.Errors(CatalogSchema, data)];
19
+ const message = errors.map((e) => `${e.path}: ${e.message}`).join('; ');
20
+ throw new Error(`Invalid catalog data: ${message}`);
21
+ }
22
+ cache = { data: data, timestamp: now };
23
+ return cache.data;
24
+ }
25
+ return {
26
+ async getCatalog() {
27
+ return fetchCatalog();
28
+ },
29
+ async getService(id) {
30
+ const catalog = await fetchCatalog();
31
+ return findService(catalog, id);
32
+ },
33
+ };
34
+ }
@@ -0,0 +1,496 @@
1
+ <script lang="ts">
2
+ import { onMount, onDestroy } from 'svelte';
3
+ import { browser } from '$app/environment';
4
+
5
+ interface DocLink {
6
+ elementId: string;
7
+ anchor: string;
8
+ }
9
+
10
+ interface ElementClickEvent {
11
+ elementId: string;
12
+ docAnchor?: string | undefined;
13
+ }
14
+
15
+ interface Props {
16
+ xml: string;
17
+ interactive?: boolean;
18
+ class?: string;
19
+ docLinks?: DocLink[];
20
+ onElementClick?: (event: ElementClickEvent) => void;
21
+ }
22
+
23
+ interface Bounds {
24
+ x: number;
25
+ y: number;
26
+ width: number;
27
+ height: number;
28
+ }
29
+
30
+ interface BpmnCanvas {
31
+ zoom(level: 'fit-viewport' | number): void;
32
+ zoom(): number; // get current zoom
33
+ viewbox(): Bounds & { scale: number; inner: Bounds; outer: Bounds };
34
+ viewbox(bounds: Partial<Bounds>): void;
35
+ }
36
+
37
+ const FIT_PADDING = 40; // pixels of padding around diagram when fitting
38
+ const FULLSCREEN_TRANSITION_MS = 100;
39
+
40
+ interface BpmnViewer {
41
+ importXML: (xml: string) => Promise<unknown>;
42
+ get: (name: string) => unknown;
43
+ destroy?: () => void;
44
+ }
45
+
46
+ let {
47
+ xml,
48
+ interactive = false,
49
+ class: className = '',
50
+ docLinks,
51
+ onElementClick,
52
+ }: Props = $props();
53
+
54
+ let wrapper: HTMLDivElement | undefined = $state();
55
+ let container: HTMLDivElement | undefined = $state();
56
+ let resizeObserver: ResizeObserver | null = null;
57
+ let viewer: BpmnViewer | null = $state(null);
58
+ let error: string | null = $state(null);
59
+ let ready = $state(false);
60
+
61
+ // Fullscreen state
62
+ let isFullscreen = $state(false);
63
+
64
+ function setBodyOverflow(hidden: boolean) {
65
+ if (!browser) return;
66
+ document.body.style.overflow = hidden ? 'hidden' : '';
67
+ }
68
+
69
+ function handleEscapeKey(e: KeyboardEvent) {
70
+ if (e.key === 'Escape' && isFullscreen) {
71
+ toggleFullscreen();
72
+ }
73
+ }
74
+
75
+ function toggleFullscreen() {
76
+ if (!browser) return;
77
+ isFullscreen = !isFullscreen;
78
+ setBodyOverflow(isFullscreen);
79
+
80
+ if (isFullscreen) {
81
+ document.addEventListener('keydown', handleEscapeKey);
82
+ } else {
83
+ document.removeEventListener('keydown', handleEscapeKey);
84
+ }
85
+
86
+ // Re-fit diagram after layout settles
87
+ if (ready) {
88
+ setTimeout(fitWithPadding, FULLSCREEN_TRANSITION_MS);
89
+ }
90
+ }
91
+
92
+ function getCanvas(): BpmnCanvas | null {
93
+ if (!viewer) return null;
94
+ return viewer.get('canvas') as BpmnCanvas;
95
+ }
96
+
97
+ function fitWithPadding() {
98
+ const canvas = getCanvas();
99
+ if (!canvas) return;
100
+
101
+ // First fit to viewport to get the content bounds
102
+ canvas.zoom('fit-viewport');
103
+
104
+ // Get current viewbox info - inner is the diagram bounds, outer is container
105
+ const vb = canvas.viewbox();
106
+ const inner = vb.inner;
107
+ const outer = vb.outer;
108
+
109
+ // Calculate scale to fit content with padding
110
+ const availableWidth = outer.width - FIT_PADDING * 2;
111
+ const availableHeight = outer.height - FIT_PADDING * 2;
112
+ const scaleX = availableWidth / inner.width;
113
+ const scaleY = availableHeight / inner.height;
114
+ const scale = Math.min(scaleX, scaleY);
115
+
116
+ // Calculate centered viewbox dimensions
117
+ const viewWidth = outer.width / scale;
118
+ const viewHeight = outer.height / scale;
119
+
120
+ // Center the content
121
+ const centerX = inner.x + inner.width / 2;
122
+ const centerY = inner.y + inner.height / 2;
123
+
124
+ canvas.viewbox({
125
+ x: centerX - viewWidth / 2,
126
+ y: centerY - viewHeight / 2,
127
+ width: viewWidth,
128
+ height: viewHeight,
129
+ });
130
+ }
131
+
132
+ function zoomIn() {
133
+ const canvas = getCanvas();
134
+ if (!canvas) return;
135
+ const current = canvas.zoom();
136
+ canvas.zoom(current * 1.25);
137
+ }
138
+
139
+ function zoomOut() {
140
+ const canvas = getCanvas();
141
+ if (!canvas) return;
142
+ const current = canvas.zoom();
143
+ canvas.zoom(current * 0.8);
144
+ }
145
+
146
+ function resetZoom() {
147
+ fitWithPadding();
148
+ }
149
+
150
+ /**
151
+ * Re-center inline message-flow labels rendered by bpmn-js.
152
+ * bpmn-js positions these labels using getBBox() which can return
153
+ * incorrect widths before fonts are loaded, causing left-aligned labels.
154
+ */
155
+ function fixMessageFlowLabels() {
156
+ if (!viewer) return;
157
+
158
+ const elementRegistry = viewer.get('elementRegistry') as {
159
+ forEach: (cb: (el: { id: string; type: string }) => void) => void;
160
+ get: (id: string) => { id: string } | undefined;
161
+ getGraphics: (el: { id: string }) => SVGElement | null;
162
+ };
163
+
164
+ elementRegistry.forEach((el) => {
165
+ if (el.type !== 'bpmn:MessageFlow') return;
166
+
167
+ // Get the connection's SVG group
168
+ const connGfx = elementRegistry.getGraphics(el);
169
+ if (!connGfx) return;
170
+
171
+ const visual = connGfx.querySelector('.djs-visual');
172
+ if (!visual) return;
173
+
174
+ // The actual line path is a direct child of .djs-visual;
175
+ // the arrow marker path lives inside <defs> and must be skipped.
176
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- needed for svelte-check
177
+ const linePath = visual.querySelector(':scope > path') as SVGPathElement | null;
178
+ if (!linePath?.getTotalLength) return;
179
+
180
+ // bpmn-js renders labels as separate shape elements: {flowId}_label
181
+ const labelEl = elementRegistry.get(el.id + '_label');
182
+ if (!labelEl) return;
183
+
184
+ const labelGfx = elementRegistry.getGraphics(labelEl);
185
+ if (!labelGfx) return;
186
+
187
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- needed for svelte-check
188
+ const textEl = labelGfx.querySelector('text') as SVGTextElement | null;
189
+ if (!textEl) return;
190
+
191
+ const bbox = textEl.getBBox();
192
+ if (bbox.width <= 0) return;
193
+
194
+ // Center the label on the midpoint of the connection path
195
+ const midPoint = linePath.getPointAtLength(linePath.getTotalLength() / 2);
196
+ const newX = midPoint.x - bbox.width / 2 - bbox.x;
197
+ const newY = midPoint.y - bbox.height / 2 - bbox.y;
198
+ labelGfx.setAttribute('transform', `matrix(1 0 0 1 ${String(newX)} ${String(newY)})`);
199
+ });
200
+ }
201
+
202
+ onMount(async () => {
203
+ if (!browser || !xml || !container) return;
204
+
205
+ try {
206
+ // Dynamic import to avoid SSR issues
207
+ const { default: BpmnViewer } = await import('bpmn-js/lib/NavigatedViewer');
208
+
209
+ viewer = new BpmnViewer({
210
+ container,
211
+ keyboard: { bindTo: interactive ? document : undefined },
212
+ }) as BpmnViewer;
213
+
214
+ await viewer.importXML(xml);
215
+ fixMessageFlowLabels();
216
+
217
+ // Register click handler for doc links
218
+ if (onElementClick) {
219
+ const eventBus = viewer.get('eventBus') as {
220
+ on: (event: string, callback: (e: { element: { id: string } }) => void) => void;
221
+ };
222
+ const docLinkMap = new Map((docLinks ?? []).map((l) => [l.elementId, l.anchor]));
223
+
224
+ eventBus.on('element.click', (e) => {
225
+ const id = e.element.id;
226
+ onElementClick({
227
+ elementId: id,
228
+ docAnchor: docLinkMap.get(id),
229
+ });
230
+ });
231
+
232
+ // Add pointer cursor on clickable elements
233
+ if (docLinks?.length) {
234
+ const elementRegistry = viewer.get('elementRegistry') as {
235
+ forEach: (cb: (el: { id: string; type: string }) => void) => void;
236
+ getGraphics: (el: { id: string }) => SVGElement | null;
237
+ };
238
+ elementRegistry.forEach((el) => {
239
+ if (docLinkMap.has(el.id)) {
240
+ const gfx = elementRegistry.getGraphics(el);
241
+ if (gfx) gfx.style.cursor = 'pointer';
242
+ }
243
+ });
244
+ }
245
+ }
246
+
247
+ // Fit diagram to container with padding
248
+ fitWithPadding();
249
+ ready = true;
250
+
251
+ // Re-fit on resize
252
+ resizeObserver = new ResizeObserver(() => {
253
+ if (ready) fitWithPadding();
254
+ });
255
+ resizeObserver.observe(container);
256
+ } catch (e) {
257
+ error = e instanceof Error ? e.message : 'Failed to render BPMN diagram';
258
+ console.error('BPMN render error:', e);
259
+ }
260
+ });
261
+
262
+ onDestroy(() => {
263
+ if (browser) {
264
+ document.removeEventListener('keydown', handleEscapeKey);
265
+ setBodyOverflow(false);
266
+ }
267
+ resizeObserver?.disconnect();
268
+ if (viewer?.destroy) {
269
+ viewer.destroy();
270
+ }
271
+ });
272
+
273
+ $effect(() => {
274
+ // Re-import when xml changes
275
+ if (browser && viewer && xml) {
276
+ viewer
277
+ .importXML(xml)
278
+ .then(() => {
279
+ fixMessageFlowLabels();
280
+ fitWithPadding();
281
+ })
282
+ .catch((e: unknown) => {
283
+ error = e instanceof Error ? e.message : 'Failed to update diagram';
284
+ });
285
+ }
286
+ });
287
+ </script>
288
+
289
+ {#if error}
290
+ <div
291
+ class="rounded-lg border border-red-200 bg-red-50 p-4 text-red-700 dark:border-red-800 dark:bg-red-900/20 dark:text-red-400"
292
+ >
293
+ <p class="font-medium">Failed to render diagram</p>
294
+ <p class="mt-1 text-sm">{error}</p>
295
+ </div>
296
+ {:else}
297
+ <div
298
+ bind:this={wrapper}
299
+ class="bpmn-wrapper {isFullscreen
300
+ ? 'fixed inset-0 z-50 bg-white dark:bg-gray-900'
301
+ : 'relative'}"
302
+ >
303
+ <div
304
+ bind:this={container}
305
+ class="bpmn-container w-full {isFullscreen
306
+ ? 'h-full bg-white dark:bg-gray-900'
307
+ : 'bpmn-container--inline rounded-lg border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-800'} {className}"
308
+ class:cursor-grab={interactive}
309
+ role="img"
310
+ aria-label="BPMN process diagram"
311
+ ></div>
312
+
313
+ <!-- Zoom controls -->
314
+ {#if ready}
315
+ <div
316
+ class="absolute right-3 top-3 flex flex-col gap-1 rounded-lg border border-gray-200 bg-white/90 p-1 shadow-sm backdrop-blur-sm dark:border-gray-600 dark:bg-gray-800/90"
317
+ >
318
+ <button
319
+ onclick={zoomIn}
320
+ class="flex min-h-11 min-w-11 items-center justify-center rounded p-1.5 text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-primary-600 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
321
+ aria-label="Zoom in"
322
+ title="Zoom in"
323
+ >
324
+ <svg
325
+ class="h-5 w-5"
326
+ fill="none"
327
+ viewBox="0 0 24 24"
328
+ stroke="currentColor"
329
+ stroke-width="2"
330
+ aria-hidden="true"
331
+ >
332
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4" />
333
+ </svg>
334
+ </button>
335
+ <button
336
+ onclick={zoomOut}
337
+ class="flex min-h-11 min-w-11 items-center justify-center rounded p-1.5 text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-primary-600 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
338
+ aria-label="Zoom out"
339
+ title="Zoom out"
340
+ >
341
+ <svg
342
+ class="h-5 w-5"
343
+ fill="none"
344
+ viewBox="0 0 24 24"
345
+ stroke="currentColor"
346
+ stroke-width="2"
347
+ aria-hidden="true"
348
+ >
349
+ <path stroke-linecap="round" stroke-linejoin="round" d="M20 12H4" />
350
+ </svg>
351
+ </button>
352
+ <div class="my-0.5 border-t border-gray-200 dark:border-gray-600"></div>
353
+ <button
354
+ onclick={resetZoom}
355
+ class="flex min-h-11 min-w-11 items-center justify-center rounded p-1.5 text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-primary-600 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
356
+ aria-label="Fit to view"
357
+ title="Fit to view"
358
+ >
359
+ <!-- Viewfinder/target icon for fit-to-view -->
360
+ <svg
361
+ class="h-5 w-5"
362
+ fill="none"
363
+ viewBox="0 0 24 24"
364
+ stroke="currentColor"
365
+ stroke-width="2"
366
+ aria-hidden="true"
367
+ >
368
+ <path
369
+ stroke-linecap="round"
370
+ stroke-linejoin="round"
371
+ d="M7.5 3.75H6A2.25 2.25 0 003.75 6v1.5M16.5 3.75H18A2.25 2.25 0 0120.25 6v1.5m0 9V18A2.25 2.25 0 0118 20.25h-1.5m-9 0H6A2.25 2.25 0 013.75 18v-1.5M15 12a3 3 0 11-6 0 3 3 0 016 0z"
372
+ />
373
+ </svg>
374
+ </button>
375
+ <div class="my-0.5 border-t border-gray-200 dark:border-gray-600"></div>
376
+ <button
377
+ onclick={toggleFullscreen}
378
+ class="flex min-h-11 min-w-11 items-center justify-center rounded p-1.5 text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-primary-600 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
379
+ aria-label={isFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'}
380
+ title={isFullscreen ? 'Exit fullscreen' : 'Fullscreen'}
381
+ >
382
+ {#if isFullscreen}
383
+ <svg
384
+ class="h-5 w-5"
385
+ fill="none"
386
+ viewBox="0 0 24 24"
387
+ stroke="currentColor"
388
+ stroke-width="2"
389
+ aria-hidden="true"
390
+ >
391
+ <path
392
+ stroke-linecap="round"
393
+ stroke-linejoin="round"
394
+ d="M9 9V4.5M9 9H4.5M9 9L3.75 3.75M9 15v4.5M9 15H4.5M9 15l-5.25 5.25M15 9h4.5M15 9V4.5M15 9l5.25-5.25M15 15h4.5M15 15v4.5m0-4.5l5.25 5.25"
395
+ />
396
+ </svg>
397
+ {:else}
398
+ <svg
399
+ class="h-5 w-5"
400
+ fill="none"
401
+ viewBox="0 0 24 24"
402
+ stroke="currentColor"
403
+ stroke-width="2"
404
+ aria-hidden="true"
405
+ >
406
+ <path
407
+ stroke-linecap="round"
408
+ stroke-linejoin="round"
409
+ d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15"
410
+ />
411
+ </svg>
412
+ {/if}
413
+ </button>
414
+ </div>
415
+ {/if}
416
+ </div>
417
+ {/if}
418
+
419
+ <style>
420
+ .bpmn-container--inline {
421
+ aspect-ratio: 1;
422
+ max-height: 75vh;
423
+ }
424
+
425
+ .bpmn-container :global(.djs-container) {
426
+ height: 100% !important;
427
+ }
428
+
429
+ /* Dark mode styles for BPMN diagram */
430
+ :global(.dark) .bpmn-container :global(.djs-visual) {
431
+ /* Invert strokes: black -> light gray */
432
+ --bpmn-stroke: #d1d5db;
433
+ --bpmn-fill: #374151;
434
+ }
435
+
436
+ /* Participant/Pool borders and labels */
437
+ :global(.dark) .bpmn-container :global(.djs-group .djs-visual > rect) {
438
+ stroke: #9ca3af !important;
439
+ }
440
+
441
+ :global(.dark) .bpmn-container :global(.djs-group .djs-visual > path) {
442
+ stroke: #9ca3af !important;
443
+ }
444
+
445
+ /* Task boxes */
446
+ :global(.dark) .bpmn-container :global(.djs-shape .djs-visual > rect) {
447
+ stroke: #9ca3af !important;
448
+ fill: #374151 !important;
449
+ }
450
+
451
+ /* Events (circles) */
452
+ :global(.dark) .bpmn-container :global(.djs-shape .djs-visual > circle) {
453
+ stroke: #9ca3af !important;
454
+ fill: #374151 !important;
455
+ }
456
+
457
+ /* Gateways (diamonds) */
458
+ :global(.dark) .bpmn-container :global(.djs-shape .djs-visual > polygon) {
459
+ stroke: #9ca3af !important;
460
+ fill: #374151 !important;
461
+ }
462
+
463
+ /* Sequence flows (solid lines) */
464
+ :global(.dark) .bpmn-container :global(.djs-connection .djs-visual > path) {
465
+ stroke: #9ca3af !important;
466
+ }
467
+
468
+ /* Message flows (dashed lines) */
469
+ :global(.dark) .bpmn-container :global(.djs-connection .djs-visual > polyline) {
470
+ stroke: #9ca3af !important;
471
+ }
472
+
473
+ /* Arrow markers */
474
+ :global(.dark) .bpmn-container :global(marker path) {
475
+ fill: #9ca3af !important;
476
+ stroke: #9ca3af !important;
477
+ }
478
+
479
+ /* Text labels */
480
+ :global(.dark) .bpmn-container :global(.djs-label text),
481
+ :global(.dark) .bpmn-container :global(.djs-visual text),
482
+ :global(.dark) .bpmn-container :global(text) {
483
+ fill: #e5e7eb !important;
484
+ }
485
+
486
+ /* Participant header background (the title band) */
487
+ :global(.dark) .bpmn-container :global(.djs-visual > rect:first-child) {
488
+ fill: #1f2937 !important;
489
+ }
490
+
491
+ /* bpmn.io logo/watermark in corner */
492
+ :global(.dark) .bpmn-container :global(.bjs-powered-by) {
493
+ filter: invert(1) hue-rotate(180deg);
494
+ opacity: 0.7;
495
+ }
496
+ </style>
@@ -0,0 +1,18 @@
1
+ interface DocLink {
2
+ elementId: string;
3
+ anchor: string;
4
+ }
5
+ interface ElementClickEvent {
6
+ elementId: string;
7
+ docAnchor?: string | undefined;
8
+ }
9
+ interface Props {
10
+ xml: string;
11
+ interactive?: boolean;
12
+ class?: string;
13
+ docLinks?: DocLink[];
14
+ onElementClick?: (event: ElementClickEvent) => void;
15
+ }
16
+ declare const BpmnDiagram: import("svelte").Component<Props, {}, "">;
17
+ type BpmnDiagram = ReturnType<typeof BpmnDiagram>;
18
+ export default BpmnDiagram;
@@ -0,0 +1,32 @@
1
+ <script lang="ts">
2
+ interface BreadcrumbItem {
3
+ label: string;
4
+ href: string;
5
+ }
6
+
7
+ interface Props {
8
+ items: BreadcrumbItem[];
9
+ current: string;
10
+ }
11
+
12
+ let { items, current }: Props = $props();
13
+ </script>
14
+
15
+ <nav aria-label="Breadcrumb" class="mb-6">
16
+ <ol class="flex flex-wrap items-center gap-1 text-xs sm:text-sm">
17
+ {#each items as item (item.href)}
18
+ <li class="flex items-center">
19
+ <a
20
+ href={item.href}
21
+ class="text-primary-600 hover:text-primary-800 hover:underline dark:text-primary-400 dark:hover:text-primary-200"
22
+ >
23
+ {item.label}
24
+ </a>
25
+ <span class="mx-1 sm:mx-2 text-gray-400" aria-hidden="true">/</span>
26
+ </li>
27
+ {/each}
28
+ <li class="text-gray-700 dark:text-gray-300" aria-current="page">
29
+ {current}
30
+ </li>
31
+ </ol>
32
+ </nav>
@@ -0,0 +1,11 @@
1
+ interface BreadcrumbItem {
2
+ label: string;
3
+ href: string;
4
+ }
5
+ interface Props {
6
+ items: BreadcrumbItem[];
7
+ current: string;
8
+ }
9
+ declare const Breadcrumbs: import("svelte").Component<Props, {}, "">;
10
+ type Breadcrumbs = ReturnType<typeof Breadcrumbs>;
11
+ export default Breadcrumbs;
@@ -0,0 +1,26 @@
1
+ <script lang="ts">
2
+ import type { DataStore } from '@cwygoda/service-catalog-core/domain';
3
+ import DataStoreShield from './DataStoreShield.svelte';
4
+
5
+ let { dataStore }: { dataStore: DataStore } = $props();
6
+ </script>
7
+
8
+ <a
9
+ href="/data-stores/{dataStore.id}"
10
+ aria-label="View {dataStore.name} data store"
11
+ class="block rounded-lg border border-gray-200 bg-white p-6 shadow-sm transition-shadow hover:shadow-md active:shadow-sm dark:border-gray-700 dark:bg-gray-800"
12
+ >
13
+ <div class="mb-3 flex items-start gap-4">
14
+ <DataStoreShield type={dataStore.type} technology={dataStore.technology} size={44} />
15
+ <div class="min-w-0 flex-1">
16
+ <div class="flex items-center justify-between gap-2">
17
+ <h3 class="truncate text-lg font-semibold text-gray-900 dark:text-white">
18
+ {dataStore.name}
19
+ </h3>
20
+ </div>
21
+ <p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
22
+ {dataStore.description}
23
+ </p>
24
+ </div>
25
+ </div>
26
+ </a>
@@ -0,0 +1,7 @@
1
+ import type { DataStore } from '@cwygoda/service-catalog-core/domain';
2
+ type $$ComponentProps = {
3
+ dataStore: DataStore;
4
+ };
5
+ declare const DataStoreCard: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type DataStoreCard = ReturnType<typeof DataStoreCard>;
7
+ export default DataStoreCard;