@keboola/design 13.2.0 → 14.0.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/AGENTS.md +5 -1
- package/CHANGELOG.md +161 -0
- package/COMPONENT-API.md +52 -42
- package/dist/{AIKit-C5FbKjwa.js → AIKit-BRSZ3nY7.js} +2216 -302
- package/dist/AIKit-BRSZ3nY7.js.map +1 -0
- package/dist/{Alert-DuAGeYyM.js → Alert-CZ0dDsuk.js} +2 -2
- package/dist/{Alert-DuAGeYyM.js.map → Alert-CZ0dDsuk.js.map} +1 -1
- package/dist/{BrandEditor-Dq168RJc.js → BrandEditor-CN2HJ_12.js} +5 -5
- package/dist/{BrandEditor-Dq168RJc.js.map → BrandEditor-CN2HJ_12.js.map} +1 -1
- package/dist/{Breadcrumb-CUyYu5Bi.js → Breadcrumb-BnIlsoIH.js} +54 -12
- package/dist/Breadcrumb-BnIlsoIH.js.map +1 -0
- package/dist/{Checkbox-D9rafoKh.js → Checkbox-uFE8XLT_.js} +2 -2
- package/dist/{Checkbox-D9rafoKh.js.map → Checkbox-uFE8XLT_.js.map} +1 -1
- package/dist/{Clipboard-BHWUodAx.js → Clipboard-D-9XOqy4.js} +2 -2
- package/dist/{Clipboard-BHWUodAx.js.map → Clipboard-D-9XOqy4.js.map} +1 -1
- package/dist/{ClipboardButton-Urg-kZbh.js → ClipboardButton-DxvZG_H0.js} +2 -2
- package/dist/{ClipboardButton-Urg-kZbh.js.map → ClipboardButton-DxvZG_H0.js.map} +1 -1
- package/dist/{CodeEditor-Y0iE-SwV.js → CodeEditor-BfRSCKEs.js} +5 -5
- package/dist/{CodeEditor-Y0iE-SwV.js.map → CodeEditor-BfRSCKEs.js.map} +1 -1
- package/dist/{ColorInput-D1nC0X6i.js → ColorInput-CDKH8q_f.js} +2 -2
- package/dist/{ColorInput-D1nC0X6i.js.map → ColorInput-CDKH8q_f.js.map} +1 -1
- package/dist/{ColorModeSwitch-Cxee8jnB.js → ColorModeSwitch-DR6J2U6M.js} +2 -2
- package/dist/{ColorModeSwitch-Cxee8jnB.js.map → ColorModeSwitch-DR6J2U6M.js.map} +1 -1
- package/dist/{Combobox-B1Kjoy5J.js → Combobox-CEesMe2M.js} +240 -38
- package/dist/Combobox-CEesMe2M.js.map +1 -0
- package/dist/{Command-BuOA3V18.js → Command-T3MLRaen.js} +8 -4
- package/dist/Command-T3MLRaen.js.map +1 -0
- package/dist/{CopyField-DsOVtV2Q.js → CopyField-CNbCHw9a.js} +2 -2
- package/dist/{CopyField-DsOVtV2Q.js.map → CopyField-CNbCHw9a.js.map} +1 -1
- package/dist/{DateInput-BlKkequE.js → DateInput-ClK1lCxz.js} +2 -2
- package/dist/{DateInput-BlKkequE.js.map → DateInput-ClK1lCxz.js.map} +1 -1
- package/dist/{DateRangeInput-DOxJHw8V.js → DateRangeInput-DtlAffS1.js} +2 -2
- package/dist/{DateRangeInput-DOxJHw8V.js.map → DateRangeInput-DtlAffS1.js.map} +1 -1
- package/dist/{DropdownMenu-DtRYcKEx.js → DropdownMenu-B1gGvZoh.js} +2 -2
- package/dist/{DropdownMenu-DtRYcKEx.js.map → DropdownMenu-B1gGvZoh.js.map} +1 -1
- package/dist/{FieldItem-Cf1E3rfQ.js → FieldItem-CYvJyjiI.js} +3 -3
- package/dist/{FieldItem-Cf1E3rfQ.js.map → FieldItem-CYvJyjiI.js.map} +1 -1
- package/dist/{Form-CuaAn-lR.js → Form-CD55zJiy.js} +5 -5
- package/dist/{Form-CuaAn-lR.js.map → Form-CD55zJiy.js.map} +1 -1
- package/dist/{FormGroup-D4O96YPm.js → FormGroup-DiYFdEhH.js} +3 -3
- package/dist/{FormGroup-D4O96YPm.js.map → FormGroup-DiYFdEhH.js.map} +1 -1
- package/dist/{HoverCard-Cy6nhg4u.js → HoverCard-BBdPYqEf.js} +2 -2
- package/dist/HoverCard-BBdPYqEf.js.map +1 -0
- package/dist/{InlineEdit-DSMIEUUa.js → InlineEdit-D5RdunZo.js} +3 -3
- package/dist/{InlineEdit-DSMIEUUa.js.map → InlineEdit-D5RdunZo.js.map} +1 -1
- package/dist/{Kbd-Du93F3o4.js → Kbd-3YM7NQO4.js} +2 -2
- package/dist/{Kbd-Du93F3o4.js.map → Kbd-3YM7NQO4.js.map} +1 -1
- package/dist/MermaidDiagram-CeiOkI60.js +199 -0
- package/dist/MermaidDiagram-CeiOkI60.js.map +1 -0
- package/dist/{Modal-DQIauzar.js → Modal-DMZRswk4.js} +4 -3
- package/dist/Modal-DMZRswk4.js.map +1 -0
- package/dist/{MultiActionButtonGroup-xKqiucAN.js → MultiActionButtonGroup-CEv_txvz.js} +2 -2
- package/dist/{MultiActionButtonGroup-xKqiucAN.js.map → MultiActionButtonGroup-CEv_txvz.js.map} +1 -1
- package/dist/{RadioGroup-C2vk5acr.js → RadioGroup-C8EC3fqO.js} +2 -2
- package/dist/{RadioGroup-C2vk5acr.js.map → RadioGroup-C8EC3fqO.js.map} +1 -1
- package/dist/{Search-CVrv3hqf.js → Search-aaUMI4ix.js} +2 -2
- package/dist/{Search-CVrv3hqf.js.map → Search-aaUMI4ix.js.map} +1 -1
- package/dist/{SearchInput-BnU6YHgT.js → SearchInput-Dxi2GK4n.js} +2 -2
- package/dist/{SearchInput-BnU6YHgT.js.map → SearchInput-Dxi2GK4n.js.map} +1 -1
- package/dist/{SectionNav-XlhvXXdF.js → SectionNav-BnHOMLtE.js} +3 -3
- package/dist/{SectionNav-XlhvXXdF.js.map → SectionNav-BnHOMLtE.js.map} +1 -1
- package/dist/Sheet-utw4_rUa.js.map +1 -1
- package/dist/{Sidebar-CKaxOPwY.js → Sidebar-BAmux2Uj.js} +5 -5
- package/dist/{Sidebar-CKaxOPwY.js.map → Sidebar-BAmux2Uj.js.map} +1 -1
- package/dist/{Slider-Rcc2qVct.js → Slider-DbzzSDRV.js} +2 -2
- package/dist/{Slider-Rcc2qVct.js.map → Slider-DbzzSDRV.js.map} +1 -1
- package/dist/{Table-CgSC-fSo.js → Table-CilEvpMq.js} +62 -3
- package/dist/Table-CilEvpMq.js.map +1 -0
- package/dist/{TextInput-B5TDDOty.js → TextInput-K2DSDs5O.js} +2 -2
- package/dist/{TextInput-B5TDDOty.js.map → TextInput-K2DSDs5O.js.map} +1 -1
- package/dist/{ThemeProvider-Dt9b67sW.js → ThemeProvider-C8yaKlM1.js} +2 -2
- package/dist/{ThemeProvider-Dt9b67sW.js.map → ThemeProvider-C8yaKlM1.js.map} +1 -1
- package/dist/ToggleAllTooltip-Dlmq-yqS.js +37 -0
- package/dist/ToggleAllTooltip-Dlmq-yqS.js.map +1 -0
- package/dist/{ZoomableSvg-D8sXKpoV.js → ZoomableSvg-Dh1OstPN.js} +84 -80
- package/dist/ZoomableSvg-Dh1OstPN.js.map +1 -0
- package/dist/editors.js +3 -3
- package/dist/flint.js +10 -45
- package/dist/flint.js.map +1 -1
- package/dist/highlightCode-D354tCXO.js +81 -0
- package/dist/highlightCode-D354tCXO.js.map +1 -0
- package/dist/index.d.ts +792 -346
- package/dist/index.js +35 -35
- package/dist/internal/blocks/AIKit.js +2 -2
- package/dist/internal/blocks/BrandEditor.js +1 -1
- package/dist/internal/blocks/ChipEditor.js +1 -1
- package/dist/internal/blocks/ClipboardButton.js +1 -1
- package/dist/internal/blocks/ColorInput.js +1 -1
- package/dist/internal/blocks/ColorModeSwitch.js +1 -1
- package/dist/internal/blocks/Command.js +1 -1
- package/dist/internal/blocks/CopyField.js +1 -1
- package/dist/internal/blocks/DateInput.js +1 -1
- package/dist/internal/blocks/DateRangeInput.js +1 -1
- package/dist/internal/blocks/DiagramShared/DiagramSkeleton.js.map +1 -1
- package/dist/internal/blocks/ExportMenu.js +2 -2
- package/dist/internal/blocks/Form/connectors/ComboboxField.js +2 -2
- package/dist/internal/blocks/Form/connectors/MultiComboboxField.js +1 -1
- package/dist/internal/blocks/Form.js +1 -1
- package/dist/internal/blocks/FormGroup.js +1 -1
- package/dist/internal/blocks/InlineEdit.js +1 -1
- package/dist/internal/blocks/MultiActionButtonGroup.js +1 -1
- package/dist/internal/blocks/MultiCombobox.js +204 -68
- package/dist/internal/blocks/MultiCombobox.js.map +1 -1
- package/dist/internal/blocks/PasswordInput.js +1 -1
- package/dist/internal/blocks/SearchInput.js +1 -1
- package/dist/internal/blocks/SearchInputWithNavigation.js +2 -2
- package/dist/internal/blocks/SectionNav.js +1 -1
- package/dist/internal/blocks/SplitButton.js +1 -1
- package/dist/internal/blocks/TextInput.js +1 -1
- package/dist/internal/blocks/TimeInput.js +1 -1
- package/dist/internal/components/Alert.js +1 -1
- package/dist/internal/components/Avatar.js +1 -1
- package/dist/internal/components/Breadcrumb.js +1 -1
- package/dist/internal/components/Checkbox.js +1 -1
- package/dist/internal/components/Clipboard.js +1 -1
- package/dist/internal/components/CollapsiblePanel.js +1 -1
- package/dist/internal/components/Combobox.js +1 -1
- package/dist/internal/components/DropdownMenu.js +1 -1
- package/dist/internal/components/HoverCard.js +1 -1
- package/dist/internal/components/Kbd.js +1 -1
- package/dist/internal/components/Modal.js +1 -1
- package/dist/internal/components/RadioGroup.js +1 -1
- package/dist/internal/components/Search.js +1 -1
- package/dist/internal/components/Sidebar.js +1 -1
- package/dist/internal/components/Skeleton/Skeleton.js +1 -1
- package/dist/internal/components/Skeleton/Skeleton.js.map +1 -1
- package/dist/internal/components/Slider.js +1 -1
- package/dist/internal/components/Table.js +1 -1
- package/dist/internal/components/ThemeProvider.js +1 -1
- package/dist/internal/components/Tooltip.js +1 -1
- package/dist/internal/components/Truncated.js +1 -1
- package/dist/internal/components/ZoomableSvg.js +1 -1
- package/dist/internal/utils.js +1 -1
- package/dist/markdown-editor.js +6 -6
- package/dist/mermaid.d.ts +1 -2
- package/dist/mermaid.js +1 -1
- package/dist/rjsf.js +98 -11
- package/dist/rjsf.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/svgCache-Dhmbe-7i.js +26 -0
- package/dist/svgCache-Dhmbe-7i.js.map +1 -0
- package/dist/svgTransforms-Hnvev3Vs.js +38 -0
- package/dist/svgTransforms-Hnvev3Vs.js.map +1 -0
- package/dist/testing.js +1 -1
- package/dist/{themeTokens-7DEab0kp.js → themeTokens-l2Fsfcs7.js} +3 -12
- package/dist/themeTokens-l2Fsfcs7.js.map +1 -0
- package/dist/{utils-p6tjltbX.js → utils-LQ4jJA-9.js} +18 -2
- package/dist/utils-LQ4jJA-9.js.map +1 -0
- package/dist/vega.js +1 -1
- package/dist/vegaTheme-BrmVoPEz.js +49 -0
- package/dist/vegaTheme-BrmVoPEz.js.map +1 -0
- package/dist/{vegaTheme-VQMWTlBw.js → vegaTokens-Ce07C0DH.js} +36 -44
- package/dist/vegaTokens-Ce07C0DH.js.map +1 -0
- package/package.json +3 -4
- package/src/styles.css +4 -4
- package/dist/AIKit-C5FbKjwa.js.map +0 -1
- package/dist/Breadcrumb-CUyYu5Bi.js.map +0 -1
- package/dist/Combobox-B1Kjoy5J.js.map +0 -1
- package/dist/Command-BuOA3V18.js.map +0 -1
- package/dist/DiagramSource-jTwmmi9g.js +0 -11
- package/dist/DiagramSource-jTwmmi9g.js.map +0 -1
- package/dist/HoverCard-Cy6nhg4u.js.map +0 -1
- package/dist/MermaidDiagram-D2ThT8Q-.js +0 -302
- package/dist/MermaidDiagram-D2ThT8Q-.js.map +0 -1
- package/dist/Modal-DQIauzar.js.map +0 -1
- package/dist/Table-CgSC-fSo.js.map +0 -1
- package/dist/ZoomableSvg-D8sXKpoV.js.map +0 -1
- package/dist/svgTransforms-Dw2hRA-i.js +0 -51
- package/dist/svgTransforms-Dw2hRA-i.js.map +0 -1
- package/dist/themeTokens-7DEab0kp.js.map +0 -1
- package/dist/utils-p6tjltbX.js.map +0 -1
- package/dist/vegaTheme-VQMWTlBw.js.map +0 -1
package/dist/flint.js
CHANGED
|
@@ -1,40 +1,14 @@
|
|
|
1
|
-
import { DiagramSkeleton } from "./internal/blocks/DiagramShared/DiagramSkeleton.js";
|
|
2
|
-
import { DiagramSource } from "./DiagramSource-jTwmmi9g.js";
|
|
3
1
|
import { useThemeAttributes } from "./useThemeAttributes-B199Regy.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
2
|
+
import { DiagramSkeleton } from "./internal/blocks/DiagramShared/DiagramSkeleton.js";
|
|
3
|
+
import { DiagramSource, makeResponsive } from "./svgTransforms-Hnvev3Vs.js";
|
|
4
|
+
import { ZoomableSvg } from "./ZoomableSvg-Dh1OstPN.js";
|
|
5
|
+
import { SERIES_TOKENS, createVegaColorResolver, tierTwoColour } from "./vegaTokens-Ce07C0DH.js";
|
|
6
|
+
import { vegaTheme } from "./vegaTheme-BrmVoPEz.js";
|
|
7
|
+
import { createSvgCache, diagramCacheKey } from "./svgCache-Dhmbe-7i.js";
|
|
7
8
|
import { useEffect, useState } from "react";
|
|
8
9
|
import { jsx } from "react/jsx-runtime";
|
|
9
10
|
//#region src/blocks/FlintChart/flintTheme.ts
|
|
10
11
|
/**
|
|
11
|
-
* How the second palette tier (series 6-10) is derived from the core hues.
|
|
12
|
-
*
|
|
13
|
-
* - Each hue moves *away* from its surface: darker on light, lighter on dark.
|
|
14
|
-
* Darkening on dark would drop contrast from ~6.7:1 to ~3.7:1.
|
|
15
|
-
* - `amount` is the fraction of the distance travelled toward `towards`.
|
|
16
|
-
*/
|
|
17
|
-
const TIER_TWO_SHIFT = {
|
|
18
|
-
light: {
|
|
19
|
-
towards: 0,
|
|
20
|
-
amount: .38
|
|
21
|
-
},
|
|
22
|
-
dark: {
|
|
23
|
-
towards: 255,
|
|
24
|
-
amount: .35
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
/**
|
|
28
|
-
* Move an `rgb()` string's channels a fraction of the way toward `towards`
|
|
29
|
-
* (0 = black, 255 = white). Returns the input unchanged if it is not that shape.
|
|
30
|
-
*/
|
|
31
|
-
const shiftChannels = (colour, towards, amount) => {
|
|
32
|
-
const channels = colour.match(/\d+/g);
|
|
33
|
-
if (!channels || channels.length < 3) return colour;
|
|
34
|
-
const shifted = channels.slice(0, 3).map((c) => Math.round(Number(c) + (towards - Number(c)) * amount));
|
|
35
|
-
return vegaRgb(shifted.join(" "));
|
|
36
|
-
};
|
|
37
|
-
/**
|
|
38
12
|
* Put the design system's `--chart-1..5` palette on a Flint input.
|
|
39
13
|
*
|
|
40
14
|
* - Flint resolves series colours during assembly, so they ride `theme_spec`, not `vegaTheme`.
|
|
@@ -63,7 +37,7 @@ const withFlintSeriesPalette = (input, scheme, from) => {
|
|
|
63
37
|
},
|
|
64
38
|
series: {
|
|
65
39
|
categorical,
|
|
66
|
-
categoricalExtended: [...categorical, ...categorical.map((colour) =>
|
|
40
|
+
categoricalExtended: [...categorical, ...categorical.map((colour) => tierTwoColour(colour, scheme))],
|
|
67
41
|
overflow: token("muted-foreground"),
|
|
68
42
|
sequential: {
|
|
69
43
|
stops: [token("accent"), token("chart-1")],
|
|
@@ -201,20 +175,11 @@ const getFlintRuntime = () => {
|
|
|
201
175
|
};
|
|
202
176
|
const RENDER_DEBOUNCE_MS = 300;
|
|
203
177
|
const MAX_SOURCE_CHARS = 2e5;
|
|
204
|
-
const
|
|
205
|
-
const svgCache = /* @__PURE__ */ new Map();
|
|
206
|
-
const cacheSvg = (key, svg) => {
|
|
207
|
-
if (!svgCache.has(key) && svgCache.size >= MAX_CACHED_CHARTS) {
|
|
208
|
-
const oldest = svgCache.keys().next();
|
|
209
|
-
if (!oldest.done) svgCache.delete(oldest.value);
|
|
210
|
-
}
|
|
211
|
-
svgCache.set(key, svg);
|
|
212
|
-
};
|
|
213
|
-
const chartCacheKey = (code, themeKey) => `${themeKey}\n${code}`;
|
|
178
|
+
const svgCache = createSvgCache();
|
|
214
179
|
const prevThemeKeyOf = (key) => key.slice(0, key.indexOf("\n"));
|
|
215
180
|
const FlintChart = ({ code, renderActions }) => {
|
|
216
181
|
const { scheme, cacheKey: themeKey, scope } = useThemeAttributes();
|
|
217
|
-
const cacheKey =
|
|
182
|
+
const cacheKey = diagramCacheKey(code, themeKey);
|
|
218
183
|
const [svg, setSvg] = useState(() => svgCache.get(cacheKey) ?? null);
|
|
219
184
|
const [renderFailed, setRenderFailed] = useState(false);
|
|
220
185
|
const [prevKey, setPrevKey] = useState(cacheKey);
|
|
@@ -254,7 +219,7 @@ const FlintChart = ({ code, renderActions }) => {
|
|
|
254
219
|
if (cancelled) return;
|
|
255
220
|
const svgOutput = makeResponsive(await renderFlintSvg(runtime, withFlintSeriesPalette(input, scheme, scope), vegaTheme(scheme, scope)));
|
|
256
221
|
if (cancelled) return;
|
|
257
|
-
|
|
222
|
+
svgCache.set(cacheKey, svgOutput);
|
|
258
223
|
setSvg(svgOutput);
|
|
259
224
|
setRenderFailed(false);
|
|
260
225
|
}).catch((err) => {
|
package/dist/flint.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flint.js","names":[],"sources":["../src/blocks/FlintChart/flintTheme.ts","../src/blocks/FlintChart/refuseUnsafeSpec.ts","../src/blocks/FlintChart/renderFlintSvg.ts","../src/blocks/FlintChart/FlintChart.tsx"],"sourcesContent":["import type { ChartAssemblyInput } from 'flint-chart';\n\nimport type { ResolvedColorScheme } from '../../components/ThemeProvider/types';\nimport { createVegaColorResolver, SERIES_TOKENS, vegaRgb } from '../../vega/vegaTokens';\n\n/**\n * How the second palette tier (series 6-10) is derived from the core hues.\n *\n * - Each hue moves *away* from its surface: darker on light, lighter on dark.\n * Darkening on dark would drop contrast from ~6.7:1 to ~3.7:1.\n * - `amount` is the fraction of the distance travelled toward `towards`.\n */\nconst TIER_TWO_SHIFT = {\n light: { towards: 0, amount: 0.38 },\n dark: { towards: 255, amount: 0.35 },\n} as const;\n\n/**\n * Move an `rgb()` string's channels a fraction of the way toward `towards`\n * (0 = black, 255 = white). Returns the input unchanged if it is not that shape.\n */\nconst shiftChannels = (colour: string, towards: number, amount: number): string => {\n const channels = colour.match(/\\d+/g);\n if (!channels || channels.length < 3) return colour;\n const shifted = channels\n .slice(0, 3)\n .map((c) => Math.round(Number(c) + (towards - Number(c)) * amount));\n return vegaRgb(shifted.join(' '));\n};\n\n/**\n * Put the design system's `--chart-1..5` palette on a Flint input.\n *\n * - Flint resolves series colours during assembly, so they ride `theme_spec`, not `vegaTheme`.\n * - An author-supplied `theme_spec` is passed through untouched.\n * - A `null` `theme_spec` is replaced like a missing one — flint throws on it.\n */\nexport const withFlintSeriesPalette = (\n input: ChartAssemblyInput,\n scheme: ResolvedColorScheme,\n from?: Element | null,\n): ChartAssemblyInput => {\n if (input.theme_spec !== undefined && input.theme_spec !== null) return input;\n\n const token = createVegaColorResolver(scheme, from);\n const categorical = SERIES_TOKENS.map((name) => token(name));\n\n return {\n ...input,\n theme_spec: {\n ink: {\n // `endpointsAgainstSurface` below needs to know what the chart sits on.\n surface: { canvas: token('card') },\n // Flint's defaults assume a light canvas, so text and structure come from tokens.\n text: {\n primary: token('foreground'),\n secondary: token('muted-foreground'),\n muted: token('muted-foreground'),\n // Ink on a filled (series-coloured) mark.\n inverse: token('card'),\n },\n structure: {\n axis: token('border'),\n grid: token('border'),\n frame: token('border'),\n rule: token('border'),\n },\n series: {\n categorical,\n // Past five series flint swaps `categorical` for its theme-blind `tableau10`;\n // this keeps 6-10 on our hues (`TIER_TWO_SHIFT`).\n categoricalExtended: [\n ...categorical,\n ...categorical.map((colour) =>\n shiftChannels(colour, TIER_TWO_SHIFT[scheme].towards, TIER_TWO_SHIFT[scheme].amount),\n ),\n ],\n // The tail past even the extended set.\n overflow: token('muted-foreground'),\n // For a quantitative `color` field; flint's default starts at black, invisible on dark.\n sequential: {\n stops: [token('accent'), token('chart-1')],\n space: 'lab',\n endpointsAgainstSurface: true,\n },\n },\n },\n },\n };\n};\n","/**\n * The boundary in front of the flint renderer: nothing in a spec may fetch or run.\n *\n * - Checked on the input, before `assembleVegaLite`: compiled Vega carries its own `signals`/`expr`.\n * - Refused on a key's presence, not its value: a relative `data.url` is still a fetch.\n */\n\n/** Keys whose presence anywhere in a spec is refused: each fetches, evaluates or binds a signal. */\nconst REFUSED_KEYS = new Set(['url', 'expr', 'signal', 'signals', 'params']);\n\n/** Suffix of Vega-Lite's other expression properties; matched by shape so new ones are refused. */\nconst EXPRESSION_KEY_SUFFIX = /Expr$/;\n\n/**\n * An own property's value, or `undefined`.\n *\n * - Via a descriptor: inherited keys are ignored and a getter is never called.\n */\nconst ownValue = (node: unknown, key: string): unknown => {\n if (typeof node !== 'object' || node === null) return undefined;\n\n return Object.getOwnPropertyDescriptor(node, key)?.value;\n};\n\n/**\n * The chart's rows (`spec.data.values` only), which the walk does not descend into.\n *\n * - Rows are data vega never evaluates, so a column named `url` must not be refused.\n * - Flint takes one top-level dataset, so every other node is spec.\n * - Skipped by identity, not key name: a `values` key elsewhere must not hide a refused key.\n */\nconst inlineRows = (spec: unknown): unknown[] | null => {\n const values = ownValue(ownValue(spec, 'data'), 'values');\n\n return Array.isArray(values) ? values : null;\n};\n\n/**\n * The top-level maps flint keys by column name, so their keys are data, not spec.\n *\n * - Only the keys are exempt; each value is walked like any other node.\n * - Matched by identity, as in `inlineRows`: a nested object with the same name gets no exemption.\n */\nconst COLUMN_KEYED_MAPS = ['semantic_types', 'field_display_names'];\n\nconst columnKeyedMaps = (spec: unknown): unknown[] =>\n COLUMN_KEYED_MAPS.map((key) => ownValue(spec, key)).filter(\n (map) => typeof map === 'object' && map !== null,\n );\n\n/** Ceiling on the nodes the walk visits; exceeding it refuses, since the spec went unchecked. */\nconst MAX_SPEC_NODES = 10_000;\n\nconst TOO_LARGE = 'is too large to check';\n\n/**\n * Why this spec may not be rendered, or `null` if it may.\n *\n * - The reason goes to the console; the reader sees `DiagramSource`'s fallback.\n * - Iterative, so deep nesting cannot overflow the stack; nodes are charged on push.\n * - An accessor is refused, not skipped: its value cannot be read without running it.\n */\nexport const findUnsafeSpecKey = (spec: unknown): string | null => {\n const rows = inlineRows(spec);\n const columnKeyed = columnKeyedMaps(spec);\n const stack: unknown[] = [spec];\n let visited = 1;\n\n while (stack.length > 0) {\n const node = stack.pop();\n\n if (Array.isArray(node)) {\n visited += node.length;\n if (visited > MAX_SPEC_NODES) return TOO_LARGE;\n stack.push(...node);\n continue;\n }\n\n if (typeof node !== 'object' || node === null) continue;\n\n const keysAreColumnNames = columnKeyed.includes(node);\n\n // Descriptors: skip inherited keys and never call a getter.\n for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(node))) {\n if (!keysAreColumnNames && (REFUSED_KEYS.has(key) || EXPRESSION_KEY_SUFFIX.test(key))) {\n return `carries \\`${key}\\``;\n }\n if (!('value' in descriptor)) return `carries an accessor at \\`${key}\\``;\n\n const { value } = descriptor;\n if (rows !== null && value === rows) continue;\n\n visited += 1;\n if (visited > MAX_SPEC_NODES) return TOO_LARGE;\n stack.push(value);\n }\n }\n\n return null;\n};\n","import type { ChartAssemblyInput } from 'flint-chart';\nimport type { assembleVegaLite } from 'flint-chart/vegalite';\nimport type * as vega from 'vega';\nimport type * as vegaLite from 'vega-lite';\nimport type { Config, TopLevelSpec } from 'vega-lite';\n\nimport { findUnsafeSpecKey } from './refuseUnsafeSpec';\n\n// The lazily imported modules the pipeline needs; a parameter so tests can pass a fake runtime.\nexport type FlintRuntime = {\n assembleVegaLite: typeof assembleVegaLite;\n vega: Pick<typeof vega, 'View' | 'parse'>;\n vegaLite: Pick<typeof vegaLite, 'compile'>;\n};\n\n/**\n * Compile a Flint `ChartAssemblyInput` to a static SVG string.\n *\n * - The one place unsafe input is refused (`refuseUnsafeSpec.ts`), before anything compiles it.\n * - Flint's `_`-prefixed hint keys are not Vega-Lite schema and are stripped before compiling.\n * - `config` rides the compile options; flint's spec-side config sets no colours to conflict.\n * - Renders headless; `toSVG()` is the only output.\n */\nexport const renderFlintSvg = async (\n runtime: FlintRuntime,\n input: ChartAssemblyInput,\n config: Config,\n): Promise<string> => {\n const unsafe = findUnsafeSpecKey(input);\n if (unsafe !== null) {\n throw new Error(`Refusing to render a flint spec that ${unsafe}`);\n }\n\n const assembled: Record<string, unknown> = runtime.assembleVegaLite(input);\n // `assembleVegaLite` is typed loosely upstream; without the hint keys it is a Vega-Lite spec.\n const vlSpec = Object.fromEntries(\n Object.entries(assembled).filter(([key]) => !key.startsWith('_')),\n ) as unknown as TopLevelSpec;\n const { spec } = runtime.vegaLite.compile(vlSpec, { config });\n const view = new runtime.vega.View(runtime.vega.parse(spec), { renderer: 'none' });\n try {\n await view.runAsync();\n return await view.toSVG();\n } finally {\n view.finalize();\n }\n};\n","import { useEffect, useState } from 'react';\nimport type { ReactNode } from 'react';\nimport type { ChartAssemblyInput } from 'flint-chart';\n\nimport { useThemeAttributes } from '../../components/ThemeProvider/useThemeAttributes';\nimport { ZoomableSvg } from '../../components/ZoomableSvg';\nimport { vegaTheme } from '../../vega/vegaTheme';\nimport { DiagramSkeleton } from '../DiagramShared/DiagramSkeleton';\nimport { DiagramSource } from '../DiagramShared/DiagramSource';\nimport { makeResponsive } from '../DiagramShared/svgTransforms';\n\nimport { withFlintSeriesPalette } from './flintTheme';\nimport type { FlintRuntime } from './renderFlintSvg';\nimport { renderFlintSvg } from './renderFlintSvg';\n\nlet runtimePromise: Promise<FlintRuntime> | null = null;\n\n// Memoized chart-stack import, reset on failure so a chunk-load error can retry.\n// Mirrors `getMermaid()` in `MermaidDiagram`.\nconst getFlintRuntime = (): Promise<FlintRuntime> => {\n if (!runtimePromise) {\n runtimePromise = Promise.all([\n import('flint-chart/vegalite'),\n import('vega'),\n import('vega-lite'),\n ])\n .then(([flint, vega, vegaLite]) => ({\n assembleVegaLite: flint.assembleVegaLite,\n vega,\n vegaLite,\n }))\n .catch((err) => {\n runtimePromise = null;\n throw err;\n });\n }\n return runtimePromise;\n};\n\nconst RENDER_DEBOUNCE_MS = 300;\n\n// A streaming fence is unparseable just like a broken one, so unparseable source\n// keeps the skeleton until it has been unchanged this long, then shows as raw source.\nconst PARSE_GRACE_MS = 1500;\n\n// Parse, compile and layout are synchronous, so a larger source would freeze the chat.\n// Far above the prompt's ~50-row inline data cap.\nconst MAX_SOURCE_CHARS = 200_000;\n\n// SVG cache that survives remounts.\n// - Keyed by theme key + code: colours are baked into the markup.\n// - Values are already `makeResponsive`d, so ZoomableSvg's sanitize memo stays stable.\n// - Bounded and evicted oldest-first (`Map` insertion order): entries run to hundreds of KB.\nconst MAX_CACHED_CHARTS = 24;\nconst svgCache = new Map<string, string>();\n\nconst cacheSvg = (key: string, svg: string): void => {\n // Overwriting an existing key adds no entry, so it must not evict one.\n if (!svgCache.has(key) && svgCache.size >= MAX_CACHED_CHARTS) {\n const oldest = svgCache.keys().next();\n if (!oldest.done) svgCache.delete(oldest.value);\n }\n svgCache.set(key, svg);\n};\n\nconst chartCacheKey = (code: string, themeKey: string): string => `${themeKey}\\n${code}`;\n\n// The theme half of a cache key — tells a still-streaming spec from a theme switch.\nconst prevThemeKeyOf = (key: string): string => key.slice(0, key.indexOf('\\n'));\n\ntype Props = {\n /** Raw source of a ```flint fence: a flint-chart `ChartAssemblyInput` as JSON. */\n code: string;\n /** Extra toolbar controls (e.g. `ExportMenu`) for `ZoomableSvg`; receives the sanitized SVG. */\n renderActions?: (safeSvg: string) => ReactNode;\n};\n\nexport const FlintChart = ({ code, renderActions }: Props) => {\n // - Re-renders on a scheme or brand change, same contract as MermaidDiagram.\n // - `scope` is the element tokens are read off (`null` = `<html>`); it is part of `themeKey`.\n const { scheme, cacheKey: themeKey, scope } = useThemeAttributes();\n const cacheKey = chartCacheKey(code, themeKey);\n\n const [svg, setSvg] = useState<string | null>(() => svgCache.get(cacheKey) ?? null);\n const [renderFailed, setRenderFailed] = useState(false);\n\n // Reset stale state synchronously on a code or theme change (no one-frame flash):\n // 1. A cache hit is applied at once.\n // 2. Same theme, source only grew (still streaming): keep the last chart, no flicker.\n // 3. Anything else clears — colours are baked in, so a stale chart is wrong.\n const [prevKey, setPrevKey] = useState(cacheKey);\n const [prevCode, setPrevCode] = useState(code);\n if (cacheKey !== prevKey) {\n const cached = svgCache.get(cacheKey);\n const sameThemeStillStreaming =\n themeKey === prevThemeKeyOf(prevKey) && code.startsWith(prevCode);\n setPrevKey(cacheKey);\n setPrevCode(code);\n if (cached) setSvg(cached);\n else if (!sameThemeStillStreaming) setSvg(null);\n setRenderFailed(false);\n }\n\n useEffect(() => {\n if (svgCache.has(cacheKey)) return;\n\n let cancelled = false;\n let graceTimer: ReturnType<typeof setTimeout> | undefined;\n\n const debounceTimer = setTimeout(() => {\n if (code.length > MAX_SOURCE_CHARS) {\n // Never becomes renderable, so skip the grace period.\n console.error(\n `[FlintChart] source exceeds ${MAX_SOURCE_CHARS} characters (${code.length}); not rendering`,\n );\n setSvg(null);\n setRenderFailed(true);\n return;\n }\n\n let input: ChartAssemblyInput;\n try {\n input = JSON.parse(code);\n } catch (err) {\n // Likely still streaming — fail only if unchanged when the grace period ends.\n graceTimer = setTimeout(() => {\n console.debug('[FlintChart] source never became parseable JSON:', err);\n setSvg(null);\n setRenderFailed(true);\n }, PARSE_GRACE_MS - RENDER_DEBOUNCE_MS);\n return;\n }\n\n getFlintRuntime()\n .then(async (runtime) => {\n if (cancelled) return;\n // `scheme` comes from the same read as the cache key, so palette and key agree.\n const svgOutput = makeResponsive(\n await renderFlintSvg(\n runtime,\n withFlintSeriesPalette(input, scheme, scope),\n vegaTheme(scheme, scope),\n ),\n );\n if (cancelled) return;\n cacheSvg(cacheKey, svgOutput);\n setSvg(svgOutput);\n setRenderFailed(false);\n })\n .catch((err) => {\n if (cancelled) return;\n console.error('[FlintChart] render failed:', err);\n setSvg(null);\n setRenderFailed(true);\n });\n }, RENDER_DEBOUNCE_MS);\n\n return () => {\n cancelled = true;\n clearTimeout(debounceTimer);\n if (graceTimer) clearTimeout(graceTimer);\n };\n }, [cacheKey, code, scheme, scope]);\n\n if (renderFailed) {\n return <DiagramSource code={code} />;\n }\n\n if (svg) {\n return (\n <ZoomableSvg\n // Keyed by SVG, not `code`: a streaming chart is held, and a remount per chunk\n // would drop zoom/pan.\n key={svg}\n html={svg}\n copyText={`\\`\\`\\`flint\\n${code}\\n\\`\\`\\``}\n className=\"tw:my-3\"\n renderActions={renderActions}\n />\n );\n }\n\n return <DiagramSkeleton />;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"flint.js","names":[],"sources":["../src/blocks/FlintChart/flintTheme.ts","../src/blocks/FlintChart/refuseUnsafeSpec.ts","../src/blocks/FlintChart/renderFlintSvg.ts","../src/blocks/FlintChart/FlintChart.tsx"],"sourcesContent":["import type { ChartAssemblyInput } from 'flint-chart';\n\nimport type { ResolvedColorScheme } from '../../components/ThemeProvider/types';\nimport { createVegaColorResolver, SERIES_TOKENS, tierTwoColour } from '../../vega/vegaTokens';\n\n/**\n * Put the design system's `--chart-1..5` palette on a Flint input.\n *\n * - Flint resolves series colours during assembly, so they ride `theme_spec`, not `vegaTheme`.\n * - An author-supplied `theme_spec` is passed through untouched.\n * - A `null` `theme_spec` is replaced like a missing one — flint throws on it.\n */\nexport const withFlintSeriesPalette = (\n input: ChartAssemblyInput,\n scheme: ResolvedColorScheme,\n from?: Element | null,\n): ChartAssemblyInput => {\n if (input.theme_spec !== undefined && input.theme_spec !== null) return input;\n\n const token = createVegaColorResolver(scheme, from);\n const categorical = SERIES_TOKENS.map((name) => token(name));\n\n return {\n ...input,\n theme_spec: {\n ink: {\n // `endpointsAgainstSurface` below needs to know what the chart sits on.\n surface: { canvas: token('card') },\n // Flint's defaults assume a light canvas, so text and structure come from tokens.\n text: {\n primary: token('foreground'),\n secondary: token('muted-foreground'),\n muted: token('muted-foreground'),\n // Ink on a filled (series-coloured) mark.\n inverse: token('card'),\n },\n structure: {\n axis: token('border'),\n grid: token('border'),\n frame: token('border'),\n rule: token('border'),\n },\n series: {\n categorical,\n // Past five series flint swaps `categorical` for its theme-blind `tableau10`;\n // this keeps 6-10 on our hues (`tierTwoColour`).\n categoricalExtended: [\n ...categorical,\n ...categorical.map((colour) => tierTwoColour(colour, scheme)),\n ],\n // The tail past even the extended set.\n overflow: token('muted-foreground'),\n // For a quantitative `color` field; flint's default starts at black, invisible on dark.\n sequential: {\n stops: [token('accent'), token('chart-1')],\n space: 'lab',\n endpointsAgainstSurface: true,\n },\n },\n },\n },\n };\n};\n","/**\n * The boundary in front of the flint renderer: nothing in a spec may fetch or run.\n *\n * - Checked on the input, before `assembleVegaLite`: compiled Vega carries its own `signals`/`expr`.\n * - Refused on a key's presence, not its value: a relative `data.url` is still a fetch.\n */\n\n/** Keys whose presence anywhere in a spec is refused: each fetches, evaluates or binds a signal. */\nconst REFUSED_KEYS = new Set(['url', 'expr', 'signal', 'signals', 'params']);\n\n/** Suffix of Vega-Lite's other expression properties; matched by shape so new ones are refused. */\nconst EXPRESSION_KEY_SUFFIX = /Expr$/;\n\n/**\n * An own property's value, or `undefined`.\n *\n * - Via a descriptor: inherited keys are ignored and a getter is never called.\n */\nconst ownValue = (node: unknown, key: string): unknown => {\n if (typeof node !== 'object' || node === null) return undefined;\n\n return Object.getOwnPropertyDescriptor(node, key)?.value;\n};\n\n/**\n * The chart's rows (`spec.data.values` only), which the walk does not descend into.\n *\n * - Rows are data vega never evaluates, so a column named `url` must not be refused.\n * - Flint takes one top-level dataset, so every other node is spec.\n * - Skipped by identity, not key name: a `values` key elsewhere must not hide a refused key.\n */\nconst inlineRows = (spec: unknown): unknown[] | null => {\n const values = ownValue(ownValue(spec, 'data'), 'values');\n\n return Array.isArray(values) ? values : null;\n};\n\n/**\n * The top-level maps flint keys by column name, so their keys are data, not spec.\n *\n * - Only the keys are exempt; each value is walked like any other node.\n * - Matched by identity, as in `inlineRows`: a nested object with the same name gets no exemption.\n */\nconst COLUMN_KEYED_MAPS = ['semantic_types', 'field_display_names'];\n\nconst columnKeyedMaps = (spec: unknown): unknown[] =>\n COLUMN_KEYED_MAPS.map((key) => ownValue(spec, key)).filter(\n (map) => typeof map === 'object' && map !== null,\n );\n\n/** Ceiling on the nodes the walk visits; exceeding it refuses, since the spec went unchecked. */\nconst MAX_SPEC_NODES = 10_000;\n\nconst TOO_LARGE = 'is too large to check';\n\n/**\n * Why this spec may not be rendered, or `null` if it may.\n *\n * - The reason goes to the console; the reader sees `DiagramSource`'s fallback.\n * - Iterative, so deep nesting cannot overflow the stack; nodes are charged on push.\n * - An accessor is refused, not skipped: its value cannot be read without running it.\n */\nexport const findUnsafeSpecKey = (spec: unknown): string | null => {\n const rows = inlineRows(spec);\n const columnKeyed = columnKeyedMaps(spec);\n const stack: unknown[] = [spec];\n let visited = 1;\n\n while (stack.length > 0) {\n const node = stack.pop();\n\n if (Array.isArray(node)) {\n visited += node.length;\n if (visited > MAX_SPEC_NODES) return TOO_LARGE;\n stack.push(...node);\n continue;\n }\n\n if (typeof node !== 'object' || node === null) continue;\n\n const keysAreColumnNames = columnKeyed.includes(node);\n\n // Descriptors: skip inherited keys and never call a getter.\n for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(node))) {\n if (!keysAreColumnNames && (REFUSED_KEYS.has(key) || EXPRESSION_KEY_SUFFIX.test(key))) {\n return `carries \\`${key}\\``;\n }\n if (!('value' in descriptor)) return `carries an accessor at \\`${key}\\``;\n\n const { value } = descriptor;\n if (rows !== null && value === rows) continue;\n\n visited += 1;\n if (visited > MAX_SPEC_NODES) return TOO_LARGE;\n stack.push(value);\n }\n }\n\n return null;\n};\n","import type { ChartAssemblyInput } from 'flint-chart';\nimport type { assembleVegaLite } from 'flint-chart/vegalite';\nimport type * as vega from 'vega';\nimport type * as vegaLite from 'vega-lite';\nimport type { Config, TopLevelSpec } from 'vega-lite';\n\nimport { findUnsafeSpecKey } from './refuseUnsafeSpec';\n\n// The lazily imported modules the pipeline needs; a parameter so tests can pass a fake runtime.\nexport type FlintRuntime = {\n assembleVegaLite: typeof assembleVegaLite;\n vega: Pick<typeof vega, 'View' | 'parse'>;\n vegaLite: Pick<typeof vegaLite, 'compile'>;\n};\n\n/**\n * Compile a Flint `ChartAssemblyInput` to a static SVG string.\n *\n * - The one place unsafe input is refused (`refuseUnsafeSpec.ts`), before anything compiles it.\n * - Flint's `_`-prefixed hint keys are not Vega-Lite schema and are stripped before compiling.\n * - `config` rides the compile options; flint's spec-side config sets no colours to conflict.\n * - Renders headless; `toSVG()` is the only output.\n */\nexport const renderFlintSvg = async (\n runtime: FlintRuntime,\n input: ChartAssemblyInput,\n config: Config,\n): Promise<string> => {\n const unsafe = findUnsafeSpecKey(input);\n if (unsafe !== null) {\n throw new Error(`Refusing to render a flint spec that ${unsafe}`);\n }\n\n const assembled: Record<string, unknown> = runtime.assembleVegaLite(input);\n // `assembleVegaLite` is typed loosely upstream; without the hint keys it is a Vega-Lite spec.\n const vlSpec = Object.fromEntries(\n Object.entries(assembled).filter(([key]) => !key.startsWith('_')),\n ) as unknown as TopLevelSpec;\n const { spec } = runtime.vegaLite.compile(vlSpec, { config });\n const view = new runtime.vega.View(runtime.vega.parse(spec), { renderer: 'none' });\n try {\n await view.runAsync();\n return await view.toSVG();\n } finally {\n view.finalize();\n }\n};\n","import { useEffect, useState } from 'react';\nimport type { ReactNode } from 'react';\nimport type { ChartAssemblyInput } from 'flint-chart';\n\nimport { useThemeAttributes } from '../../components/ThemeProvider/useThemeAttributes';\nimport { ZoomableSvg } from '../../components/ZoomableSvg';\nimport { vegaTheme } from '../../vega/vegaTheme';\nimport { DiagramSkeleton } from '../DiagramShared/DiagramSkeleton';\nimport { DiagramSource } from '../DiagramShared/DiagramSource';\nimport { createSvgCache, diagramCacheKey } from '../DiagramShared/svgCache';\nimport { makeResponsive } from '../DiagramShared/svgTransforms';\n\nimport { withFlintSeriesPalette } from './flintTheme';\nimport type { FlintRuntime } from './renderFlintSvg';\nimport { renderFlintSvg } from './renderFlintSvg';\n\nlet runtimePromise: Promise<FlintRuntime> | null = null;\n\n// Memoized chart-stack import, reset on failure so a chunk-load error can retry.\n// Mirrors `getMermaid()` in `MermaidDiagram`.\nconst getFlintRuntime = (): Promise<FlintRuntime> => {\n if (!runtimePromise) {\n runtimePromise = Promise.all([\n import('flint-chart/vegalite'),\n import('vega'),\n import('vega-lite'),\n ])\n .then(([flint, vega, vegaLite]) => ({\n assembleVegaLite: flint.assembleVegaLite,\n vega,\n vegaLite,\n }))\n .catch((err) => {\n runtimePromise = null;\n throw err;\n });\n }\n return runtimePromise;\n};\n\nconst RENDER_DEBOUNCE_MS = 300;\n\n// A streaming fence is unparseable just like a broken one, so unparseable source\n// keeps the skeleton until it has been unchanged this long, then shows as raw source.\nconst PARSE_GRACE_MS = 1500;\n\n// Parse, compile and layout are synchronous, so a larger source would freeze the chat.\n// Far above the prompt's ~50-row inline data cap.\nconst MAX_SOURCE_CHARS = 200_000;\n\n// Values are already `makeResponsive`d, so ZoomableSvg's sanitize memo stays stable.\nconst svgCache = createSvgCache();\n\n// The theme half of a cache key — tells a still-streaming spec from a theme switch.\nconst prevThemeKeyOf = (key: string): string => key.slice(0, key.indexOf('\\n'));\n\ntype Props = {\n /** Raw source of a ```flint fence: a flint-chart `ChartAssemblyInput` as JSON. */\n code: string;\n /** Extra toolbar controls (e.g. `ExportMenu`) for `ZoomableSvg`; receives the sanitized SVG. */\n renderActions?: (safeSvg: string) => ReactNode;\n};\n\nexport const FlintChart = ({ code, renderActions }: Props) => {\n // - Re-renders on a scheme or brand change, same contract as MermaidDiagram.\n // - `scope` is the element tokens are read off (`null` = `<html>`); it is part of `themeKey`.\n const { scheme, cacheKey: themeKey, scope } = useThemeAttributes();\n const cacheKey = diagramCacheKey(code, themeKey);\n\n const [svg, setSvg] = useState<string | null>(() => svgCache.get(cacheKey) ?? null);\n const [renderFailed, setRenderFailed] = useState(false);\n\n // Reset stale state synchronously on a code or theme change (no one-frame flash):\n // 1. A cache hit is applied at once.\n // 2. Same theme, source only grew (still streaming): keep the last chart, no flicker.\n // 3. Anything else clears — colours are baked in, so a stale chart is wrong.\n const [prevKey, setPrevKey] = useState(cacheKey);\n const [prevCode, setPrevCode] = useState(code);\n if (cacheKey !== prevKey) {\n const cached = svgCache.get(cacheKey);\n const sameThemeStillStreaming =\n themeKey === prevThemeKeyOf(prevKey) && code.startsWith(prevCode);\n setPrevKey(cacheKey);\n setPrevCode(code);\n if (cached) setSvg(cached);\n else if (!sameThemeStillStreaming) setSvg(null);\n setRenderFailed(false);\n }\n\n useEffect(() => {\n if (svgCache.has(cacheKey)) return;\n\n let cancelled = false;\n let graceTimer: ReturnType<typeof setTimeout> | undefined;\n\n const debounceTimer = setTimeout(() => {\n if (code.length > MAX_SOURCE_CHARS) {\n // Never becomes renderable, so skip the grace period.\n console.error(\n `[FlintChart] source exceeds ${MAX_SOURCE_CHARS} characters (${code.length}); not rendering`,\n );\n setSvg(null);\n setRenderFailed(true);\n return;\n }\n\n let input: ChartAssemblyInput;\n try {\n input = JSON.parse(code);\n } catch (err) {\n // Likely still streaming — fail only if unchanged when the grace period ends.\n graceTimer = setTimeout(() => {\n console.debug('[FlintChart] source never became parseable JSON:', err);\n setSvg(null);\n setRenderFailed(true);\n }, PARSE_GRACE_MS - RENDER_DEBOUNCE_MS);\n return;\n }\n\n getFlintRuntime()\n .then(async (runtime) => {\n if (cancelled) return;\n // `scheme` comes from the same read as the cache key, so palette and key agree.\n const svgOutput = makeResponsive(\n await renderFlintSvg(\n runtime,\n withFlintSeriesPalette(input, scheme, scope),\n vegaTheme(scheme, scope),\n ),\n );\n if (cancelled) return;\n svgCache.set(cacheKey, svgOutput);\n setSvg(svgOutput);\n setRenderFailed(false);\n })\n .catch((err) => {\n if (cancelled) return;\n console.error('[FlintChart] render failed:', err);\n setSvg(null);\n setRenderFailed(true);\n });\n }, RENDER_DEBOUNCE_MS);\n\n return () => {\n cancelled = true;\n clearTimeout(debounceTimer);\n if (graceTimer) clearTimeout(graceTimer);\n };\n }, [cacheKey, code, scheme, scope]);\n\n if (renderFailed) {\n return <DiagramSource code={code} />;\n }\n\n if (svg) {\n return (\n <ZoomableSvg\n // Keyed by SVG, not `code`: a streaming chart is held, and a remount per chunk\n // would drop zoom/pan.\n key={svg}\n html={svg}\n copyText={`\\`\\`\\`flint\\n${code}\\n\\`\\`\\``}\n className=\"tw:my-3\"\n renderActions={renderActions}\n />\n );\n }\n\n return <DiagramSkeleton />;\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { javascriptLanguage, jsxLanguage, tsxLanguage, typescriptLanguage } from "@codemirror/lang-javascript";
|
|
2
|
+
import { jsonLanguage } from "@codemirror/lang-json";
|
|
3
|
+
import { pythonLanguage } from "@codemirror/lang-python";
|
|
4
|
+
import { StandardSQL } from "@codemirror/lang-sql";
|
|
5
|
+
import { StreamLanguage } from "@codemirror/language";
|
|
6
|
+
import { shell } from "@codemirror/legacy-modes/mode/shell";
|
|
7
|
+
import { highlightCode as highlightCode$1, tagHighlighter, tags } from "@lezer/highlight";
|
|
8
|
+
import { yaml } from "@codemirror/legacy-modes/mode/yaml";
|
|
9
|
+
//#region src/blocks/AIKit/highlightCode.ts
|
|
10
|
+
const GRAMMARS = {
|
|
11
|
+
sql: StandardSQL.language,
|
|
12
|
+
python: pythonLanguage,
|
|
13
|
+
javascript: javascriptLanguage,
|
|
14
|
+
jsx: jsxLanguage,
|
|
15
|
+
typescript: typescriptLanguage,
|
|
16
|
+
tsx: tsxLanguage,
|
|
17
|
+
json: jsonLanguage,
|
|
18
|
+
shell: StreamLanguage.define(shell),
|
|
19
|
+
yaml: StreamLanguage.define(yaml)
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Syntax tag → token classes.
|
|
23
|
+
*
|
|
24
|
+
* - Semantic tokens only, so a brand recolours code with the rest of the answer.
|
|
25
|
+
* - A sub-tag (`controlKeyword`) falls back to its parent's rule.
|
|
26
|
+
*/
|
|
27
|
+
const highlighter = tagHighlighter([
|
|
28
|
+
{
|
|
29
|
+
tag: [
|
|
30
|
+
tags.keyword,
|
|
31
|
+
tags.operatorKeyword,
|
|
32
|
+
tags.modifier
|
|
33
|
+
],
|
|
34
|
+
class: "tw:text-primary"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
tag: [
|
|
38
|
+
tags.string,
|
|
39
|
+
tags.special(tags.string),
|
|
40
|
+
tags.regexp
|
|
41
|
+
],
|
|
42
|
+
class: "tw:text-success"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
tag: tags.comment,
|
|
46
|
+
class: "tw:text-muted-foreground tw:italic"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
tag: [
|
|
50
|
+
tags.number,
|
|
51
|
+
tags.bool,
|
|
52
|
+
tags.null,
|
|
53
|
+
tags.atom
|
|
54
|
+
],
|
|
55
|
+
class: "tw:text-info"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
tag: [tags.function(tags.variableName), tags.function(tags.propertyName)],
|
|
59
|
+
class: "tw:font-semibold"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
tag: [tags.typeName, tags.className],
|
|
63
|
+
class: "tw:text-info"
|
|
64
|
+
}
|
|
65
|
+
]);
|
|
66
|
+
/** Splits `code` into runs, each with the classes its syntax earns; line breaks are `\n` runs. */
|
|
67
|
+
const highlightCode = (code, grammar) => {
|
|
68
|
+
const tokens = [];
|
|
69
|
+
highlightCode$1(code, GRAMMARS[grammar].parser.parse(code), highlighter, (text, className) => tokens.push({
|
|
70
|
+
text,
|
|
71
|
+
className
|
|
72
|
+
}), () => tokens.push({
|
|
73
|
+
text: "\n",
|
|
74
|
+
className: ""
|
|
75
|
+
}));
|
|
76
|
+
return tokens;
|
|
77
|
+
};
|
|
78
|
+
//#endregion
|
|
79
|
+
export { highlightCode };
|
|
80
|
+
|
|
81
|
+
//# sourceMappingURL=highlightCode-D354tCXO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"highlightCode-D354tCXO.js","names":[],"sources":["../src/blocks/AIKit/highlightCode.ts"],"sourcesContent":["import {\n javascriptLanguage,\n jsxLanguage,\n tsxLanguage,\n typescriptLanguage,\n} from '@codemirror/lang-javascript';\nimport { jsonLanguage } from '@codemirror/lang-json';\nimport { pythonLanguage } from '@codemirror/lang-python';\nimport { StandardSQL } from '@codemirror/lang-sql';\nimport type { Language } from '@codemirror/language';\nimport { StreamLanguage } from '@codemirror/language';\nimport { shell } from '@codemirror/legacy-modes/mode/shell';\nimport { yaml } from '@codemirror/legacy-modes/mode/yaml';\nimport { highlightCode as walkHighlights, tagHighlighter, tags } from '@lezer/highlight';\n\nimport type { Highlighter, HighlightToken } from './highlightChunk';\nimport type { HighlightGrammar } from './highlightLanguages';\n\nconst GRAMMARS: Record<HighlightGrammar, Language> = {\n sql: StandardSQL.language,\n python: pythonLanguage,\n javascript: javascriptLanguage,\n jsx: jsxLanguage,\n typescript: typescriptLanguage,\n tsx: tsxLanguage,\n json: jsonLanguage,\n shell: StreamLanguage.define(shell),\n yaml: StreamLanguage.define(yaml),\n};\n\n/**\n * Syntax tag → token classes.\n *\n * - Semantic tokens only, so a brand recolours code with the rest of the answer.\n * - A sub-tag (`controlKeyword`) falls back to its parent's rule.\n */\nconst highlighter = tagHighlighter([\n { tag: [tags.keyword, tags.operatorKeyword, tags.modifier], class: 'tw:text-primary' },\n { tag: [tags.string, tags.special(tags.string), tags.regexp], class: 'tw:text-success' },\n { tag: tags.comment, class: 'tw:text-muted-foreground tw:italic' },\n { tag: [tags.number, tags.bool, tags.null, tags.atom], class: 'tw:text-info' },\n {\n tag: [tags.function(tags.variableName), tags.function(tags.propertyName)],\n class: 'tw:font-semibold',\n },\n { tag: [tags.typeName, tags.className], class: 'tw:text-info' },\n]);\n\n/** Splits `code` into runs, each with the classes its syntax earns; line breaks are `\\n` runs. */\nexport const highlightCode: Highlighter = (code, grammar) => {\n const tokens: HighlightToken[] = [];\n walkHighlights(\n code,\n GRAMMARS[grammar].parser.parse(code),\n highlighter,\n (text, className) => tokens.push({ text, className }),\n () => tokens.push({ text: '\\n', className: '' }),\n );\n return tokens;\n};\n"],"mappings":";;;;;;;;;AAkBA,MAAM,WAA+C;CACnD,KAAK,YAAY;CACjB,QAAQ;CACR,YAAY;CACZ,KAAK;CACL,YAAY;CACZ,KAAK;CACL,MAAM;CACN,OAAO,eAAe,OAAO,KAAK;CAClC,MAAM,eAAe,OAAO,IAAI;AAClC;;;;;;;AAQA,MAAM,cAAc,eAAe;CACjC;EAAE,KAAK;GAAC,KAAK;GAAS,KAAK;GAAiB,KAAK;EAAQ;EAAG,OAAO;CAAkB;CACrF;EAAE,KAAK;GAAC,KAAK;GAAQ,KAAK,QAAQ,KAAK,MAAM;GAAG,KAAK;EAAM;EAAG,OAAO;CAAkB;CACvF;EAAE,KAAK,KAAK;EAAS,OAAO;CAAqC;CACjE;EAAE,KAAK;GAAC,KAAK;GAAQ,KAAK;GAAM,KAAK;GAAM,KAAK;EAAI;EAAG,OAAO;CAAe;CAC7E;EACE,KAAK,CAAC,KAAK,SAAS,KAAK,YAAY,GAAG,KAAK,SAAS,KAAK,YAAY,CAAC;EACxE,OAAO;CACT;CACA;EAAE,KAAK,CAAC,KAAK,UAAU,KAAK,SAAS;EAAG,OAAO;CAAe;AAChE,CAAC;;AAGD,MAAa,iBAA8B,MAAM,YAAY;CAC3D,MAAM,SAA2B,CAAC;CAClC,gBACE,MACA,SAAS,QAAQ,CAAC,OAAO,MAAM,IAAI,GACnC,cACC,MAAM,cAAc,OAAO,KAAK;EAAE;EAAM;CAAU,CAAC,SAC9C,OAAO,KAAK;EAAE,MAAM;EAAM,WAAW;CAAG,CAAC,CACjD;CACA,OAAO;AACT"}
|