@soroush.tech/markdown 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +179 -0
  3. package/dist/CodeBlock-CVlrvVPP.mjs +159 -0
  4. package/dist/CodeBlock-Cymx1ZAY.cjs +176 -0
  5. package/dist/CodeBlock.cjs +5 -0
  6. package/dist/CodeBlock.d.cts +2 -0
  7. package/dist/CodeBlock.d.mts +2 -0
  8. package/dist/CodeBlock.mjs +2 -0
  9. package/dist/Control.cjs +61 -0
  10. package/dist/Control.d.cts +18 -0
  11. package/dist/Control.d.mts +18 -0
  12. package/dist/Control.mjs +60 -0
  13. package/dist/Editor.cjs +129 -0
  14. package/dist/Editor.d.cts +40 -0
  15. package/dist/Editor.d.mts +40 -0
  16. package/dist/Editor.mjs +128 -0
  17. package/dist/MarkdownContext.cjs +13 -0
  18. package/dist/MarkdownContext.d.cts +2 -0
  19. package/dist/MarkdownContext.d.mts +2 -0
  20. package/dist/MarkdownContext.mjs +11 -0
  21. package/dist/Mermaid-BclI9A7e.mjs +231 -0
  22. package/dist/Mermaid-DqP2Q0bt.cjs +236 -0
  23. package/dist/Mermaid.cjs +3 -0
  24. package/dist/Mermaid.d.cts +2 -0
  25. package/dist/Mermaid.d.mts +2 -0
  26. package/dist/Mermaid.mjs +2 -0
  27. package/dist/Preview-BVT70uXa.cjs +276 -0
  28. package/dist/Preview-C1CwRDj5.mjs +246 -0
  29. package/dist/Preview.cjs +3 -0
  30. package/dist/Preview.d.cts +53 -0
  31. package/dist/Preview.d.mts +53 -0
  32. package/dist/Preview.mjs +2 -0
  33. package/dist/Toolbar-Ba3l-oBU.cjs +600 -0
  34. package/dist/Toolbar-C0SNI-JK.mjs +589 -0
  35. package/dist/Toolbar.cjs +4 -0
  36. package/dist/Toolbar.d.cts +13 -0
  37. package/dist/Toolbar.d.mts +13 -0
  38. package/dist/Toolbar.mjs +2 -0
  39. package/dist/applyAction-BDkgYhOd.cjs +120 -0
  40. package/dist/applyAction-BGWMbUYT.mjs +115 -0
  41. package/dist/index-BO0LyUUG.d.cts +27 -0
  42. package/dist/index-BO0LyUUG.d.mts +27 -0
  43. package/dist/index-BtMTBOcM.d.cts +48 -0
  44. package/dist/index-BtMTBOcM.d.mts +48 -0
  45. package/dist/index-rGZIhUgj.d.cts +76 -0
  46. package/dist/index-rGZIhUgj.d.mts +76 -0
  47. package/dist/index.cjs +19 -0
  48. package/dist/index.d.cts +50 -0
  49. package/dist/index.d.mts +50 -0
  50. package/dist/index.mjs +8 -0
  51. package/package.json +110 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Masoud Soroush <https://soroush.tech>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,179 @@
