@juspay/svelte-ui-components 2.136.0 → 2.136.7

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.
@@ -32,8 +32,15 @@
32
32
  }
33
33
 
34
34
  .loader {
35
- height: var(--loader-height, 100vh);
36
- width: var(--loader-width, 100vw);
35
+ /* --loader-height/--loader-width are also independently read by the
36
+ unrelated Loader component (with a 20px default there, vs 100vh/100vw
37
+ here), so a consumer setting either name in a scope containing both
38
+ components would size them identically. --brand-loader-* is the
39
+ namespaced, collision-free override point; the legacy --loader-*
40
+ names are kept as a fallback so existing consumer overrides keep
41
+ working unchanged. */
42
+ height: var(--brand-loader-height, var(--loader-height, 100vh));
43
+ width: var(--brand-loader-width, var(--loader-width, 100vw));
37
44
  border-radius: var(--loader-background-border-radius, 0px);
38
45
  display: flex;
39
46
  justify-content: var(--loader-justify-content, center);
@@ -232,7 +232,8 @@
232
232
  opacity: var(--opacity, 1);
233
233
  border: var(--button-border, var(--_btn-border, none));
234
234
  box-sizing: border-box;
235
- text-decoration: none;
235
+ text-decoration: var(--button-text-decoration, none);
236
+ line-height: var(--button-line-height, normal);
236
237
  display: flex;
237
238
  justify-content: var(--button-justify-content, center);
238
239
  align-items: center;
@@ -255,6 +256,7 @@
255
256
  color: var(--disabled-text-color, var(--button-text-color, var(--_btn-text-color, white)));
256
257
  font-size: var(--disabled-font-size);
257
258
  font-weight: var(--disabled-font-weight);
259
+ text-decoration: var(--button-disabled-text-decoration, var(--button-text-decoration, none));
258
260
  /* Preserve the variant border when disabled so secondary (bordered) stays distinct from ghost. */
259
261
  border: var(--disabled-border, var(--button-border, var(--_btn-border, none)));
260
262
  background: var(
@@ -135,6 +135,12 @@
135
135
  });
136
136
 
