flet-box 1.1.2 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/AGENTS_FOR_CODE_GEN.md +284 -0
  2. package/CONTRIBUTING.md +238 -0
  3. package/README.md +2 -2
  4. package/bin/cli.js +88 -1
  5. package/bin/commands/auditFix.js +181 -27
  6. package/bin/commands/checkApi.js +14 -3
  7. package/bin/commands/checkStructure.js +368 -0
  8. package/bin/commands/checkSyntax.js +293 -0
  9. package/bin/commands/checkUi.js +666 -14
  10. package/bin/commands/create.js +14 -0
  11. package/bin/commands/createBundle.js +2 -2
  12. package/bin/commands/metrics.js +448 -0
  13. package/bin/commands/release.js +18 -0
  14. package/bin/commands/report.js +849 -0
  15. package/bin/commands/tools.js +76 -0
  16. package/bin/commands/unpack.js +39 -11
  17. package/bin/commands/verify.js +879 -0
  18. package/bin/mcp.js +280 -0
  19. package/bin/utils/contract.js +27 -0
  20. package/bin/utils/dtsProps.js +5 -1
  21. package/bin/utils/migrationGuide.js +146 -0
  22. package/bin/utils/moduleExports.js +9 -5
  23. package/bin/utils/templates.js +225 -18
  24. package/bin/utils/toolkit.js +191 -0
  25. package/docs/README.md +5 -1
  26. package/docs/arquitectura.md +1 -1
  27. package/docs/cli/README.md +168 -2
  28. package/docs/guides/README.md +6 -0
  29. package/docs/guides/accessibility.md +2 -1
  30. package/docs/guides/ai-toolkit.md +166 -0
  31. package/docs/guides/compatibility.md +1 -1
  32. package/docs/guides/migration.md +92 -0
  33. package/docs/guides/minification-and-protection.md +2 -1
  34. package/docs/guides/mobile-and-platforms.md +1 -1
  35. package/docs/guides/online-updates.md +2 -0
  36. package/docs/server/deployment.md +1 -1
  37. package/docs/tools/styling.md +2 -2
  38. package/docs/tools/theme.md +139 -4
  39. package/docs/widget/Accordion.md +74 -8
  40. package/docs/widget/AdMob.md +68 -3
  41. package/docs/widget/AdSense.md +67 -2
  42. package/docs/widget/AlertDialog.md +1 -1
  43. package/docs/widget/AnimatedBox.md +80 -39
  44. package/docs/widget/AnimatedText.md +74 -18
  45. package/docs/widget/Audio.md +73 -14
  46. package/docs/widget/Avatar.md +69 -4
  47. package/docs/widget/Badge.md +68 -3
  48. package/docs/widget/BottomSheet.md +2 -2
  49. package/docs/widget/Button.md +69 -3
  50. package/docs/widget/Card.md +69 -4
  51. package/docs/widget/Carousel.md +67 -2
  52. package/docs/widget/Chart.md +67 -2
  53. package/docs/widget/Checkbox.md +74 -8
  54. package/docs/widget/Chip.md +68 -3
  55. package/docs/widget/CircularBar.md +74 -15
  56. package/docs/widget/CircularChart.md +71 -10
  57. package/docs/widget/CodeViewer.md +67 -2
  58. package/docs/widget/Column.md +68 -3
  59. package/docs/widget/Container.md +68 -3
  60. package/docs/widget/DataTable.md +67 -2
  61. package/docs/widget/Divider.md +67 -2
  62. package/docs/widget/DraggBox.md +67 -2
  63. package/docs/widget/Dropdown.md +76 -10
  64. package/docs/widget/DroppBox.md +67 -2
  65. package/docs/widget/FloatingActionButton.md +68 -3
  66. package/docs/widget/GridView.md +67 -2
  67. package/docs/widget/Icon.md +71 -5
  68. package/docs/widget/Image.md +68 -3
  69. package/docs/widget/Input.md +68 -3
  70. package/docs/widget/Inspector.md +2 -2
  71. package/docs/widget/InstallButton.md +73 -13
  72. package/docs/widget/ListTile.md +69 -3
  73. package/docs/widget/ListView.md +67 -2
  74. package/docs/widget/Maps.md +68 -3
  75. package/docs/widget/Markdown.md +74 -15
  76. package/docs/widget/MatrixRain.md +72 -11
  77. package/docs/widget/Menu.md +209 -0
  78. package/docs/widget/Modal.md +3 -3
  79. package/docs/widget/Pagination.md +68 -3
  80. package/docs/widget/ParallaxBox.md +72 -12
  81. package/docs/widget/ProgressBar.md +69 -4
  82. package/docs/widget/QRCode.md +67 -2
  83. package/docs/widget/README.md +54 -50
  84. package/docs/widget/Radio.md +76 -10
  85. package/docs/widget/Rating.md +70 -6
  86. package/docs/widget/Row.md +68 -3
  87. package/docs/widget/Skeleton.md +68 -3
  88. package/docs/widget/Slider.md +75 -9
  89. package/docs/widget/SnackBar.md +2 -2
  90. package/docs/widget/Stack.md +68 -3
  91. package/docs/widget/Stepper.md +68 -3
  92. package/docs/widget/Switch.md +74 -8
  93. package/docs/widget/Text.md +71 -4
  94. package/docs/widget/ThemePicker.md +197 -0
  95. package/docs/widget/Tooltip.md +72 -7
  96. package/docs/widget/TreeView.md +70 -10
  97. package/docs/widget/Video.md +71 -10
  98. package/package.json +10 -3
  99. package/skills/fletbox-design/SKILL.md +89 -5
  100. package/skills/fletbox-motion/SKILL.md +1 -1
  101. package/skills/fletbox-rules/SKILL.md +56 -4
  102. package/src/core/runApp.js +12 -0
  103. package/src/index.d.ts +374 -101
  104. package/src/index.js +9 -0
  105. package/src/navigations/AdaptiveScaffold.js +2 -2
  106. package/src/navigations/BottomNavigation.js +1 -1
  107. package/src/navigations/Scaffold.js +18 -6
  108. package/src/tools/useState.js +11 -4
  109. package/src/utils/colors.js +275 -0
  110. package/src/utils/errorBoundary.js +288 -0
  111. package/src/utils/index.js +10 -2
  112. package/src/utils/stackPosition.js +39 -24
  113. package/src/utils/syntaxHighlight.js +4 -1
  114. package/src/utils/themes.js +279 -3
  115. package/src/widget-factory/addChildren.js +17 -10
  116. package/src/widget-factory/assignProps.js +18 -6
  117. package/src/widget-factory/createWidget.js +7 -0
  118. package/src/widget-factory/deprecations.js +77 -0
  119. package/src/widget-factory/translateProps.js +11 -1
  120. package/src/widget-factory/warnings.js +1 -27
  121. package/src/widget-factory/widgetFactory.js +59 -17
  122. package/src/widgets/Accordion.js +21 -7
  123. package/src/widgets/Badge.js +11 -2
  124. package/src/widgets/Button.js +15 -3
  125. package/src/widgets/Carousel.js +12 -4
  126. package/src/widgets/Chart.js +7 -2
  127. package/src/widgets/Checkbox.js +1 -2
  128. package/src/widgets/Chip.js +24 -10
  129. package/src/widgets/CircularBar.js +9 -3
  130. package/src/widgets/CircularChart.js +5 -3
  131. package/src/widgets/Container.js +6 -0
  132. package/src/widgets/Divider.js +3 -1
  133. package/src/widgets/DraggBox.js +9 -0
  134. package/src/widgets/Dropdown.js +3 -3
  135. package/src/widgets/DroppBox.js +5 -0
  136. package/src/widgets/FloatingActionButton.js +1 -1
  137. package/src/widgets/Icon.js +5 -1
  138. package/src/widgets/InstallButton.js +4 -2
  139. package/src/widgets/Markdown.js +171 -103
  140. package/src/widgets/Menu.js +340 -0
  141. package/src/widgets/Modal.js +7 -1
  142. package/src/widgets/Pagination.js +10 -5
  143. package/src/widgets/ProgressBar.js +5 -4
  144. package/src/widgets/QRCode.js +4 -0
  145. package/src/widgets/Radio.js +1 -2
  146. package/src/widgets/Rating.js +9 -0
  147. package/src/widgets/Slider.js +4 -1
  148. package/src/widgets/SnackBar.js +2 -2
  149. package/src/widgets/Switch.js +1 -2
  150. package/src/widgets/Text.js +9 -5
  151. package/src/widgets/ThemePicker.js +234 -0
  152. package/src/widgets/TreeView.js +2 -1
  153. package/src/widgets/index.js +2 -0
  154. package/docs/CONTRIBUTING.md +0 -156
