@aceshooting/lyra-ui 2.0.0 → 2.1.0

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 (224) hide show
  1. package/README.md +87 -14
  2. package/custom-elements.json +11555 -5539
  3. package/dist/components/app-rail/app-rail-item.d.ts +30 -0
  4. package/dist/components/app-rail/app-rail-item.js +68 -0
  5. package/dist/components/app-rail/app-rail-item.styles.d.ts +1 -0
  6. package/dist/components/app-rail/app-rail-item.styles.js +65 -0
  7. package/dist/components/app-rail/app-rail.d.ts +21 -24
  8. package/dist/components/app-rail/app-rail.js +68 -132
  9. package/dist/components/app-rail/app-rail.styles.js +5 -2
  10. package/dist/components/attachment-chip/attachment-chip.d.ts +38 -11
  11. package/dist/components/attachment-chip/attachment-chip.js +73 -29
  12. package/dist/components/attachment-trigger/attachment-trigger.d.ts +13 -0
  13. package/dist/components/attachment-trigger/attachment-trigger.js +13 -1
  14. package/dist/components/attachment-trigger/attachment-trigger.styles.js +28 -6
  15. package/dist/components/chart/bar-chart.js +2 -18
  16. package/dist/components/chart/box-plot.d.ts +25 -1
  17. package/dist/components/chart/box-plot.js +136 -6
  18. package/dist/components/chart/box-plot.styles.js +13 -0
  19. package/dist/components/chart/bubble-chart.js +2 -18
  20. package/dist/components/chart/chart.d.ts +54 -10
  21. package/dist/components/chart/chart.js +182 -39
  22. package/dist/components/chart/doughnut-chart.js +2 -18
  23. package/dist/components/chart/histogram-bin.d.ts +5 -2
  24. package/dist/components/chart/histogram-bin.js +12 -4
  25. package/dist/components/chart/histogram.js +8 -16
  26. package/dist/components/chart/line-chart.js +2 -18
  27. package/dist/components/chart/lite-chart.d.ts +80 -3
  28. package/dist/components/chart/lite-chart.js +250 -53
  29. package/dist/components/chart/pie-chart.js +2 -18
  30. package/dist/components/chart/polar-area-chart.js +2 -18
  31. package/dist/components/chart/radar-chart.js +2 -18
  32. package/dist/components/chart/scatter-chart.js +2 -18
  33. package/dist/components/chat-composer/chat-composer.d.ts +42 -1
  34. package/dist/components/chat-composer/chat-composer.js +84 -3
  35. package/dist/components/chat-composer/chat-composer.styles.js +11 -1
  36. package/dist/components/chat-message/chat-message.d.ts +32 -4
  37. package/dist/components/chat-message/chat-message.js +57 -17
  38. package/dist/components/checkbox/checkbox.d.ts +66 -8
  39. package/dist/components/checkbox/checkbox.js +173 -50
  40. package/dist/components/checkbox/checkbox.styles.js +9 -2
  41. package/dist/components/chip/chip-group.js +8 -2
  42. package/dist/components/chip/chip.js +9 -2
  43. package/dist/components/citation-badge/citation-badge.js +21 -3
  44. package/dist/components/code-block/code-block.d.ts +38 -0
  45. package/dist/components/code-block/code-block.js +72 -7
  46. package/dist/components/code-block/code-loader.d.ts +44 -1
  47. package/dist/components/code-block/code-loader.js +63 -0
  48. package/dist/components/combobox/combobox.d.ts +46 -5
  49. package/dist/components/combobox/combobox.js +175 -35
  50. package/dist/components/combobox/combobox.styles.js +3 -3
  51. package/dist/components/context-meter/context-meter.js +17 -8
  52. package/dist/components/context-meter/context-meter.styles.js +0 -6
  53. package/dist/components/conversation-item/conversation-item.d.ts +13 -16
  54. package/dist/components/conversation-item/conversation-item.js +29 -20
  55. package/dist/components/date-picker/calendar-core.d.ts +24 -0
  56. package/dist/components/date-picker/calendar-core.js +51 -4
  57. package/dist/components/date-picker/date-input.d.ts +77 -10
  58. package/dist/components/date-picker/date-input.js +278 -73
  59. package/dist/components/date-picker/date-picker.d.ts +28 -4
  60. package/dist/components/date-picker/date-picker.js +95 -37
  61. package/dist/components/dialog/confirm.js +3 -6
  62. package/dist/components/dialog/dialog.d.ts +75 -31
  63. package/dist/components/dialog/dialog.js +147 -131
  64. package/dist/components/dialog/dialog.styles.js +46 -2
  65. package/dist/components/dock-panel/dock-panel.d.ts +8 -0
  66. package/dist/components/dock-panel/dock-panel.js +15 -1
  67. package/dist/components/dock-panel/dock-panel.styles.js +15 -1
  68. package/dist/components/document-preview/document-preview.d.ts +15 -7
  69. package/dist/components/document-preview/document-preview.js +29 -13
  70. package/dist/components/empty/empty.d.ts +1 -2
  71. package/dist/components/empty/empty.js +18 -12
  72. package/dist/components/empty/empty.styles.js +0 -3
  73. package/dist/components/export-button/export-button.d.ts +12 -0
  74. package/dist/components/export-button/export-button.js +33 -5
  75. package/dist/components/export-button/export-button.styles.js +5 -1
  76. package/dist/components/file-input/accept.js +2 -0
  77. package/dist/components/file-input/file-input.d.ts +10 -1
  78. package/dist/components/file-input/file-input.js +36 -5
  79. package/dist/components/flag/flag.d.ts +1 -1
  80. package/dist/components/flag/flag.js +15 -1
  81. package/dist/components/flag/flag.styles.js +1 -1
  82. package/dist/components/flag/language-map.d.ts +4 -1
  83. package/dist/components/flag/language-map.js +6 -3
  84. package/dist/components/gauge/gauge.d.ts +10 -1
  85. package/dist/components/gauge/gauge.js +28 -9
  86. package/dist/components/generation-status/generation-status.d.ts +13 -5
  87. package/dist/components/generation-status/generation-status.js +59 -5
  88. package/dist/components/graph/graph.d.ts +37 -3
  89. package/dist/components/graph/graph.js +132 -20
  90. package/dist/components/heatmap/calendar-grid.d.ts +15 -6
  91. package/dist/components/heatmap/calendar-grid.js +17 -6
  92. package/dist/components/heatmap/heatmap-scale.d.ts +5 -1
  93. package/dist/components/heatmap/heatmap-scale.js +8 -2
  94. package/dist/components/heatmap/heatmap.d.ts +166 -25
  95. package/dist/components/heatmap/heatmap.js +372 -89
  96. package/dist/components/json-viewer/json-viewer.d.ts +22 -5
  97. package/dist/components/json-viewer/json-viewer.js +115 -37
  98. package/dist/components/json-viewer/json-viewer.styles.js +2 -1
  99. package/dist/components/kbd/kbd.js +24 -7
  100. package/dist/components/live-region/live-region.d.ts +10 -1
  101. package/dist/components/live-region/live-region.js +42 -6
  102. package/dist/components/map/map.d.ts +24 -1
  103. package/dist/components/map/map.js +89 -24
  104. package/dist/components/markdown/markdown-loader.d.ts +24 -0
  105. package/dist/components/markdown/markdown-loader.js +35 -1
  106. package/dist/components/markdown/markdown.d.ts +46 -8
  107. package/dist/components/markdown/markdown.js +136 -31
  108. package/dist/components/markdown/markdown.styles.js +7 -1
  109. package/dist/components/media-card/media-card.d.ts +25 -17
  110. package/dist/components/media-card/media-card.js +43 -65
  111. package/dist/components/mention-popover/mention-popover.d.ts +4 -1
  112. package/dist/components/mention-popover/mention-popover.js +23 -5
  113. package/dist/components/menu/menu-item.d.ts +32 -3
  114. package/dist/components/menu/menu-item.js +94 -7
  115. package/dist/components/menu/menu-item.styles.js +8 -0
  116. package/dist/components/menu/menu.d.ts +25 -1
  117. package/dist/components/menu/menu.js +99 -3
  118. package/dist/components/menu/menu.styles.js +3 -2
  119. package/dist/components/model-select/model-select.d.ts +48 -3
  120. package/dist/components/model-select/model-select.js +120 -22
  121. package/dist/components/model-select/model-select.styles.js +17 -3
  122. package/dist/components/model-settings-panel/model-settings-panel.d.ts +25 -1
  123. package/dist/components/model-settings-panel/model-settings-panel.js +65 -1
  124. package/dist/components/playback/playback.d.ts +6 -2
  125. package/dist/components/playback/playback.js +72 -29
  126. package/dist/components/playback/playback.styles.js +6 -1
  127. package/dist/components/responsive-panel/responsive-panel.d.ts +29 -16
  128. package/dist/components/responsive-panel/responsive-panel.js +130 -132
  129. package/dist/components/responsive-panel/responsive-panel.styles.js +1 -1
  130. package/dist/components/result-card/result-card.d.ts +8 -2
  131. package/dist/components/result-card/result-card.js +9 -3
  132. package/dist/components/result-card/result-card.styles.js +0 -1
  133. package/dist/components/select/select.d.ts +59 -20
  134. package/dist/components/select/select.js +125 -43
  135. package/dist/components/skeleton/skeleton.d.ts +3 -0
  136. package/dist/components/skeleton/skeleton.js +7 -1
  137. package/dist/components/skeleton/skeleton.styles.js +5 -4
  138. package/dist/components/slider/slider.d.ts +57 -5
  139. package/dist/components/slider/slider.js +195 -80
  140. package/dist/components/source-card/source-card.d.ts +17 -5
  141. package/dist/components/source-card/source-card.js +55 -8
  142. package/dist/components/source-card/source-card.styles.js +1 -1
  143. package/dist/components/source-list/source-list.js +9 -1
  144. package/dist/components/sparkline/sparkline.js +34 -10
  145. package/dist/components/split/split.d.ts +141 -14
  146. package/dist/components/split/split.js +357 -52
  147. package/dist/components/split/split.styles.js +18 -1
  148. package/dist/components/stat/stat.d.ts +10 -4
  149. package/dist/components/stat/stat.js +46 -11
  150. package/dist/components/stream-status/stream-status.d.ts +27 -11
  151. package/dist/components/stream-status/stream-status.js +95 -13
  152. package/dist/components/streaming-text/streaming-text.d.ts +9 -3
  153. package/dist/components/streaming-text/streaming-text.js +25 -10
  154. package/dist/components/streaming-text/streaming-text.styles.js +3 -2
  155. package/dist/components/switch/switch.d.ts +52 -7
  156. package/dist/components/switch/switch.js +111 -40
  157. package/dist/components/switch/switch.styles.js +1 -1
  158. package/dist/components/table/table.d.ts +18 -3
  159. package/dist/components/table/table.js +88 -20
  160. package/dist/components/tabs/tabs.d.ts +23 -0
  161. package/dist/components/tabs/tabs.js +76 -12
  162. package/dist/components/tabs/tabs.styles.js +11 -0
  163. package/dist/components/thinking-panel/thinking-panel.d.ts +3 -1
  164. package/dist/components/thinking-panel/thinking-panel.js +34 -9
  165. package/dist/components/thinking-panel/thinking-panel.styles.js +6 -0
  166. package/dist/components/time-range/time-range.d.ts +59 -2
  167. package/dist/components/time-range/time-range.js +163 -67
  168. package/dist/components/toast/toast-item.d.ts +12 -3
  169. package/dist/components/toast/toast-item.js +90 -53
  170. package/dist/components/toast/toast-item.styles.js +59 -28
  171. package/dist/components/toast/toast.js +17 -5
  172. package/dist/components/toast/toaster.js +10 -1
  173. package/dist/components/tool-approval-dialog/tool-approval-dialog.d.ts +11 -10
  174. package/dist/components/tool-approval-dialog/tool-approval-dialog.js +50 -124
  175. package/dist/components/tool-approval-dialog/tool-approval-dialog.styles.js +1 -1
  176. package/dist/components/tool-call-chip/tool-call-chip.d.ts +18 -2
  177. package/dist/components/tool-call-chip/tool-call-chip.js +86 -25
  178. package/dist/components/tool-param-form/tool-param-form.d.ts +89 -28
  179. package/dist/components/tool-param-form/tool-param-form.js +350 -106
  180. package/dist/components/tool-result-dialog/tool-result-dialog.d.ts +27 -7
  181. package/dist/components/tool-result-dialog/tool-result-dialog.js +80 -115
  182. package/dist/components/tool-result-dialog/tool-result-dialog.styles.js +1 -1
  183. package/dist/components/tool-result-view/registry.d.ts +0 -8
  184. package/dist/components/tool-result-view/registry.js +7 -2
  185. package/dist/components/tool-result-view/tool-result-view.d.ts +6 -3
  186. package/dist/components/tool-result-view/tool-result-view.js +18 -4
  187. package/dist/components/tool-select-dialog/tool-select-dialog.d.ts +11 -11
  188. package/dist/components/tool-select-dialog/tool-select-dialog.js +44 -133
  189. package/dist/components/tool-select-dialog/tool-select-dialog.styles.js +13 -4
  190. package/dist/components/tree/tree-node.d.ts +4 -1
  191. package/dist/components/tree/tree-node.js +4 -1
  192. package/dist/components/tree/tree.d.ts +17 -8
  193. package/dist/components/tree/tree.js +21 -20
  194. package/dist/components/typing-indicator/typing-indicator.d.ts +12 -2
  195. package/dist/components/typing-indicator/typing-indicator.js +16 -4
  196. package/dist/components/typing-indicator/typing-indicator.styles.js +2 -2
  197. package/dist/components/virtual-list/virtual-list.d.ts +56 -19
  198. package/dist/components/virtual-list/virtual-list.js +184 -38
  199. package/dist/components/virtual-list/virtual-list.styles.js +12 -0
  200. package/dist/components/widget/widget.d.ts +5 -4
  201. package/dist/components/widget/widget.js +54 -120
  202. package/dist/components/widget/widget.styles.js +8 -2
  203. package/dist/components/word-cloud/word-cloud-layout.d.ts +7 -0
  204. package/dist/components/word-cloud/word-cloud-layout.js +20 -6
  205. package/dist/components/word-cloud/word-cloud.d.ts +22 -2
  206. package/dist/components/word-cloud/word-cloud.js +45 -6
  207. package/dist/components/word-cloud/word-cloud.styles.js +13 -1
  208. package/dist/internal/anchored-validity.d.ts +1 -0
  209. package/dist/internal/anchored-validity.js +86 -0
  210. package/dist/internal/form-associated.d.ts +7 -0
  211. package/dist/internal/form-associated.js +43 -4
  212. package/dist/internal/numbers.d.ts +12 -0
  213. package/dist/internal/numbers.js +26 -0
  214. package/dist/internal/overlay-manager.d.ts +51 -0
  215. package/dist/internal/overlay-manager.js +523 -0
  216. package/dist/internal/positioner.js +27 -3
  217. package/dist/internal/safe-url.d.ts +12 -0
  218. package/dist/internal/safe-url.js +41 -0
  219. package/dist/internal/tokens.styles.js +17 -11
  220. package/dist/lyra.d.ts +5 -2
  221. package/dist/lyra.js +3 -1
  222. package/llms-full.txt +970 -426
  223. package/llms.txt +5 -4
  224. package/package.json +19 -2
