@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.
- package/README.md +87 -14
- package/custom-elements.json +11555 -5539
- package/dist/components/app-rail/app-rail-item.d.ts +30 -0
- package/dist/components/app-rail/app-rail-item.js +68 -0
- package/dist/components/app-rail/app-rail-item.styles.d.ts +1 -0
- package/dist/components/app-rail/app-rail-item.styles.js +65 -0
- package/dist/components/app-rail/app-rail.d.ts +21 -24
- package/dist/components/app-rail/app-rail.js +68 -132
- package/dist/components/app-rail/app-rail.styles.js +5 -2
- package/dist/components/attachment-chip/attachment-chip.d.ts +38 -11
- package/dist/components/attachment-chip/attachment-chip.js +73 -29
- package/dist/components/attachment-trigger/attachment-trigger.d.ts +13 -0
- package/dist/components/attachment-trigger/attachment-trigger.js +13 -1
- package/dist/components/attachment-trigger/attachment-trigger.styles.js +28 -6
- package/dist/components/chart/bar-chart.js +2 -18
- package/dist/components/chart/box-plot.d.ts +25 -1
- package/dist/components/chart/box-plot.js +136 -6
- package/dist/components/chart/box-plot.styles.js +13 -0
- package/dist/components/chart/bubble-chart.js +2 -18
- package/dist/components/chart/chart.d.ts +54 -10
- package/dist/components/chart/chart.js +182 -39
- package/dist/components/chart/doughnut-chart.js +2 -18
- package/dist/components/chart/histogram-bin.d.ts +5 -2
- package/dist/components/chart/histogram-bin.js +12 -4
- package/dist/components/chart/histogram.js +8 -16
- package/dist/components/chart/line-chart.js +2 -18
- package/dist/components/chart/lite-chart.d.ts +80 -3
- package/dist/components/chart/lite-chart.js +250 -53
- package/dist/components/chart/pie-chart.js +2 -18
- package/dist/components/chart/polar-area-chart.js +2 -18
- package/dist/components/chart/radar-chart.js +2 -18
- package/dist/components/chart/scatter-chart.js +2 -18
- package/dist/components/chat-composer/chat-composer.d.ts +42 -1
- package/dist/components/chat-composer/chat-composer.js +84 -3
- package/dist/components/chat-composer/chat-composer.styles.js +11 -1
- package/dist/components/chat-message/chat-message.d.ts +32 -4
- package/dist/components/chat-message/chat-message.js +57 -17
- package/dist/components/checkbox/checkbox.d.ts +66 -8
- package/dist/components/checkbox/checkbox.js +173 -50
- package/dist/components/checkbox/checkbox.styles.js +9 -2
- package/dist/components/chip/chip-group.js +8 -2
- package/dist/components/chip/chip.js +9 -2
- package/dist/components/citation-badge/citation-badge.js +21 -3
- package/dist/components/code-block/code-block.d.ts +38 -0
- package/dist/components/code-block/code-block.js +72 -7
- package/dist/components/code-block/code-loader.d.ts +44 -1
- package/dist/components/code-block/code-loader.js +63 -0
- package/dist/components/combobox/combobox.d.ts +46 -5
- package/dist/components/combobox/combobox.js +175 -35
- package/dist/components/combobox/combobox.styles.js +3 -3
- package/dist/components/context-meter/context-meter.js +17 -8
- package/dist/components/context-meter/context-meter.styles.js +0 -6
- package/dist/components/conversation-item/conversation-item.d.ts +13 -16
- package/dist/components/conversation-item/conversation-item.js +29 -20
- package/dist/components/date-picker/calendar-core.d.ts +24 -0
- package/dist/components/date-picker/calendar-core.js +51 -4
- package/dist/components/date-picker/date-input.d.ts +77 -10
- package/dist/components/date-picker/date-input.js +278 -73
- package/dist/components/date-picker/date-picker.d.ts +28 -4
- package/dist/components/date-picker/date-picker.js +95 -37
- package/dist/components/dialog/confirm.js +3 -6
- package/dist/components/dialog/dialog.d.ts +75 -31
- package/dist/components/dialog/dialog.js +147 -131
- package/dist/components/dialog/dialog.styles.js +46 -2
- package/dist/components/dock-panel/dock-panel.d.ts +8 -0
- package/dist/components/dock-panel/dock-panel.js +15 -1
- package/dist/components/dock-panel/dock-panel.styles.js +15 -1
- package/dist/components/document-preview/document-preview.d.ts +15 -7
- package/dist/components/document-preview/document-preview.js +29 -13
- package/dist/components/empty/empty.d.ts +1 -2
- package/dist/components/empty/empty.js +18 -12
- package/dist/components/empty/empty.styles.js +0 -3
- package/dist/components/export-button/export-button.d.ts +12 -0
- package/dist/components/export-button/export-button.js +33 -5
- package/dist/components/export-button/export-button.styles.js +5 -1
- package/dist/components/file-input/accept.js +2 -0
- package/dist/components/file-input/file-input.d.ts +10 -1
- package/dist/components/file-input/file-input.js +36 -5
- package/dist/components/flag/flag.d.ts +1 -1
- package/dist/components/flag/flag.js +15 -1
- package/dist/components/flag/flag.styles.js +1 -1
- package/dist/components/flag/language-map.d.ts +4 -1
- package/dist/components/flag/language-map.js +6 -3
- package/dist/components/gauge/gauge.d.ts +10 -1
- package/dist/components/gauge/gauge.js +28 -9
- package/dist/components/generation-status/generation-status.d.ts +13 -5
- package/dist/components/generation-status/generation-status.js +59 -5
- package/dist/components/graph/graph.d.ts +37 -3
- package/dist/components/graph/graph.js +132 -20
- package/dist/components/heatmap/calendar-grid.d.ts +15 -6
- package/dist/components/heatmap/calendar-grid.js +17 -6
- package/dist/components/heatmap/heatmap-scale.d.ts +5 -1
- package/dist/components/heatmap/heatmap-scale.js +8 -2
- package/dist/components/heatmap/heatmap.d.ts +166 -25
- package/dist/components/heatmap/heatmap.js +372 -89
- package/dist/components/json-viewer/json-viewer.d.ts +22 -5
- package/dist/components/json-viewer/json-viewer.js +115 -37
- package/dist/components/json-viewer/json-viewer.styles.js +2 -1
- package/dist/components/kbd/kbd.js +24 -7
- package/dist/components/live-region/live-region.d.ts +10 -1
- package/dist/components/live-region/live-region.js +42 -6
- package/dist/components/map/map.d.ts +24 -1
- package/dist/components/map/map.js +89 -24
- package/dist/components/markdown/markdown-loader.d.ts +24 -0
- package/dist/components/markdown/markdown-loader.js +35 -1
- package/dist/components/markdown/markdown.d.ts +46 -8
- package/dist/components/markdown/markdown.js +136 -31
- package/dist/components/markdown/markdown.styles.js +7 -1
- package/dist/components/media-card/media-card.d.ts +25 -17
- package/dist/components/media-card/media-card.js +43 -65
- package/dist/components/mention-popover/mention-popover.d.ts +4 -1
- package/dist/components/mention-popover/mention-popover.js +23 -5
- package/dist/components/menu/menu-item.d.ts +32 -3
- package/dist/components/menu/menu-item.js +94 -7
- package/dist/components/menu/menu-item.styles.js +8 -0
- package/dist/components/menu/menu.d.ts +25 -1
- package/dist/components/menu/menu.js +99 -3
- package/dist/components/menu/menu.styles.js +3 -2
- package/dist/components/model-select/model-select.d.ts +48 -3
- package/dist/components/model-select/model-select.js +120 -22
- package/dist/components/model-select/model-select.styles.js +17 -3
- package/dist/components/model-settings-panel/model-settings-panel.d.ts +25 -1
- package/dist/components/model-settings-panel/model-settings-panel.js +65 -1
- package/dist/components/playback/playback.d.ts +6 -2
- package/dist/components/playback/playback.js +72 -29
- package/dist/components/playback/playback.styles.js +6 -1
- package/dist/components/responsive-panel/responsive-panel.d.ts +29 -16
- package/dist/components/responsive-panel/responsive-panel.js +130 -132
- package/dist/components/responsive-panel/responsive-panel.styles.js +1 -1
- package/dist/components/result-card/result-card.d.ts +8 -2
- package/dist/components/result-card/result-card.js +9 -3
- package/dist/components/result-card/result-card.styles.js +0 -1
- package/dist/components/select/select.d.ts +59 -20
- package/dist/components/select/select.js +125 -43
- package/dist/components/skeleton/skeleton.d.ts +3 -0
- package/dist/components/skeleton/skeleton.js +7 -1
- package/dist/components/skeleton/skeleton.styles.js +5 -4
- package/dist/components/slider/slider.d.ts +57 -5
- package/dist/components/slider/slider.js +195 -80
- package/dist/components/source-card/source-card.d.ts +17 -5
- package/dist/components/source-card/source-card.js +55 -8
- package/dist/components/source-card/source-card.styles.js +1 -1
- package/dist/components/source-list/source-list.js +9 -1
- package/dist/components/sparkline/sparkline.js +34 -10
- package/dist/components/split/split.d.ts +141 -14
- package/dist/components/split/split.js +357 -52
- package/dist/components/split/split.styles.js +18 -1
- package/dist/components/stat/stat.d.ts +10 -4
- package/dist/components/stat/stat.js +46 -11
- package/dist/components/stream-status/stream-status.d.ts +27 -11
- package/dist/components/stream-status/stream-status.js +95 -13
- package/dist/components/streaming-text/streaming-text.d.ts +9 -3
- package/dist/components/streaming-text/streaming-text.js +25 -10
- package/dist/components/streaming-text/streaming-text.styles.js +3 -2
- package/dist/components/switch/switch.d.ts +52 -7
- package/dist/components/switch/switch.js +111 -40
- package/dist/components/switch/switch.styles.js +1 -1
- package/dist/components/table/table.d.ts +18 -3
- package/dist/components/table/table.js +88 -20
- package/dist/components/tabs/tabs.d.ts +23 -0
- package/dist/components/tabs/tabs.js +76 -12
- package/dist/components/tabs/tabs.styles.js +11 -0
- package/dist/components/thinking-panel/thinking-panel.d.ts +3 -1
- package/dist/components/thinking-panel/thinking-panel.js +34 -9
- package/dist/components/thinking-panel/thinking-panel.styles.js +6 -0
- package/dist/components/time-range/time-range.d.ts +59 -2
- package/dist/components/time-range/time-range.js +163 -67
- package/dist/components/toast/toast-item.d.ts +12 -3
- package/dist/components/toast/toast-item.js +90 -53
- package/dist/components/toast/toast-item.styles.js +59 -28
- package/dist/components/toast/toast.js +17 -5
- package/dist/components/toast/toaster.js +10 -1
- package/dist/components/tool-approval-dialog/tool-approval-dialog.d.ts +11 -10
- package/dist/components/tool-approval-dialog/tool-approval-dialog.js +50 -124
- package/dist/components/tool-approval-dialog/tool-approval-dialog.styles.js +1 -1
- package/dist/components/tool-call-chip/tool-call-chip.d.ts +18 -2
- package/dist/components/tool-call-chip/tool-call-chip.js +86 -25
- package/dist/components/tool-param-form/tool-param-form.d.ts +89 -28
- package/dist/components/tool-param-form/tool-param-form.js +350 -106
- package/dist/components/tool-result-dialog/tool-result-dialog.d.ts +27 -7
- package/dist/components/tool-result-dialog/tool-result-dialog.js +80 -115
- package/dist/components/tool-result-dialog/tool-result-dialog.styles.js +1 -1
- package/dist/components/tool-result-view/registry.d.ts +0 -8
- package/dist/components/tool-result-view/registry.js +7 -2
- package/dist/components/tool-result-view/tool-result-view.d.ts +6 -3
- package/dist/components/tool-result-view/tool-result-view.js +18 -4
- package/dist/components/tool-select-dialog/tool-select-dialog.d.ts +11 -11
- package/dist/components/tool-select-dialog/tool-select-dialog.js +44 -133
- package/dist/components/tool-select-dialog/tool-select-dialog.styles.js +13 -4
- package/dist/components/tree/tree-node.d.ts +4 -1
- package/dist/components/tree/tree-node.js +4 -1
- package/dist/components/tree/tree.d.ts +17 -8
- package/dist/components/tree/tree.js +21 -20
- package/dist/components/typing-indicator/typing-indicator.d.ts +12 -2
- package/dist/components/typing-indicator/typing-indicator.js +16 -4
- package/dist/components/typing-indicator/typing-indicator.styles.js +2 -2
- package/dist/components/virtual-list/virtual-list.d.ts +56 -19
- package/dist/components/virtual-list/virtual-list.js +184 -38
- package/dist/components/virtual-list/virtual-list.styles.js +12 -0
- package/dist/components/widget/widget.d.ts +5 -4
- package/dist/components/widget/widget.js +54 -120
- package/dist/components/widget/widget.styles.js +8 -2
- package/dist/components/word-cloud/word-cloud-layout.d.ts +7 -0
- package/dist/components/word-cloud/word-cloud-layout.js +20 -6
- package/dist/components/word-cloud/word-cloud.d.ts +22 -2
- package/dist/components/word-cloud/word-cloud.js +45 -6
- package/dist/components/word-cloud/word-cloud.styles.js +13 -1
- package/dist/internal/anchored-validity.d.ts +1 -0
- package/dist/internal/anchored-validity.js +86 -0
- package/dist/internal/form-associated.d.ts +7 -0
- package/dist/internal/form-associated.js +43 -4
- package/dist/internal/numbers.d.ts +12 -0
- package/dist/internal/numbers.js +26 -0
- package/dist/internal/overlay-manager.d.ts +51 -0
- package/dist/internal/overlay-manager.js +523 -0
- package/dist/internal/positioner.js +27 -3
- package/dist/internal/safe-url.d.ts +12 -0
- package/dist/internal/safe-url.js +41 -0
- package/dist/internal/tokens.styles.js +17 -11
- package/dist/lyra.d.ts +5 -2
- package/dist/lyra.js +3 -1
- package/llms-full.txt +970 -426
- package/llms.txt +5 -4
- 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
|
|
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.
|
|
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
|
-
|
|
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
|
-
* `
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
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
|
|
53
|
-
* always added alongside it
|
|
54
|
-
|
|
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
|
'&': '&',
|
|
@@ -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
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
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
|
|
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
|
|
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
|
-
* `
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
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
|
|
94
|
-
* always added alongside it
|
|
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') ||
|
|
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
|
-
|
|
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` —
|
|
210
|
-
// change between renders, and marked's `.use()` otherwise
|
|
211
|
-
// whatever renderer it was given for the lifetime of the
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
248
|
-
`target="${escapeHtml(linkTarget)}" rel="noopener noreferrer"
|
|
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.
|
|
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;
|