@@ -6,7 +6,11 @@
6
6
  import { WidgetFactory } from "../widget-factory/index.js";
7
7
  import { colors } from "../utils/themes.js";
8
8
  import { composeUpdate } from "../utils/composeUpdate.js";
9
- import { generateHighlightedHtml } from "../utils/syntaxHighlight.js";
9
+ import {
10
+ generateHighlightedHtml,
11
+ highlightColors,
12
+ syntaxPaletteFor,
13
+ } from "../utils/syntaxHighlight.js";
10
14
  import { escapeHtml } from "../helpers/html.js";
11
15
  import { toREM } from "../utils/units.js";
12
16
 
@@ -79,12 +83,23 @@ const sanitizeMarkdown = (html) => {
79
83
  // has a single import boundary and the parsers are never pulled into unrelated
80
84
  // dependency graphs.
81
85
 
82
- /** One dark–inspired palette shared by every language highlighter. */
83
- const SH = {
84
- keyword: "#c678dd",
86
+ /**
87
+ * The two ink maps the per-language highlighters paint with, one per code-plate
88
+ * lightness. `muted`, `ink` and `hairline` are the fence header's chrome (the
89
+ * language label, the copy button and its border), which sits on a band that
90
+ * shades the plate by 18 % black — a stricter background than the plate itself.
91
+ *
92
+ * Every value is the ratio it ships, measured on the plate it lands on: the
93
+ * framework's own `colors.gray100` (#fafafa light / #27272a dark) for the tokens
94
+ * and that plate under the header band for the chrome. The dark comment used to
95
+ * be One Dark's `#5c6370` — 2.46:1 on a dark card, and a comment is still a
96
+ * sentence someone has to read.
97
+ */
98
+ const SH_DARK = {
99
+ keyword: "#c678dd", // 5.06:1 on #1b2740, 5.33:1 on #27272a
85
100
  string: "#98c379",
86
101
  number: "#d19a66",
87
- comment: "#5c6370",
102
+ comment: "#8b949e", // 4.85:1 on #1b2740 — the grey GitHub paints comments with
88
103
  tag: "#e06c75",
89
104
  attr: "#d19a66",
90
105
  selector: "#e06c75",
@@ -95,6 +110,46 @@ const SH = {
95
110
  punctuation: "#abb2bf",
96
111
  operator: "#56b6c2",
97
112
  variable: "#e5c07b",
113
+ muted: "#c9d1d9", // 10.2:1 on the header band
114
+ ink: "#ffffff",
115
+ hairline: "rgba(255, 255, 255, 0.15)",
116
+ hairlineHover: "rgba(255, 255, 255, 0.4)",
117
+ };
118
+
119
+ /** The same roles for a light plate — every one is 4.7:1 or better on #eef2f7. */
120
+ const SH_LIGHT = {
121
+ keyword: "#cf222e", // 4.75:1
122
+ string: "#0a3069", // 11.32:1
123
+ number: "#0550ae", // 6.73:1
124
+ comment: "#57606a", // 5.65:1
125
+ tag: "#116329", // 6.53:1
126
+ attr: "#953800", // 6.54:1
127
+ selector: "#116329",
128
+ property: "#0550ae",
129
+ value: "#0a3069",
130
+ function: "#6f42c1", // 5.76:1
131
+ builtin: "#0e7490", // 4.74:1
132
+ punctuation: "#57606a",
133
+ operator: "#cf222e",
134
+ variable: "#953800",
135
+ muted: "#24292f", // 8.6:1 on the header band
136
+ ink: "#0d1117",
137
+ hairline: "rgba(36, 41, 47, 0.25)",
138
+ hairlineHover: "rgba(36, 41, 47, 0.55)",
139
+ };
140
+
141
+ /**
142
+ * The two colour maps one code plate gets: the JS tokenizer's own, and the role
143
+ * map for the other languages. `highlightColors` is `syntaxHighlight.js`'s dark
144
+ * map, so identity with it is the same lightness question the JS branch already
145
+ * answered — one plate, one answer, and the two highlighters cannot disagree
146
+ * about what "a dark code block" means.
147
+ *
148
+ * @param {string} background - The fence's own `preBgColor`.
149
+ */
150
+ const syntaxFor = (background) => {
151
+ const js = syntaxPaletteFor(background);
152
+ return { js, sh: js === highlightColors ? SH_DARK : SH_LIGHT };
98
153
  };
99
154
 
100
155
  /**
@@ -136,99 +191,105 @@ const applyRules = (line, rules) => {
136
191
  };
137
192
 
138
193
  // ── Per-language rule tables ─────────────────────────────────────────────────
194
+ //
195
+ // Patterns are keyed by *role*, never by colour: a regex rebuilt per line throws
196
+ // away the engine's own compilation cache, and a colour baked into the table is
197
+ // exactly how this widget came to read a light page with dark-theme ink.
198
+ const PATTERNS = {
199
+ python: [
200
+ [/(#.*)$/, "comment"],
201
+ [/("""[\s\S]*?"""|'''[\s\S]*?'''|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/, "string"],
202
+ [/\b(def|class|if|elif|else|for|while|try|except|finally|with|as|import|from|return|yield|pass|break|continue|raise|lambda|not|and|or|in|is|None|True|False|async|await|global|nonlocal|del)\b/, "keyword"],
203
+ [/\b(print|len|range|type|str|int|float|bool|list|dict|tuple|set|enumerate|zip|map|filter|sorted|reversed|open|super|self|cls)\b/, "builtin"],
204
+ [/\b([0-9]+(?:\.[0-9]+)?)\b/, "number"],
205
+ [/([+\-*/=<>!&|^~%]+)/, "operator"],
206
+ ],
207
+ css: [
208
+ [/(\/\*[\s\S]*?\*\/)/, "comment"],
209
+ [/("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/, "string"],
210
+ [/(#[0-9a-fA-F]{3,8})\b/, "value"],
211
+ [/\b([0-9]+(?:\.[0-9]+)?(?:px|em|rem|%|vh|vw|vmin|vmax|pt|cm|mm|deg|s|ms)?)\b/, "number"],
212
+ [/(:[a-zA-Z-]+\(|:[a-zA-Z-]+\b|::[a-zA-Z-]+)/, "selector"],
213
+ [/([.#]?[a-zA-Z][a-zA-Z0-9_-]*)(?=\s*\{)/, "selector"],
214
+ [/\b(animation|background|border|color|display|flex|font|gap|grid|height|justify|margin|max|min|opacity|overflow|padding|position|text|transform|transition|width|z-index|align|content|cursor|float|outline|pointer|shadow|top|bottom|left|right)\b/, "property"],
215
+ [/\b(inherit|initial|none|auto|normal|bold|italic|relative|absolute|fixed|sticky|block|inline|flex|grid|center|left|right|top|bottom|middle|start|end|solid|dashed|dotted|transparent|var)\b/, "keyword"],
216
+ [/([{}:;,])/, "punctuation"],
217
+ ],
218
+ html: [
219
+ [/(<!--[\s\S]*?-->)/, "comment"],
220
+ [/("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/, "string"],
221
+ [/(<\/?)([a-zA-Z][a-zA-Z0-9-]*)/, "tag"], // tag name
222
+ [/\b([a-zA-Z:][a-zA-Z0-9_:-]*)(?=\s*=)/, "attr"], // attribute name
223
+ [/([<>/=])/, "punctuation"],
224
+ ],
225
+ bash: [
226
+ [/(#.*)$/, "comment"],
227
+ [/("(?:[^"\\$]|\\.|\$[^{(]|\$\{[^}]*\}|\$\([^)]*\))*"|'[^']*')/, "string"],
228
+ [/\b(if|then|else|elif|fi|for|while|do|done|case|esac|in|function|return|exit|local|export|readonly|declare|source|echo|cd|ls|mkdir|rm|mv|cp|cat|grep|sed|awk|find|curl|wget|git|npm|node|python|pip|sudo|chmod|chown|eval|exec|trap|set|unset|shift|read|printf|test)\b/, "keyword"],
229
+ [/(\$\{[^}]*\}|\$[A-Za-z_][A-Za-z0-9_]*|\$[0-9@#*!?$-])/, "variable"],
230
+ [/\b([0-9]+)\b/, "number"],
231
+ [/([|&;<>(){}[\]])/, "operator"],
232
+ ],
233
+ json: [
234
+ [/("(?:[^"\\]|\\.)*")(?=\s*:)/, "property"],
235
+ [/("(?:[^"\\]|\\.)*")/, "string"],
236
+ [/\b(true|false|null)\b/, "keyword"],
237
+ [/(-?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?)/, "number"],
238
+ [/([{}[\]:,])/, "punctuation"],
239
+ ],
240
+ };
139
241
 
140
- const RULES_PYTHON = [
141
- [/(#.*)$/, SH.comment],
142
- [/("""[\s\S]*?"""|'''[\s\S]*?'''|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/, SH.string],
143
- [/\b(def|class|if|elif|else|for|while|try|except|finally|with|as|import|from|return|yield|pass|break|continue|raise|lambda|not|and|or|in|is|None|True|False|async|await|global|nonlocal|del)\b/, SH.keyword],
144
- [/\b(print|len|range|type|str|int|float|bool|list|dict|tuple|set|enumerate|zip|map|filter|sorted|reversed|open|super|self|cls)\b/, SH.builtin],
145
- [/\b([0-9]+(?:\.[0-9]+)?)\b/, SH.number],
146
- [/([+\-*/=<>!&|^~%]+)/, SH.operator],
147
- ];
148
-
149
- const RULES_CSS = [
150
- [/(\/\*[\s\S]*?\*\/)/, SH.comment],
151
- [/("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/, SH.string],
152
- [/(#[0-9a-fA-F]{3,8})\b/, SH.value],
153
- [/\b([0-9]+(?:\.[0-9]+)?(?:px|em|rem|%|vh|vw|vmin|vmax|pt|cm|mm|deg|s|ms)?)\b/, SH.number],
154
- [/(:[a-zA-Z-]+\(|:[a-zA-Z-]+\b|::[a-zA-Z-]+)/, SH.selector],
155
- [/([.#]?[a-zA-Z][a-zA-Z0-9_-]*)(?=\s*\{)/, SH.selector],
156
- [/\b(animation|background|border|color|display|flex|font|gap|grid|height|justify|margin|max|min|opacity|overflow|padding|position|text|transform|transition|width|z-index|align|content|cursor|float|outline|pointer|shadow|top|bottom|left|right)\b/, SH.property],
157
- [/\b(inherit|initial|none|auto|normal|bold|italic|relative|absolute|fixed|sticky|block|inline|flex|grid|center|left|right|top|bottom|middle|start|end|solid|dashed|dotted|transparent|var)\b/, SH.keyword],
158
- [/([{}:;,])/, SH.punctuation],
159
- ];
160
-
161
- const RULES_HTML = [
162
- [/(<!--[\s\S]*?-->)/, SH.comment],
163
- [/("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/, SH.string],
164
- [/(<\/?)([a-zA-Z][a-zA-Z0-9-]*)/, SH.tag], // tag name
165
- [/\b([a-zA-Z:][a-zA-Z0-9_:-]*)(?=\s*=)/, SH.attr], // attribute name
166
- [/([<>/=])/, SH.punctuation],
167
- ];
168
-
169
- const RULES_BASH = [
170
- [/(#.*)$/, SH.comment],
171
- [/("(?:[^"\\$]|\\.|\$[^{(]|\$\{[^}]*\}|\$\([^)]*\))*"|'[^']*')/, SH.string],
172
- [/\b(if|then|else|elif|fi|for|while|do|done|case|esac|in|function|return|exit|local|export|readonly|declare|source|echo|cd|ls|mkdir|rm|mv|cp|cat|grep|sed|awk|find|curl|wget|git|npm|node|python|pip|sudo|chmod|chown|eval|exec|trap|set|unset|shift|read|printf|test)\b/, SH.keyword],
173
- [/(\$\{[^}]*\}|\$[A-Za-z_][A-Za-z0-9_]*|\$[0-9@#*!?$-])/, SH.variable],
174
- [/\b([0-9]+)\b/, SH.number],
175
- [/([|&;<>(){}[\]])/, SH.operator],
176
- ];
177
-
178
- const RULES_JSON = [
179
- [/("(?:[^"\\]|\\.)*")(?=\s*:)/, SH.property],
180
- [/("(?:[^"\\]|\\.)*")/, SH.string],
181
- [/\b(true|false|null)\b/, SH.keyword],
182
- [/(-?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?)/, SH.number],
183
- [/([{}[\]:,])/, SH.punctuation],
184
- ];
242
+ /** @type {Map<object, Record<string, Array<[RegExp, string]>>>} Coloured tables, one per palette. */
243
+ const ruleTables = new Map();
185
244
 
186
245
  /**
187
- * Highlights one source line using the appropriate language highlighter.
188
- * Falls through to plain HTML-escaped text for unknown languages.
246
+ * The `[pattern, colour]` rules for one language on one code plate, built once
247
+ * per palette so the regexes themselves are never recompiled.
189
248
  *
190
- * @param {string} line - One line of source code (no trailing newline).
191
- * @param {string} lang - Normalised language identifier (lower-case).
192
- * @returns {string} HTML fragment.
249
+ * @param {string} lang - Normalised language identifier (lower-case).
250
+ * @param {Record<string, string>} sh - The role map the plate chose.
251
+ * @returns {Array<[RegExp, string]> | null} `null` for a language with no rules.
193
252
  */
194
- const highlightLine = (line, lang) => {
195
- switch (lang) {
196
- case "js":
197
- case "jsx":
198
- case "javascript":
199
- case "ts":
200
- case "tsx":
201
- case "typescript":
202
- // Delegate to the existing FletBox tokenizer for JS/TS.
203
- return generateHighlightedHtml(line);
204
-
205
- case "py":
206
- case "python":
207
- return applyRules(line, RULES_PYTHON);
208
-
209
- case "css":
210
- case "scss":
211
- case "less":
212
- return applyRules(line, RULES_CSS);
213
-
214
- case "html":
215
- case "xml":
216
- case "svg":
217
- return applyRules(line, RULES_HTML);
218
-
219
- case "sh":
220
- case "bash":
221
- case "shell":
222
- case "zsh":
223
- return applyRules(line, RULES_BASH);
224
-
225
- case "json":
226
- case "jsonc":
227
- return applyRules(line, RULES_JSON);
228
-
229
- default:
230
- return escapeHtml(line);
253
+ const rulesFor = (lang, sh) => {
254
+ if (!PATTERNS[lang]) return null;
255
+ let table = ruleTables.get(sh);
256
+ if (!table) {
257
+ table = {};
258
+ for (const [name, rows] of Object.entries(PATTERNS)) {
259
+ table[name] = rows.map(([pattern, role]) => [pattern, sh[role]]);
260
+ }
261
+ ruleTables.set(sh, table);
231
262
  }
263
+ return table[lang];
264
+ };
265
+
266
+
267
+ /** Language aliases a fence's info string may use, mapped to a `PATTERNS` key. */
268
+ const LANGUAGES = {
269
+ py: "python", python: "python",
270
+ css: "css", scss: "css", less: "css",
271
+ html: "html", xml: "html", svg: "html",
272
+ sh: "bash", bash: "bash", shell: "bash", zsh: "bash",
273
+ json: "json", jsonc: "json",
274
+ };
275
+
276
+ /** The languages the shared tokenizer owns; everything else uses `PATTERNS`. */
277
+ const TOKENISED = new Set(["js", "jsx", "javascript", "ts", "tsx", "typescript"]);
278
+
279
+ /**
280
+ * Highlights one source line with the highlighter that speaks its language,
281
+ * using the colours the code plate asked for. Unknown languages are escaped and
282
+ * left plain: a wrong guess is worse than no highlighting.
283
+ *
284
+ * @param {string} line - One line of source code (no trailing newline).
285
+ * @param {string} lang - Normalised language identifier (lower-case).
286
+ * @param {{js: object, sh: object}} syntax - The plate's two colour maps.
287
+ * @returns {string} HTML fragment.
288
+ */
289
+ const highlightLine = (line, lang, syntax) => {
290
+ if (TOKENISED.has(lang)) return generateHighlightedHtml(line, syntax.js);
291
+ const key = LANGUAGES[lang];
292
+ return key ? applyRules(line, rulesFor(key, syntax.sh)) : escapeHtml(line);
232
293
  };
233
294
 
234
295
  /**
@@ -275,9 +336,10 @@ const legacyCopy = (text) => {
275
336
  * content node after mount (see the Markdown body).
276
337
  *
277
338
  * @param {{ lang: string, code: string }} block
339
+ * @param {{js: object, sh: object}} syntax - The plate's two colour maps.
278
340
  * @returns {string} HTML fragment.
279
341
  */
280
- const renderFencedBlock = (block) => {
342
+ const renderFencedBlock = (block, syntax) => {
281
343
  const lang = block.lang.replace(/[^A-Za-z0-9#+._-]/g, "").toLowerCase();
282
344
  const cls = lang ? ` class="language-${lang}"` : "";
283
345
  const label = lang ? `<span class="md-lang-label">${escapeHtml(lang)}</span>` : "";
@@ -287,7 +349,7 @@ const renderFencedBlock = (block) => {
287
349
  // doubling the visual gap regardless of line-height.
288
350
  const lines = block.code.split("\n");
289
351
  const highlighted = lines
290
- .map((line) => highlightLine(line, lang))
352
+ .map((line) => highlightLine(line, lang, syntax))
291
353
  .join("\n");
292
354
 
293
355
  // The raw code is embedded in a hidden <textarea> so the copy handler can
@@ -541,10 +603,11 @@ const extractRawHtml = (text) => {
541
603
  * replacing this with a battle-tested library such as `marked` or `micromark`.
542
604
  *
543
605
  * @param {string} text - Raw Markdown string to convert.
606
+ * @param {{js: object, sh: object}} syntax - Colours for the fences inside it.
544
607
  * @returns {string} HTML string. Output may contain tags but no `<script>` or event handlers
545
608
  * unless `allowDangerousHtml` is explicitly set on the widget.
546
609
  */
547
- const parseMarkdown = (text) => {
610
+ const parseMarkdown = (text, syntax) => {
548
611
  if (!text) return "";
549
612
 
550
613
  let html = text.replace(/\r\n?/g, "\n");
@@ -668,7 +731,7 @@ const parseMarkdown = (text) => {
668
731
  (_match, rawIndex) => {
669
732
  const block = blocks[Number(rawIndex)];
670
733
  if (!block) return "";
671
- return renderFencedBlock(block);
734
+ return renderFencedBlock(block, syntax);
672
735
  },
673
736
  );
674
737
 
@@ -861,8 +924,13 @@ export const Markdown = (props) => {
861
924
  // so the last mounted widget's style props leaked into all the others.
862
925
  const scope = `markdown-scope-${++markdownScopeUid}`;
863
926
 
927
+ // The fence colours are chosen from the plate the app actually paints, not
928
+ // from the reader's theme: `preBgColor` is a public prop, so a light palette
929
+ // can hand its code blocks a dark plate and vice versa.
930
+ const syntax = syntaxFor(preBgColor);
931
+
864
932
  // Parse Markdown to raw HTML
865
- const rawHtml = parseMarkdown(markdownText);
933
+ const rawHtml = parseMarkdown(markdownText, syntax);
866
934
 
867
935
  // Outer container
868
936
  const container = WidgetFactory({
@@ -1108,7 +1176,7 @@ export const Markdown = (props) => {
1108
1176
  .markdown-content .md-lang-label {
1109
1177
  font-family: ${codeFontFamily};
1110
1178
  font-size: 0.75em;
1111
- color: ${SH.comment};
1179
+ color: ${syntax.sh.muted};
1112
1180
  text-transform: uppercase;
1113
1181
  letter-spacing: 0.06em;
1114
1182
  user-select: none;
@@ -1116,9 +1184,9 @@ export const Markdown = (props) => {
1116
1184
 
1117
1185
  .markdown-content .md-copy-btn {
1118
1186
  background: none;
1119
- border: 1px solid rgba(255,255,255,0.15);
1187
+ border: 1px solid ${syntax.sh.hairline};
1120
1188
  border-radius: 4px;
1121
- color: ${SH.comment};
1189
+ color: ${syntax.sh.muted};
1122
1190
  cursor: pointer;
1123
1191
  font-size: 0.72em;
1124
1192
  padding: 0.15em 0.55em;
@@ -1127,8 +1195,8 @@ export const Markdown = (props) => {
1127
1195
  font-family: ${codeFontFamily};
1128
1196
  }
1129
1197
  .markdown-content .md-copy-btn:hover {
1130
- color: #fff;
1131
- border-color: rgba(255,255,255,0.4);
1198
+ color: ${syntax.sh.ink};
1199
+ border-color: ${syntax.sh.hairlineHover};
1132
1200
  }
1133
1201
 
1134
1202
  /* Hidden textarea holding the raw source for the copy handler */
@@ -1235,7 +1303,7 @@ export const Markdown = (props) => {
1235
1303
  * @param {string} newText - The updated Markdown string.
1236
1304
  */
1237
1305
  container.updateContent = (newText) => {
1238
- const newHtml = parseMarkdown(newText);
1306
+ const newHtml = parseMarkdown(newText, syntax);
1239
1307
  contentDiv.innerHTML = allowDangerousHtml
1240
1308
  ? newHtml
1241
1309
  : sanitizeMarkdown(newHtml);