@@ -65,10 +65,22 @@ function sanitizeSwatchColor(color) {
65
65
  * choropleth GeoJSON layer, plus a raw `map` escape hatch. Requires the
66
66
  * optional peer dep `maplibre-gl` (consumers also import its CSS once).
67
67
  *
68
+ * The underlying `maplibregl.Map` — and the WebGL context it opens — isn't
69
+ * constructed until this element is first visible in the viewport (tracked
70
+ * via `IntersectionObserver`), even once the `maplibre-gl` peer dependency
71
+ * has finished loading. Browsers hard-cap concurrent WebGL contexts per
72
+ * page, so a grid/dashboard of many `<lyra-map>` instances only constructs
73
+ * the ones actually on-screen instead of racing to exhaust that budget the
74
+ * instant each one mounts. `map` stays `undefined` (and `lyra-map-load`
75
+ * doesn't fire) until construction actually happens.
76
+ *
68
77
  * @customElement lyra-map
69
78
  * @event lyra-map-load - Fired once the underlying maplibregl.Map loads.
70
79
  * @event lyra-map-click - `detail: { lngLat, feature? }`.
71
- * @csspart base, container, legend, legend-swatch
80
+ * @csspart base - The map wrapper.
81
+ * @csspart container - The maplibre container.
82
+ * @csspart legend - The map legend.
83
+ * @csspart legend-swatch - A legend color swatch.
72
84
  *
73
85
  * ⚠️ The default `mapStyle` (when unset) uses OpenStreetMap's demo tile
74
86
  * server, which is not suitable for production traffic — see the
@@ -85,6 +97,14 @@ export class LyraMap extends LyraElement {
85
97
  this.markers = [];
86
98
  /** True until the lazy-loaded `maplibre-gl` peer dependency has settled (success or failure). */
87
99
  this.loading = true;
100
+ // Gates the actual `new mod.Map(...)` construction (see `tryConstructMap()`)
101
+ // -- starts `false` so a `<lyra-map>` mounted off-screen doesn't open a
102
+ // WebGL context before it's ever seen, and only flips `true` once the
103
+ // IntersectionObserver below reports this element actually intersecting
104
+ // the viewport. Defaults `true` outright when `IntersectionObserver` isn't
105
+ // available at all (fail open, matching `lyra-chart`'s own fallback)
106
+ // rather than gating construction on an observer that will never fire.
107
+ this.visible = typeof IntersectionObserver === 'undefined';
88
108
  // Tracks whether the style has fired its initial 'load' (i.e. addSource/
89
109
  // addLayer/setPaintProperty are now safe to call), rather than re-querying
90
110
  // `this._map.isStyleLoaded()`: that also reflects in-flight *tile* loading
@@ -121,6 +141,17 @@ export class LyraMap extends LyraElement {
121
141
  connectedCallback() {
122
142
  super.connectedCallback();
123
143
  const generation = ++this._connectGeneration;
144
+ // A reconnect always tears the map down in disconnectedCallback() below,
145
+ // so it needs its own fresh visibility read rather than trusting
146
+ // whatever `visible` was left at from before the previous disconnect.
147
+ this.visible = typeof IntersectionObserver === 'undefined';
148
+ if (typeof IntersectionObserver !== 'undefined') {
149
+ this.intersectionObserver = new IntersectionObserver((entries) => {
150
+ if (entries[0]?.isIntersecting)
151
+ this.visible = true;
152
+ });
153
+ this.intersectionObserver.observe(this);
154
+ }
124
155
  void loadMaplibre().then(async (mod) => {
125
156
  // A newer connectedCallback (disconnect + reconnect) already
126
157
  // superseded this attempt while loadMaplibre()'s cached promise was
@@ -144,28 +175,7 @@ export class LyraMap extends LyraElement {
144
175
  // cycle that happened during the `await` above.
145
176
  if (generation !== this._connectGeneration || !this.containerEl || !this.isConnected)
146
177
  return;
147
- this._map = new mod.Map({
148
- container: this.containerEl,
149
- style: this.mapStyle,
150
- center: this.center,
151
- zoom: this.zoom,
152
- });
153
- this._map.on('load', () => {
154
- this._styleLoaded = true;
155
- this.applyChoropleth();
156
- this.applyMarkers();
157
- this.emit('lyra-map-load');
158
- });
159
- this._map.on('click', (e) => {
160
- const fillLayerId = this._appliedFillLayerId;
161
- const features = fillLayerId && this._map.getLayer(fillLayerId)
162
- ? this._map.queryRenderedFeatures(e.point, { layers: [fillLayerId] })
163
- : [];
164
- this.emit('lyra-map-click', {
165
- lngLat: [e.lngLat.lng, e.lngLat.lat],
166
- feature: features[0],
167
- });
168
- });
178
+ this.tryConstructMap();
169
179
  });
170
180
  }
171
181
  disconnectedCallback() {
@@ -175,16 +185,59 @@ export class LyraMap extends LyraElement {
175
185
  this._styleLoaded = false;
176
186
  this._appliedChoroplethSourceId = undefined;
177
187
  this._appliedFillLayerId = undefined;
188
+ this.intersectionObserver?.disconnect();
189
+ this.intersectionObserver = undefined;
178
190
  for (const marker of this._markerInstances.values())
179
191
  marker.remove();
180
192
  this._markerInstances.clear();
181
193
  this._markerColors.clear();
182
194
  }
195
+ /**
196
+ * Constructs the underlying `maplibregl.Map` — called once both the
197
+ * lazy-loaded `maplibre-gl` module has resolved and this element has been
198
+ * observed intersecting the viewport, whichever settles last. Idempotent
199
+ * (a no-op once `_map` already exists), so it's safe to call from both the
200
+ * `connectedCallback()` load path and the visibility path in `updated()`
201
+ * below without risking a double construction.
202
+ */
203
+ tryConstructMap() {
204
+ if (this._map || !this._maplibreModule || !this.containerEl || !this.visible || !this.isConnected)
205
+ return;
206
+ const mod = this._maplibreModule;
207
+ this._map = new mod.Map({
208
+ container: this.containerEl,
209
+ style: this.mapStyle,
210
+ center: this.center,
211
+ zoom: this.zoom,
212
+ });
213
+ this._map.on('load', () => {
214
+ this._styleLoaded = true;
215
+ this.applyChoropleth();
216
+ this.applyMarkers();
217
+ this.emit('lyra-map-load');
218
+ });
219
+ this._map.on('click', (e) => {
220
+ const fillLayerId = this._appliedFillLayerId;
221
+ const features = fillLayerId && this._map.getLayer(fillLayerId)
222
+ ? this._map.queryRenderedFeatures(e.point, { layers: [fillLayerId] })
223
+ : [];
224
+ this.emit('lyra-map-click', {
225
+ lngLat: [e.lngLat.lng, e.lngLat.lat],
226
+ feature: features[0],
227
+ });
228
+ });
229
+ }
183
230
  updated(changed) {
184
231
  if (this.loading)
185
232
  this.setAttribute('aria-busy', 'true');
186
233
  else
187
234
  this.removeAttribute('aria-busy');
235
+ // Became visible after the maplibre-gl module had already loaded (the
236
+ // reverse order — module loads first, visibility follows — is the
237
+ // common case and is instead handled at the end of the
238
+ // `loadMaplibre().then()` chain in connectedCallback() above).
239
+ if (changed.has('visible') && this.visible)
240
+ this.tryConstructMap();
188
241
  if (changed.has('mapStyle') && this._map) {
189
242
  // A style change wipes every layer/source maplibre-gl knows about, so
190
243
  // the previously-applied choropleth bookkeeping is stale the instant
@@ -233,7 +286,16 @@ export class LyraMap extends LyraElement {
233
286
  existingSource.setData(geojson);
234
287
  }
235
288
  else {
236
- this._map.addSource(sourceId, { type: 'geojson', data: geojson, promoteId: 'id' });
289
+ // No `promoteId` -- maplibre-gl falls back to its own default id
290
+ // resolution (the standard top-level GeoJSON `Feature.id`, when
291
+ // present). A hardcoded `promoteId: 'id'` here would instead require
292
+ // every feature to *also* duplicate its id inside `properties.id`,
293
+ // silently discarding the real top-level `id` otherwise and breaking
294
+ // `feature.id` on `lyra-map-click` for the common case. Nothing in this
295
+ // component actually needs feature-state promotion today; if that's
296
+ // added later it should be driven by an explicit, documented
297
+ // `ChoroplethLayer` option (e.g. `idField`), not a silent default.
298
+ this._map.addSource(sourceId, { type: 'geojson', data: geojson });
237
299
  }
238
300
  this._appliedChoroplethSourceId = sourceId;
239
301
  // An `interpolate` expression needs at least one [value, color] stop pair
@@ -408,6 +470,9 @@ __decorate([
408
470
  __decorate([
409
471
  state()
410
472
  ], LyraMap.prototype, "loading", void 0);
473
+ __decorate([
474
+ state()
475
+ ], LyraMap.prototype, "visible", void 0);
411
476
  __decorate([
412
477
  query('[part="container"]')
413
478
  ], LyraMap.prototype, "containerEl", void 0);
@@ -30,5 +30,29 @@ export declare function loadMarkdownAndSanitizer(importMarked?: () => Promise<ty
30
30
  * independently). Cached the same way `chart-loader.ts`/`map-loader.ts`
31
31
  * cache their promise, so every `<lyra-markdown>` instance on a page shares
32
32
  * one load.
33
+ *
34
+ * The dynamic `import()` inside `loadMarkdownAndSanitizer()` is always
35
+ * asynchronous — even when both peers are already installed and resolve
36
+ * without error — so every `<lyra-markdown>` that calls this from
37
+ * `connectedCallback()` paints its plain-text fallback (`data-fallback` on
38
+ * the `content` part) for at least one microtask before the real rendered
39
+ * output replaces it. That window is unconditional, not just a failure
40
+ * path: it ends only once this promise settles. A consumer that wants to
41
+ * skip it can set `eager-load` on `<lyra-markdown>`, which calls
42
+ * `getMarkdownDepsIfLoaded()` below instead of awaiting this function —
43
+ * see that property's doc for what "already warm" means in practice.
33
44
  */
34
45
  export declare function loadMarkdownDeps(): Promise<MarkdownDeps>;
46
+ /**
47
+ * Synchronous companion to `loadMarkdownDeps()`: returns the same
48
+ * module-level cached `MarkdownDeps` if some earlier call to
49
+ * `loadMarkdownDeps()` — from any `<lyra-markdown>` instance on the page, or
50
+ * a consumer priming it directly at startup — has already settled by the
51
+ * time this is called, or `undefined` if the cache isn't warm yet (nothing
52
+ * has called `loadMarkdownDeps()` before, or it's still in flight). Used by
53
+ * `eager-load` to skip the dynamic `import()`'s async hop in the common case
54
+ * where the peers are already loaded; it cannot make the *very first*
55
+ * `<lyra-markdown>` on a page synchronous, since that first call is what
56
+ * populates the cache in the first place.
57
+ */
58
+ export declare function getMarkdownDepsIfLoaded(): MarkdownDeps | undefined;
@@ -1,4 +1,9 @@
1
1
  let deps;
2
+ // Populated the instant `deps` settles (inside `loadMarkdownDeps()` below) so
3
+ // `getMarkdownDepsIfLoaded()` can hand one back synchronously -- the whole
4
+ // point of that function, since `deps` itself is a Promise and awaiting it
5
+ // always costs at least one microtask, even once already resolved.
6
+ let resolvedDeps;
2
7
  /**
3
8
  * Independently loads the optional peer dependencies `marked` (Markdown
4
9
  * parsing) and `dompurify` (HTML sanitizing), mirroring `chart-loader.ts`'s
@@ -34,10 +39,39 @@ export async function loadMarkdownAndSanitizer(importMarked = () => import('mark
34
39
  * independently). Cached the same way `chart-loader.ts`/`map-loader.ts`
35
40
  * cache their promise, so every `<lyra-markdown>` instance on a page shares
36
41
  * one load.
42
+ *
43
+ * The dynamic `import()` inside `loadMarkdownAndSanitizer()` is always
44
+ * asynchronous — even when both peers are already installed and resolve
45
+ * without error — so every `<lyra-markdown>` that calls this from
46
+ * `connectedCallback()` paints its plain-text fallback (`data-fallback` on
47
+ * the `content` part) for at least one microtask before the real rendered
48
+ * output replaces it. That window is unconditional, not just a failure
49
+ * path: it ends only once this promise settles. A consumer that wants to
50
+ * skip it can set `eager-load` on `<lyra-markdown>`, which calls
51
+ * `getMarkdownDepsIfLoaded()` below instead of awaiting this function —
52
+ * see that property's doc for what "already warm" means in practice.
37
53
  */
38
54
  export function loadMarkdownDeps() {
39
55
  if (!deps) {
40
- deps = loadMarkdownAndSanitizer();
56
+ deps = loadMarkdownAndSanitizer().then((resolved) => {
57
+ resolvedDeps = resolved;
58
+ return resolved;
59
+ });
41
60
  }
42
61
  return deps;
43
62
  }
63
+ /**
64
+ * Synchronous companion to `loadMarkdownDeps()`: returns the same
65
+ * module-level cached `MarkdownDeps` if some earlier call to
66
+ * `loadMarkdownDeps()` — from any `<lyra-markdown>` instance on the page, or
67
+ * a consumer priming it directly at startup — has already settled by the
68
+ * time this is called, or `undefined` if the cache isn't warm yet (nothing
69
+ * has called `loadMarkdownDeps()` before, or it's still in flight). Used by
70
+ * `eager-load` to skip the dynamic `import()`'s async hop in the common case
71
+ * where the peers are already loaded; it cannot make the *very first*
72
+ * `<lyra-markdown>` on a page synchronous, since that first call is what
73
+ * populates the cache in the first place.
74
+ */
75
+ export function getMarkdownDepsIfLoaded() {
76
+ return resolvedDeps;
77
+ }
@@ -19,10 +19,21 @@ import { LyraElement } from '../../internal/lyra-element.js';
19
19
  * regardless of whether `dompurify` is installed — the consumer opted out
20
20
  * of sanitization, so `dompurify`'s absence is irrelevant to that path.
21
21
  *
22
- * `heading`/`code`/`blockquote`/`table`/`link` tokens are rendered through a
23
- * `marked` renderer override that injects `part="..."` attributes directly
24
- * into the produced HTML — a single pass, not a second DOM walk after
25
- * insertion.
22
+ * That same plain-text fallback rendering (`data-fallback` on the `content`
23
+ * part) is also, unconditionally and by default, a brief *transient* state on
24
+ * every connect, not just a failure path: `connectedCallback()`'s dynamic
25
+ * `import()` of `marked`/`dompurify` (see `markdown-loader.ts`) is
26
+ * asynchronous, so the very first paint of any `<lyra-markdown>` on a page
27
+ * shows plain text for at least one microtask — even when both peers are
28
+ * already installed and load without error — until that import resolves and
29
+ * a second render replaces it with the real Markdown output. Set
30
+ * `eager-load` to skip that window once the shared dependency cache is
31
+ * already warm; see that property's doc for exactly what "warm" requires.
32
+ *
33
+ * `heading`/`code`/`blockquote`/`table`/`link`/`image` tokens are rendered
34
+ * through a `marked` renderer override that injects `part="..."` attributes
35
+ * directly into the produced HTML — a single pass, not a second DOM walk
36
+ * after insertion.
26
37
  *
27
38
  * @customElement lyra-markdown
28
39
  * @event lyra-link-click - Fired (and the click prevented) when a rendered
@@ -33,11 +44,13 @@ import { LyraElement } from '../../internal/lyra-element.js';
33
44
  * text. `detail: { error: unknown }`.
34
45
  * @csspart content - The wrapper around the rendered (or plain-text
35
46
  * fallback) output.
36
- * @csspart heading - Every rendered `<h1>`–`<h6>`.
47
+ * @csspart heading - Every rendered `<h1>`–`<h6>` (shifted by
48
+ * `heading-offset`).
37
49
  * @csspart code-block - Every rendered fenced/indented `<pre>`.
38
50
  * @csspart link - Every rendered `<a>`.
39
51
  * @csspart table - Every rendered `<table>`.
40
52
  * @csspart blockquote - Every rendered `<blockquote>`.
53
+ * @csspart img - Every rendered `<img>`.
41
54
  */
42
55
  export declare class LyraMarkdown extends LyraElement {
43
56
  static styles: import("lit").CSSResultGroup[];
@@ -49,14 +62,39 @@ export declare class LyraMarkdown extends LyraElement {
49
62
  sanitize: boolean;
50
63
  /** Enable GitHub-flavored Markdown (tables, strikethrough, autolinks, task lists). */
51
64
  gfm: boolean;
52
- /** `target` applied to every rendered `<a>`; `rel="noopener noreferrer"` is
53
- * always added alongside it regardless of this value. */
54
- linkTarget: string;
65
+ /** `target` applied to every rendered `<a>`, with `rel="noopener
66
+ * noreferrer"` always added alongside it whenever a `target` is emitted.
67
+ * `'_blank'` (the default) preserves today's exact output. Set to `null`
68
+ * (or the empty string, e.g. via the `link-target=""` attribute) to omit
69
+ * `target`/`rel` entirely, so rendered links open in the same tab. */
70
+ linkTarget: string | null;
55
71
  /** When set, a rendered link whose `href` starts with this prefix is
56
72
  * treated as internal — its click is intercepted and reported via
57
73
  * `lyra-link-click` instead of navigating. Empty (the default) means
58
74
  * every link is treated as external. */
59
75
  internalLinkPrefix: string;
76
+ /** Added to every rendered heading's source `token.depth` before emitting
77
+ * `<h${depth}>` — e.g. `heading-offset="2"` renders a source `#` as
78
+ * `<h3>` and a source `##` as `<h4>`. The result is clamped to `[1, 6]`
79
+ * (a source `######` with a positive offset stays at `<h6>` rather than
80
+ * overflowing past the HTML heading levels; the floor at `1` is
81
+ * defensive, since this property is meant to be additive-only). `0`
82
+ * (the default) preserves today's exact `<h${token.depth}>` output. */
83
+ headingOffset: number;
84
+ /** When `true`, `connectedCallback()` skips awaiting `loadMarkdownDeps()`'s
85
+ * dynamic `import()` if the shared `marked`/`dompurify` module cache (see
86
+ * `markdown-loader.ts`'s `getMarkdownDepsIfLoaded()`) has *already*
87
+ * resolved — e.g. because an earlier `<lyra-markdown>` instance on the
88
+ * page already finished loading, or the consumer primed the cache
89
+ * directly by calling `loadMarkdownDeps()` themselves at startup — and
90
+ * renders synchronously instead. When the cache isn't warm yet (most
91
+ * notably: the very first `<lyra-markdown>` ever connected on a page,
92
+ * since nothing has called `loadMarkdownDeps()` before it), this still
93
+ * falls back to the normal async path — a dynamic `import()` can't be
94
+ * made synchronous, so this is a fast path for the common "already warm"
95
+ * case, not a hard guarantee. `false` (the default) is byte-identical to
96
+ * today: always the async `import()`, fallback-text window included. */
97
+ eagerLoad: boolean;
60
98
  /** Forward-compatible hint for a dedicated streaming renderer expected to
61
99
  * build on this component later (coalescing partial tokens as they
62
100
  * arrive) — setting it has no rendering effect yet. Reflects so a
@@ -9,7 +9,7 @@ import { property, state } from 'lit/decorators.js';
9
9
  import { unsafeHTML } from 'lit/directives/unsafe-html.js';
10
10
  import { LyraElement } from '../../internal/lyra-element.js';
11
11
  import { defineElement } from '../../internal/prefix.js';
12
- import { loadMarkdownDeps } from './markdown-loader.js';
12
+ import { loadMarkdownDeps, getMarkdownDepsIfLoaded } from './markdown-loader.js';
13
13
  import { styles } from './markdown.styles.js';
14
14
  const HTML_ESCAPES = {
15
15
  '&': '&amp;',
@@ -22,22 +22,22 @@ function escapeHtml(text) {
22
22
  return text.replace(/[&<>"']/g, (ch) => HTML_ESCAPES[ch] ?? ch);
23
23
  }
24
24
  /**
25
- * Mirrors marked's own default `link()` renderer's defensive href guard: a
26
- * malformed percent-escape or lone UTF-16 surrogate in the raw href throws
27
- * inside `encodeURI`, and marked's default renderer responds by dropping the
28
- * anchor (rendering the link text alone) rather than emitting a broken
29
- * `href`. `encodeURI`'s *return value* is discarded on purpose, not applied —
30
- * calling it a second time on an already-percent-encoded URL (the common
31
- * case for a real markdown link) would double-encode every existing `%XX`
32
- * escape.
25
+ * Mirrors marked's own default `link()` renderer's `cleanUrl()`: a malformed
26
+ * percent-escape or lone UTF-16 surrogate in the raw href throws inside
27
+ * `encodeURI`, and marked's default renderer responds by dropping the anchor
28
+ * (rendering the link text alone) rather than emitting a broken `href` —
29
+ * returning `null` here lets the caller do the same. The
30
+ * `.replace(/%25/g, '%')` compensates for `encodeURI` re-escaping the `%` of
31
+ * an href that was already percent-encoded (the common case for a real
32
+ * markdown link) — without it, every existing `%XX` escape would become
33
+ * `%25XX`, double-encoding it.
33
34
  */
34
- function isValidHref(href) {
35
+ function cleanHref(href) {
35
36
  try {
36
- encodeURI(href);
37
- return true;
37
+ return encodeURI(href).replace(/%25/g, '%');
38
38
  }
39
39
  catch {
40
- return false;
40
+ return null;
41
41
  }
42
42
  }
43
43
  /**
@@ -59,10 +59,21 @@ function isValidHref(href) {
59
59
  * regardless of whether `dompurify` is installed — the consumer opted out
60
60
  * of sanitization, so `dompurify`'s absence is irrelevant to that path.
61
61
  *
62
- * `heading`/`code`/`blockquote`/`table`/`link` tokens are rendered through a
63
- * `marked` renderer override that injects `part="..."` attributes directly
64
- * into the produced HTML — a single pass, not a second DOM walk after
65
- * insertion.
62
+ * That same plain-text fallback rendering (`data-fallback` on the `content`
63
+ * part) is also, unconditionally and by default, a brief *transient* state on
64
+ * every connect, not just a failure path: `connectedCallback()`'s dynamic
65
+ * `import()` of `marked`/`dompurify` (see `markdown-loader.ts`) is
66
+ * asynchronous, so the very first paint of any `<lyra-markdown>` on a page
67
+ * shows plain text for at least one microtask — even when both peers are
68
+ * already installed and load without error — until that import resolves and
69
+ * a second render replaces it with the real Markdown output. Set
70
+ * `eager-load` to skip that window once the shared dependency cache is
71
+ * already warm; see that property's doc for exactly what "warm" requires.
72
+ *
73
+ * `heading`/`code`/`blockquote`/`table`/`link`/`image` tokens are rendered
74
+ * through a `marked` renderer override that injects `part="..."` attributes
75
+ * directly into the produced HTML — a single pass, not a second DOM walk
76
+ * after insertion.
66
77
  *
67
78
  * @customElement lyra-markdown
68
79
  * @event lyra-link-click - Fired (and the click prevented) when a rendered
@@ -73,11 +84,13 @@ function isValidHref(href) {
73
84
  * text. `detail: { error: unknown }`.
74
85
  * @csspart content - The wrapper around the rendered (or plain-text
75
86
  * fallback) output.
76
- * @csspart heading - Every rendered `<h1>`–`<h6>`.
87
+ * @csspart heading - Every rendered `<h1>`–`<h6>` (shifted by
88
+ * `heading-offset`).
77
89
  * @csspart code-block - Every rendered fenced/indented `<pre>`.
78
90
  * @csspart link - Every rendered `<a>`.
79
91
  * @csspart table - Every rendered `<table>`.
80
92
  * @csspart blockquote - Every rendered `<blockquote>`.
93
+ * @csspart img - Every rendered `<img>`.
81
94
  */
82
95
  export class LyraMarkdown extends LyraElement {
83
96
  constructor() {
@@ -90,14 +103,39 @@ export class LyraMarkdown extends LyraElement {
90
103
  this.sanitize = true;
91
104
  /** Enable GitHub-flavored Markdown (tables, strikethrough, autolinks, task lists). */
92
105
  this.gfm = true;
93
- /** `target` applied to every rendered `<a>`; `rel="noopener noreferrer"` is
94
- * always added alongside it regardless of this value. */
106
+ /** `target` applied to every rendered `<a>`, with `rel="noopener
107
+ * noreferrer"` always added alongside it whenever a `target` is emitted.
108
+ * `'_blank'` (the default) preserves today's exact output. Set to `null`
109
+ * (or the empty string, e.g. via the `link-target=""` attribute) to omit
110
+ * `target`/`rel` entirely, so rendered links open in the same tab. */
95
111
  this.linkTarget = '_blank';
96
112
  /** When set, a rendered link whose `href` starts with this prefix is
97
113
  * treated as internal — its click is intercepted and reported via
98
114
  * `lyra-link-click` instead of navigating. Empty (the default) means
99
115
  * every link is treated as external. */
100
116
  this.internalLinkPrefix = '';
117
+ /** Added to every rendered heading's source `token.depth` before emitting
118
+ * `<h${depth}>` — e.g. `heading-offset="2"` renders a source `#` as
119
+ * `<h3>` and a source `##` as `<h4>`. The result is clamped to `[1, 6]`
120
+ * (a source `######` with a positive offset stays at `<h6>` rather than
121
+ * overflowing past the HTML heading levels; the floor at `1` is
122
+ * defensive, since this property is meant to be additive-only). `0`
123
+ * (the default) preserves today's exact `<h${token.depth}>` output. */
124
+ this.headingOffset = 0;
125
+ /** When `true`, `connectedCallback()` skips awaiting `loadMarkdownDeps()`'s
126
+ * dynamic `import()` if the shared `marked`/`dompurify` module cache (see
127
+ * `markdown-loader.ts`'s `getMarkdownDepsIfLoaded()`) has *already*
128
+ * resolved — e.g. because an earlier `<lyra-markdown>` instance on the
129
+ * page already finished loading, or the consumer primed the cache
130
+ * directly by calling `loadMarkdownDeps()` themselves at startup — and
131
+ * renders synchronously instead. When the cache isn't warm yet (most
132
+ * notably: the very first `<lyra-markdown>` ever connected on a page,
133
+ * since nothing has called `loadMarkdownDeps()` before it), this still
134
+ * falls back to the normal async path — a dynamic `import()` can't be
135
+ * made synchronous, so this is a fast path for the common "already warm"
136
+ * case, not a hard guarantee. `false` (the default) is byte-identical to
137
+ * today: always the async `import()`, fallback-text window included. */
138
+ this.eagerLoad = false;
101
139
  /** Forward-compatible hint for a dedicated streaming renderer expected to
102
140
  * build on this component later (coalescing partial tokens as they
103
141
  * arrive) — setting it has no rendering effect yet. Reflects so a
@@ -133,7 +171,29 @@ export class LyraMarkdown extends LyraElement {
133
171
  static { this.styles = [LyraElement.styles, styles]; }
134
172
  connectedCallback() {
135
173
  super.connectedCallback();
174
+ if (this.eagerLoad) {
175
+ // Only takes this synchronous path when the shared module cache has
176
+ // *already* settled (see getMarkdownDepsIfLoaded()'s doc) -- otherwise
177
+ // falls through to the same async path as the default below, since a
178
+ // dynamic import() can't be made synchronous from scratch.
179
+ const alreadyLoaded = getMarkdownDepsIfLoaded();
180
+ if (alreadyLoaded) {
181
+ this.deps = alreadyLoaded;
182
+ this.renderMarkdown();
183
+ return;
184
+ }
185
+ }
136
186
  void loadMarkdownDeps().then((resolved) => {
187
+ // The (module-cached, page-lifetime) loadMarkdownDeps() promise can
188
+ // resolve after this instance was removed from the DOM -- e.g. an
189
+ // <lyra-markdown> inside a conditionally-rendered chat message or a
190
+ // virtualized list. Without this guard, a detached instance would
191
+ // still have its `deps` set and renderMarkdown() called, mutating the
192
+ // @state() renderedHtml property and scheduling a Lit update no one
193
+ // will ever see. Mirrors chart.ts's own connectedCallback() guard for
194
+ // the identical race.
195
+ if (!this.isConnected)
196
+ return;
137
197
  this.deps = resolved;
138
198
  this.renderMarkdown();
139
199
  });
@@ -149,7 +209,11 @@ export class LyraMarkdown extends LyraElement {
149
209
  // whatever property values are current at that time.
150
210
  return;
151
211
  }
152
- if (changed.has('content') || changed.has('sanitize') || changed.has('gfm') || changed.has('linkTarget')) {
212
+ if (changed.has('content') ||
213
+ changed.has('sanitize') ||
214
+ changed.has('gfm') ||
215
+ changed.has('linkTarget') ||
216
+ changed.has('headingOffset')) {
153
217
  this.renderMarkdown();
154
218
  }
155
219
  }
@@ -203,16 +267,27 @@ export class LyraMarkdown extends LyraElement {
203
267
  this.emit('lyra-render-error', { error });
204
268
  }
205
269
  parseMarkdown(marked) {
206
- const linkTarget = this.linkTarget || '_blank';
270
+ // Falsy (`null` or `''`) means the consumer explicitly opted out of
271
+ // target="..."/rel="..." on rendered links -- see the linkTarget doc.
272
+ // The default '_blank' is already truthy, so this preserves today's
273
+ // exact output when the property is left unset.
274
+ const linkTarget = this.linkTarget;
275
+ const headingOffset = this.headingOffset;
207
276
  const instance = new marked.Marked();
208
277
  // A fresh renderer per parse (rather than a shared/cached one) so it
209
- // always closes over the *current* `linkTarget` — this property can
210
- // change between renders, and marked's `.use()` otherwise persists
211
- // whatever renderer it was given for the lifetime of the instance.
278
+ // always closes over the *current* `linkTarget`/`headingOffset` — these
279
+ // properties can change between renders, and marked's `.use()` otherwise
280
+ // persists whatever renderer it was given for the lifetime of the
281
+ // instance.
212
282
  instance.use({
213
283
  renderer: {
214
284
  heading(token) {
215
- return `<h${token.depth} part="heading">${this.parser.parseInline(token.tokens)}</h${token.depth}>\n`;
285
+ // Clamped to [1, 6]: a positive offset can never push a heading
286
+ // past <h6> (there is no <h7>), and the floor at 1 is defensive
287
+ // since headingOffset is meant to be additive-only (0 is the only
288
+ // documented non-positive value).
289
+ const depth = Math.min(6, Math.max(1, token.depth + headingOffset));
290
+ return `<h${depth} part="heading">${this.parser.parseInline(token.tokens)}</h${depth}>\n`;
216
291
  },
217
292
  code(token) {
218
293
  const lang = (token.lang ?? '').trim().split(/\s+/)[0] ?? '';
@@ -225,9 +300,17 @@ export class LyraMarkdown extends LyraElement {
225
300
  return `<blockquote part="blockquote">\n${this.parser.parse(token.tokens)}</blockquote>\n`;
226
301
  },
227
302
  table(token) {
303
+ // Built directly here (rather than delegating to the inherited
304
+ // tablecell()) so a scope="col" can be added -- marked's own
305
+ // default tablecell() never emits it, and without it a screen
306
+ // reader can't reliably associate a data cell with its column
307
+ // header beyond the simplest table.
228
308
  let headerRow = '';
229
- for (const cell of token.header)
230
- headerRow += this.tablecell(cell);
309
+ for (const cell of token.header) {
310
+ const text = this.parser.parseInline(cell.tokens);
311
+ const alignAttr = cell.align ? ` align="${cell.align}"` : '';
312
+ headerRow += `<th scope="col"${alignAttr}>${text}</th>`;
313
+ }
231
314
  let bodyRows = '';
232
315
  for (const row of token.rows) {
233
316
  let rowHtml = '';
@@ -241,11 +324,27 @@ export class LyraMarkdown extends LyraElement {
241
324
  },
242
325
  link(token) {
243
326
  const text = this.parser.parseInline(token.tokens);
244
- if (!isValidHref(token.href))
327
+ const href = cleanHref(token.href);
328
+ if (href === null)
245
329
  return text;
246
330
  const titleAttr = token.title ? ` title="${escapeHtml(token.title)}"` : '';
247
- return (`<a part="link" href="${escapeHtml(token.href)}"${titleAttr} ` +
248
- `target="${escapeHtml(linkTarget)}" rel="noopener noreferrer">${text}</a>`);
331
+ const targetAttr = linkTarget
332
+ ? ` target="${escapeHtml(linkTarget)}" rel="noopener noreferrer"`
333
+ : '';
334
+ return `<a part="link" href="${escapeHtml(href)}"${titleAttr}${targetAttr}>${text}</a>`;
335
+ },
336
+ image(token) {
337
+ // Mirrors marked's own default image() renderer (alt text
338
+ // re-rendered through the plain textRenderer so nested emphasis/
339
+ // strong/etc. inside the alt collapses to plain text, href run
340
+ // through the same cleanHref() the link() override above uses)
341
+ // with a part="img" added.
342
+ const altText = this.parser.parseInline(token.tokens, this.parser.textRenderer);
343
+ const href = cleanHref(token.href);
344
+ if (href === null)
345
+ return escapeHtml(altText);
346
+ const titleAttr = token.title ? ` title="${escapeHtml(token.title)}"` : '';
347
+ return `<img part="img" src="${escapeHtml(href)}" alt="${escapeHtml(altText)}"${titleAttr}>`;
249
348
  },
250
349
  },
251
350
  });
@@ -275,6 +374,12 @@ __decorate([
275
374
  __decorate([
276
375
  property({ attribute: 'internal-link-prefix' })
277
376
  ], LyraMarkdown.prototype, "internalLinkPrefix", void 0);
377
+ __decorate([
378
+ property({ type: Number, attribute: 'heading-offset' })
379
+ ], LyraMarkdown.prototype, "headingOffset", void 0);
380
+ __decorate([
381
+ property({ type: Boolean, attribute: 'eager-load' })
382
+ ], LyraMarkdown.prototype, "eagerLoad", void 0);
278
383
  __decorate([
279
384
  property({ type: Boolean, reflect: true })
280
385
  ], LyraMarkdown.prototype, "streaming", void 0);
@@ -37,7 +37,7 @@ export const styles = css `
37
37
  font-family: var(--lyra-markdown-font-mono);
38
38
  font-size: 0.875em;
39
39
  background: var(--lyra-color-brand-quiet);
40
- border-radius: 0.1875rem;
40
+ border-radius: calc(var(--lyra-radius) * 0.5);
41
41
  padding: 0.125rem 0.3125rem;
42
42
  }
43
43
  [part='code-block'] {
@@ -57,6 +57,12 @@ export const styles = css `
57
57
  color: var(--lyra-color-brand);
58
58
  text-underline-offset: 0.125rem;
59
59
  }
60
+ /* Keeps an oversized source image from overflowing the content wrapper --
61
+ matches the overflow-wrap: break-word guard on [part='content'] above,
62
+ which only covers text, not replaced elements like <img>. */
63
+ [part='img'] {
64
+ max-width: 100%;
65
+ }
60
66
  [part='blockquote'] {
61
67
  margin-block: 0 var(--lyra-space-s);
62
68
  margin-inline: 0;