1
+ [![npm version](https://img.shields.io/npm/v/@soroush.tech/markdown.svg)](https://www.npmjs.com/package/@soroush.tech/markdown)
2
+ [![npm downloads](https://img.shields.io/npm/dm/@soroush.tech/markdown.svg)](https://www.npmjs.com/package/@soroush.tech/markdown)
3
+ [![coverage](https://codecov.io/gh/soroush-tech/core/branch/main/graph/badge.svg?flag=markdown)](https://app.codecov.io/gh/soroush-tech/core?flags%5B0%5D=markdown)
4
+ [![unpacked size](https://img.shields.io/npm/unpacked-size/@soroush.tech/markdown.svg)](https://www.npmjs.com/package/@soroush.tech/markdown)
5
+ [![types included](https://img.shields.io/npm/types/@soroush.tech/markdown.svg)](https://www.npmjs.com/package/@soroush.tech/markdown)
6
+ [![license](https://img.shields.io/npm/l/@soroush.tech/markdown.svg)](./LICENSE)
7
+
8
+ ## A **headless markdown editor and preview**.
9
+
10
+ Compose a toolbar, a source editor, and a live preview however your UI needs — and every element is styled by your theme. The renderer works standalone too, turning any markdown string into themed components, with support for tables, task lists, strikethrough, autolinks, syntax-highlighted code, and mermaid diagrams.
11
+
12
+ ![The Preview renderer output — a heading, a formatted paragraph, a task list, a blockquote, a code block, a table, and a mermaid sequence diagram, all as themed components](https://raw.githubusercontent.com/soroush-tech/core/main/packages/markdown/src/assets/demo.png)
13
+
14
+ ## Why headless?
15
+
16
+ Most markdown editors hand you a fixed widget — a bordered box with a toolbar bolted on top and an Edit/Preview toggle you can't move. This one gives you the **pieces** instead:
17
+
18
+ - **You own the layout.** Put the preview beside the editor, below it, in a modal, or drop it entirely — the parts don't care.
19
+ - **You own the state.** The value is yours (`useState`, a form, a store); the editor just reads and writes it.
20
+ - **It looks like your app.** Output is themed components — `Typography`, `Link`, `Table`, and its own `CodeBlock` — not unstyled HTML, so it inherits your theme with zero extra CSS.
21
+
22
+ ## Features
23
+
24
+ - **GitHub-Flavored Markdown** — tables, task lists, strikethrough, and autolinks.
25
+ - **Syntax-highlighted code** — fenced blocks are highlighted automatically; unknown languages degrade gracefully.
26
+ - **Mermaid diagrams** — a ` ```mermaid ` fence renders as a themed diagram with built-in zoom and pan controls, loaded lazily on the client and falling back to the source until it does.
27
+ - **A real editing surface** — Tab-to-indent, selection tracking, caret restoration, formatting buttons, heading and code-language selects, a hover-grid table picker, and a diagram picker that inserts starter templates for 23 mermaid diagram types.
28
+ - **Composable parts** — `Control`, `Toolbar`, `Editor`, and `Preview`, coordinated through React context.
29
+ - **Themeable to the slot** — restyle the editor, toolbar, and preview through your theme, no forking required.
30
+ - **Typed and SSR-safe** — first-class TypeScript, 100% test coverage, no browser globals at module scope.
31
+
32
+ ## Install
33
+
34
+ ```sh
35
+ # npm
36
+ npm install @soroush.tech/markdown @soroush.tech/design-system
37
+ ```
38
+
39
+ ```sh
40
+ # pnpm
41
+ pnpm add @soroush.tech/markdown @soroush.tech/design-system
42
+ ```
43
+
44
+ ```sh
45
+ # yarn
46
+ yarn add @soroush.tech/markdown @soroush.tech/design-system
47
+ ```
48
+
49
+ > **Requires the design system.** This package renders through `@soroush.tech/design-system`, so its components must live inside that library's `ThemeProvider`. If you aren't already using the design system, [start there first](https://www.npmjs.com/package/@soroush.tech/design-system).
50
+
51
+ ## Quick start
52
+
53
+ Wrap your app in the design system's `ThemeProvider` once, then render markdown anywhere inside it:
54
+
55
+ ```tsx
56
+ import { ThemeProvider } from '@soroush.tech/design-system/theme'
57
+ import { Preview } from '@soroush.tech/markdown'
58
+
59
+ export function App() {
60
+ return (
61
+ <ThemeProvider>
62
+ <Preview>
63
+ {
64
+ '# Hello\n\nRendered through your **theme** — tables, `code`, and [links](https://soroush.tech) included.'
65
+ }
66
+ </Preview>
67
+ </ThemeProvider>
68
+ )
69
+ }
70
+ ```
71
+
72
+ That's the whole renderer. To let people _write_ markdown, add the editing parts.
73
+
74
+ ## Editor
75
+
76
+ The `Editor` works standalone via `value`/`onChange` — no `Control` needed:
77
+
78
+ ```tsx
79
+ import { useState } from 'react'
80
+ import { Editor } from '@soroush.tech/markdown'
81
+ import { ThemeProvider } from '@soroush.tech/design-system/theme'
82
+
83
+ function MarkdownField() {
84
+ const [value, setValue] = useState('# Start writing\n\nJust the source editor — no preview.')
85
+
86
+ return (
87
+ <ThemeProvider>
88
+ <Editor value={value} onChange={setValue} />
89
+ </ThemeProvider>
90
+ )
91
+ }
92
+ ```
93
+
94
+ ## A full editor and preview in a split screen
95
+
96
+ Everything inside `Control` shares one value and selection, so you arrange the panes freely. Here's a toolbar above a side-by-side source and live preview:
97
+
98
+ ```tsx
99
+ import { useState } from 'react'
100
+ import { Control, Toolbar, Editor, Preview } from '@soroush.tech/markdown'
101
+ import { Flex } from '@soroush.tech/design-system/Flex'
102
+
103
+ function MarkdownField() {
104
+ const [value, setValue] = useState(
105
+ '# Start writing\n\nType on the left, watch it render on the right. →'
106
+ )
107
+
108
+ return (
109
+ <Control value={value} onChange={setValue}>
110
+ <Toolbar />
111
+ <Flex gap={4}>
112
+ <Editor />
113
+ <Preview>{value}</Preview>
114
+ </Flex>
115
+ </Control>
116
+ )
117
+ }
118
+ ```
119
+
120
+ There is intentionally **no Edit·Split·Preview switch** — which panes to show, and when, is your call.
121
+
122
+ ## The parts
123
+
124
+ | Part | Role |
125
+ | --------- | --------------------------------------------------------------------------------- |
126
+ | `Control` | Headless provider — owns the `value`/selection state. Takes `value` + `onChange`. |
127
+ | `Toolbar` | Formatting buttons, heading/code-language selects, and the table picker. |
128
+ | `Editor` | The source `<textarea>` — Tab-to-indent, selection, caret restore. |
129
+ | `Preview` | Renders a markdown string (its child) to themed components. |
130
+
131
+ `Toolbar` and `Editor` must be rendered inside `Control`; `Preview` works anywhere. Import each directly from the package — there is no `Markdown.*` namespace. Full prop references live in the [source on GitHub](https://github.com/soroush-tech/core/tree/main/packages/markdown/src).
132
+
133
+ ## Customizing the look
134
+
135
+ Everything is styled through the design system's theme, and there are two levels to reach for — a shared theme for the whole look, and per-component slots for the markdown parts specifically.
136
+
137
+ ### Customize the theme — restyle everything
138
+
139
+ `Preview` renders design-system components that read theme tokens, so your theme already governs how markdown looks. Build one with `createTheme` — palette, spacing, type scale, radii — and pass it to `ThemeProvider`; the markdown output changes along with the rest of your UI, no markdown-specific setup. See the [design system's theming guide](https://www.npmjs.com/package/@soroush.tech/design-system) for the full token set.
140
+
141
+ ```tsx
142
+ import { ThemeProvider, createTheme, baseTheme } from '@soroush.tech/design-system/theme'
143
+
144
+ const theme = createTheme(baseTheme, {
145
+ palette: {
146
+ primary: {
147
+ main: '#7C3AED',
148
+ light: '#A78BFA',
149
+ dark: '#5B21B6',
150
+ contrastText: '#FFFFFF',
151
+ },
152
+ },
153
+ })
154
+
155
+ // <ThemeProvider theme={theme}> … </ThemeProvider>
156
+ ```
157
+
158
+ ### Customize the components — target the markdown parts
159
+
160
+ For tweaks specific to the editor, toolbar, or preview, this package registers three themeable slots under `theme.components` — `MarkdownEditor`, `MarkdownPreview`, and `MarkdownToolbar`:
161
+
162
+ ```tsx
163
+ const theme = createTheme(baseTheme, {
164
+ components: {
165
+ MarkdownPreview: { styleOverrides: { root: { maxWidth: '72ch' } } },
166
+ MarkdownToolbar: { styleOverrides: { root: { gap: 4 } } },
167
+ },
168
+ })
169
+ ```
170
+
171
+ The overrides are fully typed — importing the package registers the slots on the theme automatically.
172
+
173
+ ## Building your own parts
174
+
175
+ Need a custom control or a word count? `useMarkdownContext()` exposes the same value, selection, and `dispatch` the built-in parts use — call it from any component inside `Control`.
176
+
177
+ ## License
178
+
179
+ [MIT](./LICENSE) © [Soroush Tech](https://soroush.tech)
@@ -0,0 +1,159 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Button } from "@soroush.tech/design-system/Button";
4
+ import { Icon } from "@soroush.tech/design-system/Icon";
5
+ import { styled } from "@soroush.tech/design-system";
6
+ import { View } from "@soroush.tech/design-system/View";
7
+ //#region ../hooks/src/useCopyToClipboard.ts
8
+ /** How long the "copied" confirmation stays true before reverting, in ms. */
9
+ const COPIED_RESET_MS = 2e3;
10
+ /**
11
+ * Copy-to-clipboard with a transient `copied` flag. Guards the unavailable
12
+ * clipboard API and rejected writes so the caller never throws or gets stuck.
13
+ */
14
+ function useCopyToClipboard(resetMs = COPIED_RESET_MS) {
15
+ const [copied, setCopied] = useState(false);
16
+ const timerRef = useRef(null);
17
+ useEffect(() => () => {
18
+ if (timerRef.current) clearTimeout(timerRef.current);
19
+ }, []);
20
+ return {
21
+ copied,
22
+ copy: useCallback((text) => {
23
+ if (!navigator.clipboard?.writeText) return;
24
+ navigator.clipboard.writeText(text).then(() => {
25
+ if (timerRef.current) clearTimeout(timerRef.current);
26
+ setCopied(true);
27
+ timerRef.current = setTimeout(() => {
28
+ setCopied(false);
29
+ timerRef.current = null;
30
+ }, resetMs);
31
+ }).catch(() => {
32
+ console.error("Clipboard write failed; leave `copied` in its idle state.");
33
+ });
34
+ }, [resetMs])
35
+ };
36
+ }
37
+ //#endregion
38
+ //#region src/CodeBlock/CodeBlock.tsx
39
+ const syntaxStyles = ({ theme }) => ({
40
+ color: theme.syntax.base,
41
+ fontFamily: theme.syntax.font,
42
+ "& .hljs-comment, & .hljs-quote, & .hljs-doctag, & .hljs-meta.prompt_": {
43
+ color: theme.syntax.comment,
44
+ fontStyle: "italic"
45
+ },
46
+ "& .hljs-keyword, & .hljs-built_in, & .hljs-literal, & .hljs-selector-tag, & .hljs-selector-pseudo, & .hljs-selector-attr": { color: theme.syntax.keyword },
47
+ "& .hljs-type, & .hljs-title.class_": { color: theme.syntax.type },
48
+ "& .hljs-string, & .hljs-regexp, & .hljs-char, & .hljs-meta .hljs-string, & .hljs-addition": { color: theme.syntax.string },
49
+ "& .hljs-number, & .hljs-symbol, & .hljs-bullet, & .hljs-link": { color: theme.syntax.number },
50
+ "& .hljs-variable.constant_, & .hljs-property": { color: theme.syntax.constant },
51
+ "& .hljs-title, & .hljs-section, & .hljs-attr, & .hljs-attribute, & .hljs-selector-id": { color: theme.syntax.title },
52
+ "& .hljs-tag, & .hljs-name, & .hljs-meta, & .hljs-variable, & .hljs-selector-class, & .hljs-deletion": { color: theme.syntax.tag },
53
+ "& .hljs-emphasis": { fontStyle: "italic" },
54
+ "& .hljs-strong": { fontWeight: "bold" }
55
+ });
56
+ const CodeSurface = styled(View, {
57
+ name: "CodeBlock",
58
+ slot: "surface",
59
+ label: "CodeSurface"
60
+ })(syntaxStyles);
61
+ const Wrapper = styled(View, {
62
+ name: "CodeBlock",
63
+ label: "CodeBlock"
64
+ })({
65
+ display: "grid",
66
+ gridTemplateColumns: "minmax(0, 1fr)",
67
+ "& .code-copy": {
68
+ opacity: 0,
69
+ transition: "opacity 120ms ease"
70
+ },
71
+ "&:hover .code-copy, &:focus-within .code-copy": { opacity: 1 },
72
+ "@media (hover: none)": { "& .code-copy": { opacity: 1 } }
73
+ });
74
+ /**
75
+ * Fenced-code block: a horizontally scrollable, syntax-highlighted surface with a
76
+ * copy-to-clipboard button that sticks to the top-right while the block is in view.
77
+ */
78
+ function CodeBlock({ children, ...rest }) {
79
+ const surfaceRef = useRef(null);
80
+ const { copied, copy } = useCopyToClipboard();
81
+ const handleCopy = () => {
82
+ const raw = surfaceRef.current.textContent;
83
+ let end = raw.length;
84
+ while (end > 0 && raw[end - 1] === "\n") end--;
85
+ const text = raw.slice(0, end);
86
+ if (text) copy(text);
87
+ };
88
+ return /* @__PURE__ */ jsxs(Wrapper, {
89
+ my: 2,
90
+ position: "relative",
91
+ ...rest,
92
+ children: [/* @__PURE__ */ jsx(View, {
93
+ position: "absolute",
94
+ top: "8px",
95
+ right: "8px",
96
+ zIndex: 1,
97
+ bg: "paper",
98
+ borderRadius: "sm",
99
+ className: "code-copy",
100
+ children: /* @__PURE__ */ jsx(Button, {
101
+ type: "button",
102
+ variant: "outlined",
103
+ color: "default",
104
+ size: "sm",
105
+ borderRadius: "sm",
106
+ onClick: handleCopy,
107
+ "aria-label": copied ? "Copied" : "Copy code",
108
+ children: /* @__PURE__ */ jsx(Icon, {
109
+ name: copied ? "check" : "content_copy",
110
+ color: copied ? "success" : "primary",
111
+ size: "1.1rem"
112
+ })
113
+ })
114
+ }), /* @__PURE__ */ jsx(CodeSurface, {
115
+ as: "pre",
116
+ tabIndex: 0,
117
+ ref: surfaceRef,
118
+ bg: "terminal",
119
+ p: 3,
120
+ borderRadius: "md",
121
+ borderColor: "light",
122
+ borderWidth: "thin",
123
+ maxWidth: "100%",
124
+ overflow: "auto",
125
+ children
126
+ })]
127
+ });
128
+ }
129
+ //#endregion
130
+ //#region src/CodeBlock/CodeBlock.data.ts
131
+ const font = "'JetBrains Mono Variable', 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace";
132
+ /** Matched to the JetBrains "Islands Dark" editor scheme. */
133
+ const syntaxDark = {
134
+ base: "#BBBBBB",
135
+ keyword: "#CF8E6D",
136
+ string: "#6AAB73",
137
+ number: "#2AACB8",
138
+ title: "#6BA9FA",
139
+ constant: "#C77DBB",
140
+ type: "#2AACB8",
141
+ comment: "#919191",
142
+ tag: "#D5B778",
143
+ font
144
+ };
145
+ /** Matched to the JetBrains "Islands Light" editor scheme. */
146
+ const syntaxLight = {
147
+ base: "#080808",
148
+ keyword: "#0033B3",
149
+ string: "#067D17",
150
+ number: "#1750EB",
151
+ title: "#00627A",
152
+ constant: "#871094",
153
+ type: "#007E8A",
154
+ comment: "#8C8C8C",
155
+ tag: "#008077",
156
+ font
157
+ };
158
+ //#endregion
159
+ export { syntaxLight as n, CodeBlock as r, syntaxDark as t };
@@ -0,0 +1,176 @@
1
+ let react = require("react");
2
+ let react_jsx_runtime = require("react/jsx-runtime");
3
+ let _soroush_tech_design_system_Button = require("@soroush.tech/design-system/Button");
4
+ let _soroush_tech_design_system_Icon = require("@soroush.tech/design-system/Icon");
5
+ let _soroush_tech_design_system = require("@soroush.tech/design-system");
6
+ let _soroush_tech_design_system_View = require("@soroush.tech/design-system/View");
7
+ //#region ../hooks/src/useCopyToClipboard.ts
8
+ /** How long the "copied" confirmation stays true before reverting, in ms. */
9
+ const COPIED_RESET_MS = 2e3;
10
+ /**
11
+ * Copy-to-clipboard with a transient `copied` flag. Guards the unavailable
12
+ * clipboard API and rejected writes so the caller never throws or gets stuck.
13
+ */
14
+ function useCopyToClipboard(resetMs = COPIED_RESET_MS) {
15
+ const [copied, setCopied] = (0, react.useState)(false);
16
+ const timerRef = (0, react.useRef)(null);
17
+ (0, react.useEffect)(() => () => {
18
+ if (timerRef.current) clearTimeout(timerRef.current);
19
+ }, []);
20
+ return {
21
+ copied,
22
+ copy: (0, react.useCallback)((text) => {
23
+ if (!navigator.clipboard?.writeText) return;
24
+ navigator.clipboard.writeText(text).then(() => {
25
+ if (timerRef.current) clearTimeout(timerRef.current);
26
+ setCopied(true);
27
+ timerRef.current = setTimeout(() => {
28
+ setCopied(false);
29
+ timerRef.current = null;
30
+ }, resetMs);
31
+ }).catch(() => {
32
+ console.error("Clipboard write failed; leave `copied` in its idle state.");
33
+ });
34
+ }, [resetMs])
35
+ };
36
+ }
37
+ //#endregion
38
+ //#region src/CodeBlock/CodeBlock.tsx
39
+ const syntaxStyles = ({ theme }) => ({
40
+ color: theme.syntax.base,
41
+ fontFamily: theme.syntax.font,
42
+ "& .hljs-comment, & .hljs-quote, & .hljs-doctag, & .hljs-meta.prompt_": {
43
+ color: theme.syntax.comment,
44
+ fontStyle: "italic"
45
+ },
46
+ "& .hljs-keyword, & .hljs-built_in, & .hljs-literal, & .hljs-selector-tag, & .hljs-selector-pseudo, & .hljs-selector-attr": { color: theme.syntax.keyword },
47
+ "& .hljs-type, & .hljs-title.class_": { color: theme.syntax.type },
48
+ "& .hljs-string, & .hljs-regexp, & .hljs-char, & .hljs-meta .hljs-string, & .hljs-addition": { color: theme.syntax.string },
49
+ "& .hljs-number, & .hljs-symbol, & .hljs-bullet, & .hljs-link": { color: theme.syntax.number },
50
+ "& .hljs-variable.constant_, & .hljs-property": { color: theme.syntax.constant },
51
+ "& .hljs-title, & .hljs-section, & .hljs-attr, & .hljs-attribute, & .hljs-selector-id": { color: theme.syntax.title },
52
+ "& .hljs-tag, & .hljs-name, & .hljs-meta, & .hljs-variable, & .hljs-selector-class, & .hljs-deletion": { color: theme.syntax.tag },
53
+ "& .hljs-emphasis": { fontStyle: "italic" },
54
+ "& .hljs-strong": { fontWeight: "bold" }
55
+ });
56
+ const CodeSurface = (0, _soroush_tech_design_system.styled)(_soroush_tech_design_system_View.View, {
57
+ name: "CodeBlock",
58
+ slot: "surface",
59
+ label: "CodeSurface"
60
+ })(syntaxStyles);
61
+ const Wrapper = (0, _soroush_tech_design_system.styled)(_soroush_tech_design_system_View.View, {
62
+ name: "CodeBlock",
63
+ label: "CodeBlock"
64
+ })({
65
+ display: "grid",
66
+ gridTemplateColumns: "minmax(0, 1fr)",
67
+ "& .code-copy": {
68
+ opacity: 0,
69
+ transition: "opacity 120ms ease"
70
+ },
71
+ "&:hover .code-copy, &:focus-within .code-copy": { opacity: 1 },
72
+ "@media (hover: none)": { "& .code-copy": { opacity: 1 } }
73
+ });
74
+ /**
75
+ * Fenced-code block: a horizontally scrollable, syntax-highlighted surface with a
76
+ * copy-to-clipboard button that sticks to the top-right while the block is in view.
77
+ */
78
+ function CodeBlock({ children, ...rest }) {
79
+ const surfaceRef = (0, react.useRef)(null);
80
+ const { copied, copy } = useCopyToClipboard();
81
+ const handleCopy = () => {
82
+ const raw = surfaceRef.current.textContent;
83
+ let end = raw.length;
84
+ while (end > 0 && raw[end - 1] === "\n") end--;
85
+ const text = raw.slice(0, end);
86
+ if (text) copy(text);
87
+ };
88
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Wrapper, {
89
+ my: 2,
90
+ position: "relative",
91
+ ...rest,
92
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_soroush_tech_design_system_View.View, {
93
+ position: "absolute",
94
+ top: "8px",
95
+ right: "8px",
96
+ zIndex: 1,
97
+ bg: "paper",
98
+ borderRadius: "sm",
99
+ className: "code-copy",
100
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_soroush_tech_design_system_Button.Button, {
101
+ type: "button",
102
+ variant: "outlined",
103
+ color: "default",
104
+ size: "sm",
105
+ borderRadius: "sm",
106
+ onClick: handleCopy,
107
+ "aria-label": copied ? "Copied" : "Copy code",
108
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_soroush_tech_design_system_Icon.Icon, {
109
+ name: copied ? "check" : "content_copy",
110
+ color: copied ? "success" : "primary",
111
+ size: "1.1rem"
112
+ })
113
+ })
114
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CodeSurface, {
115
+ as: "pre",
116
+ tabIndex: 0,
117
+ ref: surfaceRef,
118
+ bg: "terminal",
119
+ p: 3,
120
+ borderRadius: "md",
121
+ borderColor: "light",
122
+ borderWidth: "thin",
123
+ maxWidth: "100%",
124
+ overflow: "auto",
125
+ children
126
+ })]
127
+ });
128
+ }
129
+ //#endregion
130
+ //#region src/CodeBlock/CodeBlock.data.ts
131
+ const font = "'JetBrains Mono Variable', 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace";
132
+ /** Matched to the JetBrains "Islands Dark" editor scheme. */
133
+ const syntaxDark = {
134
+ base: "#BBBBBB",
135
+ keyword: "#CF8E6D",
136
+ string: "#6AAB73",
137
+ number: "#2AACB8",
138
+ title: "#6BA9FA",
139
+ constant: "#C77DBB",
140
+ type: "#2AACB8",
141
+ comment: "#919191",
142
+ tag: "#D5B778",
143
+ font
144
+ };
145
+ /** Matched to the JetBrains "Islands Light" editor scheme. */
146
+ const syntaxLight = {
147
+ base: "#080808",
148
+ keyword: "#0033B3",
149
+ string: "#067D17",
150
+ number: "#1750EB",
151
+ title: "#00627A",
152
+ constant: "#871094",
153
+ type: "#007E8A",
154
+ comment: "#8C8C8C",
155
+ tag: "#008077",
156
+ font
157
+ };
158
+ //#endregion
159
+ Object.defineProperty(exports, "CodeBlock", {
160
+ enumerable: true,
161
+ get: function() {
162
+ return CodeBlock;
163
+ }
164
+ });
165
+ Object.defineProperty(exports, "syntaxDark", {
166
+ enumerable: true,
167
+ get: function() {
168
+ return syntaxDark;
169
+ }
170
+ });
171
+ Object.defineProperty(exports, "syntaxLight", {
172
+ enumerable: true,
173
+ get: function() {
174
+ return syntaxLight;
175
+ }
176
+ });
@@ -0,0 +1,5 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_CodeBlock = require("./CodeBlock-Cymx1ZAY.cjs");
3
+ exports.CodeBlock = require_CodeBlock.CodeBlock;
4
+ exports.syntaxDark = require_CodeBlock.syntaxDark;
5
+ exports.syntaxLight = require_CodeBlock.syntaxLight;
@@ -0,0 +1,2 @@
1
+ import { a as CodeBlockProps, i as CodeBlock, n as syntaxDark, r as syntaxLight, t as ThemeSyntax } from "./index-BtMTBOcM.cjs";
2
+ export { CodeBlock, CodeBlockProps, ThemeSyntax, syntaxDark, syntaxLight };
@@ -0,0 +1,2 @@
1
+ import { a as CodeBlockProps, i as CodeBlock, n as syntaxDark, r as syntaxLight, t as ThemeSyntax } from "./index-BtMTBOcM.mjs";
2
+ export { CodeBlock, CodeBlockProps, ThemeSyntax, syntaxDark, syntaxLight };
@@ -0,0 +1,2 @@
1
+ import { n as syntaxLight, r as CodeBlock, t as syntaxDark } from "./CodeBlock-CVlrvVPP.mjs";
2
+ export { CodeBlock, syntaxDark, syntaxLight };
@@ -0,0 +1,61 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_applyAction = require("./applyAction-BDkgYhOd.cjs");
3
+ const require_MarkdownContext = require("./MarkdownContext.cjs");
4
+ let react = require("react");
5
+ let react_jsx_runtime = require("react/jsx-runtime");
6
+ //#region src/Control/Control.tsx
7
+ /**
8
+ * The headless root of the Markdown compound: owns the shared value/selection state and exposes
9
+ * it (plus a `dispatch` for toolbar actions) via context. It renders no UI of its own — the
10
+ * consumer arranges the parts and decides which panes to show.
11
+ */
12
+ function Control({ value, onChange, children }) {
13
+ const lastSelectionRef = (0, react.useRef)(null);
14
+ const pendingSelectionRef = (0, react.useRef)(null);
15
+ const context = (0, react.useMemo)(() => {
16
+ const dispatch = (action) => {
17
+ const { start, end } = lastSelectionRef.current ?? {
18
+ start: value.length,
19
+ end: value.length
20
+ };
21
+ const next = require_applyAction.applyAction(action, {
22
+ value,
23
+ selectionStart: start,
24
+ selectionEnd: end
25
+ });
26
+ const selection = {
27
+ start: next.selectionStart,
28
+ end: next.selectionEnd
29
+ };
30
+ pendingSelectionRef.current = selection;
31
+ lastSelectionRef.current = selection;
32
+ onChange(next.value);
33
+ };
34
+ const rememberSelection = (selection) => {
35
+ lastSelectionRef.current = selection;
36
+ };
37
+ const queueSelection = (selection) => {
38
+ pendingSelectionRef.current = selection;
39
+ lastSelectionRef.current = selection;
40
+ };
41
+ const takeQueuedSelection = () => {
42
+ const selection = pendingSelectionRef.current;
43
+ pendingSelectionRef.current = null;
44
+ return selection;
45
+ };
46
+ return {
47
+ value,
48
+ onChange,
49
+ dispatch,
50
+ rememberSelection,
51
+ queueSelection,
52
+ takeQueuedSelection
53
+ };
54
+ }, [value, onChange]);
55
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_MarkdownContext.MarkdownContext.Provider, {
56
+ value: context,
57
+ children
58
+ });
59
+ }
60
+ //#endregion
61
+ exports.Control = Control;
@@ -0,0 +1,18 @@
1
+ import { ReactNode } from "react";
2
+ //#region src/Control/Control.d.ts
3
+ interface ControlProps {
4
+ /** Current markdown source (controlled). */
5
+ value: string;
6
+ /** Called with the next source on edit or a toolbar action. */
7
+ onChange: (value: string) => void;
8
+ /** The composed parts — `Markdown.Toolbar`, `Markdown.Editor`, `Markdown.Preview`, … */
9
+ children: ReactNode;
10
+ }
11
+ /**
12
+ * The headless root of the Markdown compound: owns the shared value/selection state and exposes
13
+ * it (plus a `dispatch` for toolbar actions) via context. It renders no UI of its own — the
14
+ * consumer arranges the parts and decides which panes to show.
15
+ */
16
+ declare function Control({ value, onChange, children }: Readonly<ControlProps>): import("react").JSX.Element;
17
+ //#endregion
18
+ export { Control, ControlProps };
@@ -0,0 +1,18 @@
1
+ import { ReactNode } from "react";
2
+ //#region src/Control/Control.d.ts
3
+ interface ControlProps {
4
+ /** Current markdown source (controlled). */
5
+ value: string;
6
+ /** Called with the next source on edit or a toolbar action. */
7
+ onChange: (value: string) => void;
8
+ /** The composed parts — `Markdown.Toolbar`, `Markdown.Editor`, `Markdown.Preview`, … */
9
+ children: ReactNode;
10
+ }
11
+ /**
12
+ * The headless root of the Markdown compound: owns the shared value/selection state and exposes
13
+ * it (plus a `dispatch` for toolbar actions) via context. It renders no UI of its own — the
14
+ * consumer arranges the parts and decides which panes to show.
15
+ */
16
+ declare function Control({ value, onChange, children }: Readonly<ControlProps>): import("react").JSX.Element;
17
+ //#endregion
18
+ export { Control, ControlProps };