137
137
  onDestroy(() => {
138
+ if (carouselDiv) {
139
+ carouselDiv.removeEventListener('touchstart', handleTouchStart);
140
+ carouselDiv.removeEventListener('touchend', handleTouchEnd);
141
+ carouselDiv.removeEventListener('mousedown', handleMouseDown);
142
+ carouselDiv.removeEventListener('mouseup', handeMouseUp);
143
+ }
138
144
  if (autoplay) {
139
145
  clearInterval(intervalId);
140
146
  }
@@ -151,11 +157,15 @@
151
157
  <div class="slidesDiv" bind:this={slidesDiv}>
152
158
  {#each views as view, index (index)}
153
159
  <div class="current-slide">
154
- {#if typeof view.properties === 'object'}
155
- <view.component properties={view.properties} />
156
- {:else}
157
- <view.component />
158
- {/if}
160
+ <!-- Spread is the real fix: every component in this library takes named
161
+ props, so passing the bag under a single `properties` key meant a
162
+ normal slide rendered empty. `properties` is ALSO still passed so
163
+ that a consumer who built a purpose-made wrapper around the old
164
+ behaviour keeps working -- that wrapper was the only shape this
165
+ component was usable with before, and breaking it would force a
166
+ major bump for a fix that is otherwise additive. Deprecated: read
167
+ the named props, not `properties`. -->
168
+ <view.component {...view.properties} properties={view.properties} />
159
169
  </div>
160
170
  {/each}
161
171
  </div>
@@ -172,7 +182,13 @@
172
182
  <div
173
183
  class={activeSlideIndex == index ? 'active-dot' : 'dot'}
174
184
  onclick={() => moveSlideToIndex(index)}
175
- {onkeydown}
185
+ onkeydown={(event) => {
186
+ if (event.key === 'Enter' || event.key === ' ') {
187
+ event.preventDefault();
188
+ moveSlideToIndex(index);
189
+ }
190
+ onkeydown?.(event);
191
+ }}
176
192
  role="button"
177
193
  tabindex="0"
178
194
  aria-label={`Go to slide ${index + 1}`}
@@ -182,6 +198,13 @@
182
198
  {/each}
183
199
  </div>
184
200
  {/if}
201
+ <!-- Slide changes are silent to assistive tech otherwise: the dots announce
202
+ where they GO, but nothing announces where the carousel ARRIVED, whether
203
+ the move came from autoplay, a swipe or a dot. polite so it never
204
+ interrupts, and atomic so the whole position is read as one phrase. -->
205
+ <div class="carousel-live-region" aria-live="polite" aria-atomic="true">
206
+ {#if views.length > 0}Slide {activeSlideIndex + 1} of {views.length}{/if}
207
+ </div>
185
208
  </div>
186
209
 
187
210
  <style>
@@ -231,6 +254,12 @@
231
254
  background: var(--carousel-dot-active-color, #000000);
232
255
  transition: 0.3s ease;
233
256
  }
257
+
258
+ .dot:focus-visible,
259
+ .active-dot:focus-visible {
260
+ outline: var(--dot-focus-outline, 2px solid #000000);
261
+ outline-offset: var(--dot-focus-outline-offset, 2px);
262
+ }
234
263
  /*
235
264
  @media only screen and (max-width: 324px) {
236
265
 
@@ -258,4 +287,19 @@
258
287
  }
259
288
 
260
289
  } */
290
+
291
+ /* Visually hidden but readable by assistive tech -- the standard clip pattern
292
+ rather than display:none, which would remove it from the a11y tree. */
293
+ .carousel-live-region {
294
+ position: absolute;
295
+ width: 1px;
296
+ height: 1px;
297
+ margin: -1px;
298
+ padding: 0;
299
+ overflow: hidden;
300
+ clip: rect(0 0 0 0);
301
+ clip-path: inset(50%);
302
+ white-space: nowrap;
303
+ border: 0;
304
+ }
261
305
  </style>
@@ -6,6 +6,11 @@ export type ChatMessageData = {
6
6
  role: ChatRole;
7
7
  content: string;
8
8
  html?: string;
9
+ /**
10
+ * Markdown source rendered through the library's sanitized pipeline (see
11
+ * MarkdownText). Wins over `html` and `content` in the bubble.
12
+ */
13
+ markdown?: string;
9
14
  streaming?: boolean;
10
15
  status?: ChatMessageStatus;
11
16
  timestamp?: number;
@@ -6,6 +6,7 @@
6
6
  import retrySvg from '../assets/retry.svg?raw';
7
7
  import thumbUpSvg from '../assets/thumb-up.svg?raw';
8
8
  import thumbDownSvg from '../assets/thumb-down.svg?raw';
9
+ import { onDestroy, onMount } from 'svelte';
9
10
  import { partyOf } from '../Chat/roles';
10
11
  import type { ChatMessageProperties } from './properties';
11
12
 
@@ -13,6 +14,7 @@
13
14
  role,
14
15
  content = '',
15
16
  html,
17
+ markdown,
16
18
  body,
17
19
  streaming = false,
18
20
  status,
@@ -37,7 +39,34 @@
37
39
  let copied = $state(false);
38
40
  let copyResetTimer: ReturnType<typeof setTimeout> | null = null;
39
41
 
40
- let hasHtml = $derived(typeof html === 'string' && html.length > 0);
42
+ onDestroy(() => {
43
+ if (copyResetTimer !== null) {
44
+ clearTimeout(copyResetTimer);
45
+ }
46
+ });
47
+
48
+ let hasMarkdown = $derived(typeof markdown === 'string' && markdown.length > 0);
49
+
50
+ /* The pipeline loads dynamically at mount (the LottiePlayer pattern) so
51
+ `marked` — a peer dependency — is only pulled in at runtime, never during
52
+ SSR, and rendering `markdown` degrades to the `html`/`content` fallback
53
+ until the module resolves or when the peer is absent. */
54
+ let renderMarkdownFn = $state<((source: string) => string) | null>(null);
55
+
56
+ onMount(() => {
57
+ void import('../MarkdownText/markdown')
58
+ .then((module) => {
59
+ renderMarkdownFn = module.renderMarkdown;
60
+ })
61
+ .catch(() => {
62
+ renderMarkdownFn = null;
63
+ });
64
+ });
65
+
66
+ let effectiveHtml = $derived(
67
+ hasMarkdown && renderMarkdownFn !== null ? renderMarkdownFn(markdown ?? '') : html
68
+ );
69
+ let hasHtml = $derived(typeof effectiveHtml === 'string' && effectiveHtml.length > 0);
41
70
  let hasBody = $derived(typeof body === 'function');
42
71
  let hasContent = $derived(hasBody || hasHtml || content.length > 0);
43
72
  let showTyping = $derived(streaming && !hasContent);
@@ -55,7 +84,14 @@
55
84
  }
56
85
 
57
86
  async function copy(): Promise<void> {
58
- const text = content.length > 0 ? content : typeof html === 'string' ? htmlToText(html) : '';
87
+ const text =
88
+ content.length > 0
89
+ ? content
90
+ : hasMarkdown
91
+ ? (markdown ?? '')
92
+ : typeof html === 'string'
93
+ ? htmlToText(html)
94
+ : '';
59
95
  if (typeof navigator !== 'undefined' && typeof navigator.clipboard?.writeText === 'function') {
60
96
  try {
61
97
  await navigator.clipboard.writeText(text);
@@ -97,7 +133,7 @@
97
133
  <div class="body">{@render body?.()}</div>
98
134
  {:else if hasHtml}
99
135
  <!-- eslint-disable-next-line svelte/no-at-html-tags -->
100
- <div class="body">{@html html}</div>
136
+ <div class="body">{@html effectiveHtml}</div>
101
137
  {:else if content.length > 0}
102
138
  <div class="body text">{content}</div>
103
139
  {/if}
@@ -331,4 +367,79 @@
331
367
  background: transparent;
332
368
  padding: 0;
333
369
  }
370
+
371
+ .body :global(ul),
372
+ .body :global(ol) {
373
+ margin: var(--chat-message-list-margin, 0.4em 0);
374
+ padding-left: var(--chat-message-list-padding, 1.4em);
375
+ }
376
+
377
+ .body :global(li) {
378
+ margin: 0.2em 0;
379
+ }
380
+
381
+ .body :global(h1),
382
+ .body :global(h2),
383
+ .body :global(h3),
384
+ .body :global(h4),
385
+ .body :global(h5),
386
+ .body :global(h6) {
387
+ margin: var(--chat-message-heading-margin, 0.8em 0 0.4em 0);
388
+ line-height: 1.3;
389
+ }
390
+
391
+ .body :global(h1) {
392
+ font-size: 1.35em;
393
+ }
394
+
395
+ .body :global(h2) {
396
+ font-size: 1.2em;
397
+ }
398
+
399
+ .body :global(h3) {
400
+ font-size: 1.1em;
401
+ }
402
+
403
+ .body :global(h4),
404
+ .body :global(h5),
405
+ .body :global(h6) {
406
+ font-size: 1em;
407
+ }
408
+
409
+ .body :global(blockquote) {
410
+ margin: 0.5em 0;
411
+ padding: 2px 0 2px 12px;
412
+ border-left: 3px solid var(--chat-message-blockquote-border-color, rgba(0, 0, 0, 0.15));
413
+ opacity: var(--chat-message-blockquote-opacity, 0.85);
414
+ }
415
+
416
+ .body :global(table) {
417
+ display: block;
418
+ max-width: 100%;
419
+ overflow-x: auto;
420
+ border-collapse: collapse;
421
+ margin: 0.5em 0;
422
+ }
423
+
424
+ .body :global(th),
425
+ .body :global(td) {
426
+ padding: 5px 10px;
427
+ border: 1px solid var(--chat-message-table-border-color, rgba(0, 0, 0, 0.12));
428
+ text-align: left;
429
+ }
430
+
431
+ .body :global(th) {
432
+ background: var(--chat-message-table-header-background, rgba(0, 0, 0, 0.04));
433
+ }
434
+
435
+ .body :global(img) {
436
+ max-width: 100%;
437
+ border-radius: var(--chat-message-image-border-radius, 8px);
438
+ }
439
+
440
+ .body :global(hr) {
441
+ border: none;
442
+ border-top: 1px solid var(--chat-message-hr-color, rgba(0, 0, 0, 0.12));
443
+ margin: 0.8em 0;
444
+ }
334
445
  </style>
@@ -8,6 +8,18 @@ export type MandatoryChatMessageProperties = {
8
8
  export type OptionalChatMessageProperties = {
9
9
  content?: string;
10
10
  html?: string;
11
+ /**
12
+ * Markdown source rendered through the library's sanitized-by-construction
13
+ * pipeline (raw HTML is escaped; unsafe link/image protocols are stripped) —
14
+ * unlike `html`, which must arrive pre-sanitized. Non-empty `markdown` takes
15
+ * precedence over `html` and `content`; a `body` snippet still wins. An
16
+ * empty string is treated as absent and falls through, exactly like `html`.
17
+ * Also serves as the copy text when `content` is empty. The pipeline loads
18
+ * on demand, so the `marked` peer is only needed when this prop is used;
19
+ * during SSR and while loading, `html`/`content` render as the fallback
20
+ * (for server-rendered markdown use `MarkdownText` or `renderMarkdown`).
21
+ */
22
+ markdown?: string;
11
23
  /**
12
24
  * Replaces the rendered bubble body with arbitrary markup while keeping the
13
25
  * message chrome (role styling, avatar, header, attachments, copy/retry/
@@ -300,6 +300,7 @@
300
300
  role={msg.role}
301
301
  content={msg.content}
302
302
  html={msg.html}
303
+ markdown={msg.markdown}
303
304
  body={typeof messageBody === 'function' ? bodyFor : null}
304
305
  streaming={msg.streaming}
305
306
  status={msg.status}
@@ -1,11 +1,13 @@
1
1
  <script lang="ts">
2
2
  import type { ChoiceboxProperties } from './properties';
3
+ import checkmarkSvg from '../assets/checkmark.svg?raw';
3
4
 
4
5
  let {
5
6
  children,
6
7
  selected = $bindable(false),
7
8
  mode = 'radio',
8
9
  disabled = false,
10
+ showIndicator = true,
9
11
  testId,
10
12
  onclick,
11
13
  classes
@@ -47,6 +49,14 @@
47
49
  {#if typeof children === 'function'}
48
50
  {@render children()}
49
51
  {/if}
52
+ {#if showIndicator}
53
+ <span class="indicator {mode}" class:selected aria-hidden="true">
54
+ {#if mode === 'checkbox' && selected}
55
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
56
+ <span class="indicator-icon">{@html checkmarkSvg}</span>
57
+ {/if}
58
+ </span>
59
+ {/if}
50
60
  </div>
51
61
 
52
62
  <style>
@@ -87,4 +97,49 @@
87
97
  opacity: var(--choicebox-disabled-opacity, 0.4);
88
98
  cursor: var(--choicebox-disabled-cursor, not-allowed);
89
99
  }
100
+
101
+ .indicator {
102
+ flex: 0 0 auto;
103
+ order: var(--choicebox-indicator-order, 1);
104
+ margin-inline-start: var(--choicebox-indicator-margin-inline-start, auto);
105
+ display: flex;
106
+ align-items: center;
107
+ justify-content: center;
108
+ box-sizing: border-box;
109
+ width: var(--choicebox-indicator-size, 20px);
110
+ height: var(--choicebox-indicator-size, 20px);
111
+ border: var(--choicebox-indicator-border, 2px solid #757575);
112
+ background: var(--choicebox-indicator-background, transparent);
113
+ transition: var(--choicebox-indicator-transition, background 0.2s, border-color 0.2s);
114
+ }
115
+
116
+ .indicator.radio {
117
+ border-radius: 50%;
118
+ }
119
+
120
+ .indicator.checkbox {
121
+ border-radius: var(--choicebox-indicator-border-radius, var(--radius, 4px));
122
+ }
123
+
124
+ .indicator.selected {
125
+ border: var(--choicebox-indicator-selected-border, 2px solid #2196f3);
126
+ background: var(--choicebox-indicator-selected-background, #2196f3);
127
+ }
128
+
129
+ .indicator.radio.selected {
130
+ box-shadow: inset 0 0 0 var(--choicebox-indicator-dot-inset, 4px)
131
+ var(--choicebox-background, #ffffff);
132
+ }
133
+
134
+ .indicator-icon {
135
+ display: flex;
136
+ width: var(--choicebox-indicator-icon-size, 14px);
137
+ height: var(--choicebox-indicator-icon-size, 14px);
138
+ color: var(--choicebox-indicator-icon-color, #ffffff);
139
+ }
140
+
141
+ .indicator-icon :global(svg) {
142
+ width: 100%;
143
+ height: 100%;
144
+ }
90
145
  </style>
@@ -6,6 +6,7 @@ export type OptionalChoiceboxProperties = {
6
6
  selected?: boolean;
7
7
  mode?: ChoiceboxMode;
8
8
  disabled?: boolean;
9
+ showIndicator?: boolean;
9
10
  testId?: string;
10
11
  classes?: string;
11
12
  };
@@ -435,7 +435,12 @@
435
435
  </div>
436
436
  {/if}
437
437
  {#if isShowingInfo}
438
- <div id={infoMessageId} class="info-message">
438
+ <div
439
+ id={infoMessageId}
440
+ class="info-message"
441
+ data-pw={typeof testId === 'string' && testId.length > 0 ? `${testId}-info-message` : null}
442
+ testID={typeof testId === 'string' && testId.length > 0 ? `${testId}-info-message` : null}
443
+ >
439
444
  {infoMessage}
440
445
  </div>
441
446
  {/if}
@@ -492,11 +497,11 @@
492
497
  .input-error {
493
498
  --input-focus-border: var(
494
499
  --input-error-border,
495
- 1px solid var(--input-error-msg-text-color, #fa1405)
500
+ 1px solid var(--input-error-msg-text-color, #c5120a)
496
501
  ) !important;
497
502
  --input-border: var(
498
503
  --input-error-border,
499
- 1px solid var(--input-error-msg-text-color, #fa1405)
504
+ 1px solid var(--input-error-msg-text-color, #c5120a)
500
505
  ) !important;
501
506
  }
502
507
 
@@ -523,7 +528,7 @@
523
528
  }
524
529
 
525
530
  .input-mandatory-asterisk {
526
- color: var(--input-mandatory-color, var(--input-error-msg-text-color, #fa1405));
531
+ color: var(--input-mandatory-color, var(--input-error-msg-text-color, #c5120a));
527
532
  margin-left: var(--input-mandatory-gap, 2px);
528
533
  }
529
534
 
@@ -591,7 +596,9 @@
591
596
  .error-message {
592
597
  font-weight: var(--input-error-msg-text-weight, 400);
593
598
  font-size: var(--input-error-msg-text-size, 12px);
594
- color: var(--input-error-msg-text-color, #fa1405);
599
+ /* #c5120a clears WCAG AA (6.06:1 on white) at this 12px size; the prior #fa1405
600
+ default was only 4.06:1, so the error message itself failed to be legible. */
601
+ color: var(--input-error-msg-text-color, #c5120a);
595
602
  margin: var(--input-error-msg-margin);
596
603
  padding: var(--input-error-msg-padding);
597
604
  }
@@ -599,7 +606,11 @@
599
606
  .info-message {
600
607
  font-weight: var(--input-info-msg-text-weight, 400);
601
608
  font-size: var(--input-info-msg-text-size, 12px);
602
- color: var(--input-info-msg-text-color, #fa1405);
609
+ /* Neutral on purpose -- this used to default to the same red as .error-message,
610
+ so helper text with no error visually read as a failed field (color-alone
611
+ signalling, WCAG 1.4.1). #52525b is the muted-text tone already used for
612
+ ChatToolStatus/ThinkingIndicator elsewhere in this library, at 7.73:1. */
613
+ color: var(--input-info-msg-text-color, #52525b);
603
614
  margin: var(--input-info-msg-margin);
604
615
  padding: var(--input-info-msg-padding);
605
616
  }
@@ -613,7 +624,7 @@
613
624
  }
614
625
 
615
626
  .input-char-count.at-limit {
616
- color: var(--input-char-count-limit-color, var(--input-error-msg-text-color, #fa1405));
627
+ color: var(--input-char-count-limit-color, var(--input-error-msg-text-color, #c5120a));
617
628
  }
618
629
 
619
630
  ::placeholder {
@@ -193,14 +193,18 @@
193
193
  .error-message {
194
194
  font-weight: var(--input-error-msg-text-weight, 400);
195
195
  font-size: var(--input-error-msg-text-size, 12px);
196
- color: var(--input-error-msg-text-color, #fa1405);
196
+ /* Shares --input-error-msg-text-color with Input.svelte -- kept in sync with its
197
+ #c5120a default (6.06:1 AA) so the same token means the same color everywhere. */
198
+ color: var(--input-error-msg-text-color, #c5120a);
197
199
  margin: var(--input-btn-error-msg-margin, 12px 0px 0px 0px);
198
200
  }
199
201
 
200
202
  .info-message {
201
203
  font-weight: var(--input-info-msg-text-weight, 400);
202
204
  font-size: var(--input-info-msg-text-size, 12px);
203
- color: var(--input-info-msg-text-color, #fa1405);
205
+ /* Shares --input-info-msg-text-color with Input.svelte -- see its .info-message
206
+ for why this is a neutral #52525b rather than the error red. */
207
+ color: var(--input-info-msg-text-color, #52525b);
204
208
  margin: var(--input-btn-info-msg-margin, 12px 0px 0px 0px);
205
209
  }
206
210
 
@@ -224,7 +228,7 @@
224
228
  }
225
229
 
226
230
  .external-error-message {
227
- color: var(--inputbutton-external-error-color, #fa1405);
231
+ color: var(--inputbutton-external-error-color, #c5120a);
228
232
  margin: var(--inputbutton-external-error-margin, 6px 0px 0px 0px);
229
233
  }
230
234
 
@@ -0,0 +1,138 @@
1
+ <script lang="ts">
2
+ import { renderMarkdown } from './markdown';
3
+ import type { MarkdownTextProperties } from './properties';
4
+
5
+ let { markdown, breaks = false, testId, classes }: MarkdownTextProperties = $props();
6
+
7
+ let html = $derived(renderMarkdown(markdown, { breaks }));
8
+ </script>
9
+
10
+ <div
11
+ class="markdown-text {classes ?? ''}"
12
+ data-pw={typeof testId === 'string' ? testId : null}
13
+ testID={typeof testId === 'string' ? testId : null}
14
+ >
15
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
16
+ {@html html}
17
+ </div>
18
+
19
+ <style>
20
+ .markdown-text {
21
+ font-size: var(--markdown-text-font-size, inherit);
22
+ line-height: var(--markdown-text-line-height, 1.5);
23
+ color: var(--markdown-text-color, inherit);
24
+ word-wrap: break-word;
25
+ overflow-wrap: anywhere;
26
+ }
27
+
28
+ .markdown-text :global(p) {
29
+ margin: var(--markdown-text-paragraph-margin, 0 0 0.5em 0);
30
+ }
31
+
32
+ .markdown-text :global(p:last-child) {
33
+ margin-bottom: 0;
34
+ }
35
+
36
+ .markdown-text :global(h1),
37
+ .markdown-text :global(h2),
38
+ .markdown-text :global(h3),
39
+ .markdown-text :global(h4),
40
+ .markdown-text :global(h5),
41
+ .markdown-text :global(h6) {
42
+ margin: var(--markdown-text-heading-margin, 0.8em 0 0.4em 0);
43
+ line-height: 1.3;
44
+ }
45
+
46
+ .markdown-text :global(h1) {
47
+ font-size: 1.35em;
48
+ }
49
+
50
+ .markdown-text :global(h2) {
51
+ font-size: 1.2em;
52
+ }
53
+
54
+ .markdown-text :global(h3) {
55
+ font-size: 1.1em;
56
+ }
57
+
58
+ .markdown-text :global(h4),
59
+ .markdown-text :global(h5),
60
+ .markdown-text :global(h6) {
61
+ font-size: 1em;
62
+ }
63
+
64
+ .markdown-text :global(a) {
65
+ color: var(--markdown-text-link-color, #6d28d9);
66
+ text-decoration: underline;
67
+ text-underline-offset: 2px;
68
+ }
69
+
70
+ .markdown-text :global(code) {
71
+ font-family: var(--markdown-text-code-font-family, ui-monospace, monospace);
72
+ font-size: 0.88em;
73
+ background: var(--markdown-text-code-background, rgba(0, 0, 0, 0.05));
74
+ padding: 1px 5px;
75
+ border-radius: 4px;
76
+ }
77
+
78
+ .markdown-text :global(pre) {
79
+ background: var(--markdown-text-pre-background, rgba(0, 0, 0, 0.05));
80
+ padding: 10px 12px;
81
+ border-radius: 8px;
82
+ overflow-x: auto;
83
+ margin: 0.5em 0;
84
+ }
85
+
86
+ .markdown-text :global(pre code) {
87
+ background: transparent;
88
+ padding: 0;
89
+ }
90
+
91
+ .markdown-text :global(ul),
92
+ .markdown-text :global(ol) {
93
+ margin: var(--markdown-text-list-margin, 0.4em 0);
94
+ padding-left: var(--markdown-text-list-padding, 1.4em);
95
+ }
96
+
97
+ .markdown-text :global(li) {
98
+ margin: 0.2em 0;
99
+ }
100
+
101
+ .markdown-text :global(blockquote) {
102
+ margin: 0.5em 0;
103
+ padding: 2px 0 2px 12px;
104
+ border-left: 3px solid var(--markdown-text-blockquote-border-color, rgba(0, 0, 0, 0.15));
105
+ color: var(--markdown-text-blockquote-color, inherit);
106
+ opacity: var(--markdown-text-blockquote-opacity, 0.85);
107
+ }
108
+
109
+ .markdown-text :global(table) {
110
+ display: block;
111
+ max-width: 100%;
112
+ overflow-x: auto;
113
+ border-collapse: collapse;
114
+ margin: 0.5em 0;
115
+ }
116
+
117
+ .markdown-text :global(th),
118
+ .markdown-text :global(td) {
119
+ padding: 5px 10px;
120
+ border: 1px solid var(--markdown-text-table-border-color, rgba(0, 0, 0, 0.12));
121
+ text-align: left;
122
+ }
123
+
124
+ .markdown-text :global(th) {
125
+ background: var(--markdown-text-table-header-background, rgba(0, 0, 0, 0.04));
126
+ }
127
+
128
+ .markdown-text :global(img) {
129
+ max-width: 100%;
130
+ border-radius: var(--markdown-text-image-border-radius, 8px);
131
+ }
132
+
133
+ .markdown-text :global(hr) {
134
+ border: none;
135
+ border-top: 1px solid var(--markdown-text-hr-color, rgba(0, 0, 0, 0.12));
136
+ margin: 0.8em 0;
137
+ }
138
+ </style>
@@ -0,0 +1,4 @@
1
+ import type { MarkdownTextProperties } from './properties';
2
+ declare const MarkdownText: import("svelte").Component<MarkdownTextProperties, {}, "">;
3
+ type MarkdownText = ReturnType<typeof MarkdownText>;
4
+ export default MarkdownText;
@@ -0,0 +1,6 @@
1
+ import type { RenderMarkdownOptions } from './properties';
2
+ /**
3
+ * Markdown → HTML with the sanitizing pipeline above. Pure string transform —
4
+ * no DOM involved — so it renders identically on server and client.
5
+ */
6
+ export declare function renderMarkdown(markdown: string, options?: RenderMarkdownOptions): string;