@mdfn/components 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.
package/README.md ADDED
@@ -0,0 +1,8 @@
1
+ # `@mdfn/components`
2
+
3
+ Framework-neutral models for complete authoring chrome: primary, selection,
4
+ and floating toolbars; slash commands; link/table/file controls; outline;
5
+ diagnostics; comments; suggestions; review state; and audit history. The engine
6
+ remains headless; framework packages render these models with UIFn components.
7
+
8
+ Import `@uifn/components/styles.css` and `@mdfn/components/styles.css` once in applications that use the styled framework components.
package/dist/index.cjs ADDED
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ MDFN_COMPONENTS_VERSION: () => MDFN_COMPONENTS_VERSION,
24
+ captureMarkdownInsertion: () => captureMarkdownInsertion,
25
+ createAuthoringModel: () => createAuthoringModel,
26
+ createToolbarModel: () => createToolbarModel,
27
+ defaultSlashCommands: () => defaultSlashCommands,
28
+ defaultToolbarGroups: () => defaultToolbarGroups,
29
+ filterSlashCommands: () => filterSlashCommands,
30
+ insertMarkdownAtSelection: () => insertMarkdownAtSelection,
31
+ insertUploadedMarkdown: () => insertUploadedMarkdown,
32
+ runToolbarAction: () => runToolbarAction
33
+ });
34
+ module.exports = __toCommonJS(index_exports);
35
+ var import_core = require("@mdfn/core");
36
+ function insertMarkdownAtSelection(controller, target, markdown, selection) {
37
+ if (!selection && target?.insertMarkdown(markdown)) return;
38
+ const state = controller.getState();
39
+ const insertionSelection = selection ?? (state.selection?.kind === "text" ? state.selection : { kind: "text", anchor: state.markdown.length, head: state.markdown.length });
40
+ const from = Math.min(insertionSelection.anchor, insertionSelection.head);
41
+ const to = Math.max(insertionSelection.anchor, insertionSelection.head);
42
+ controller.dispatch(
43
+ new import_core.Transaction().replaceSource(from, to, markdown).setSelection({ kind: "text", anchor: from + markdown.length, head: from + markdown.length }).withSource("components:file-insert")
44
+ );
45
+ }
46
+ function captureMarkdownInsertion(controller, signal) {
47
+ const state = controller.getState();
48
+ let selection = state.selection?.kind === "text" ? state.selection : { kind: "text", anchor: state.markdown.length, head: state.markdown.length };
49
+ let active = true;
50
+ const unsubscribe = controller.subscribe((change) => {
51
+ if (!active || change.changedRanges.length === 0) return;
52
+ if (change.source.startsWith("history:") && change.changedRanges.some(
53
+ (range) => range.from === 0 && range.to === change.previous.markdown.length && range.insertedLength === change.current.markdown.length
54
+ )) {
55
+ cancel();
56
+ return;
57
+ }
58
+ const mapped = (0, import_core.mapSelection)(selection, change.changedRanges);
59
+ if (mapped?.kind === "text") selection = mapped;
60
+ });
61
+ const cancel = () => {
62
+ if (!active) return;
63
+ active = false;
64
+ unsubscribe();
65
+ signal?.removeEventListener("abort", cancel);
66
+ };
67
+ if (signal?.aborted) cancel();
68
+ else signal?.addEventListener("abort", cancel, { once: true });
69
+ return {
70
+ insert(markdown) {
71
+ if (!active) return false;
72
+ cancel();
73
+ insertMarkdownAtSelection(controller, null, markdown, selection);
74
+ return true;
75
+ },
76
+ cancel
77
+ };
78
+ }
79
+ async function insertUploadedMarkdown(insertion, upload) {
80
+ const result = await upload;
81
+ if (!result) {
82
+ insertion.cancel();
83
+ return;
84
+ }
85
+ const markdown = typeof result === "string" ? result : result.markdown;
86
+ try {
87
+ if (!insertion.insert(markdown)) throw new Error("MDFN_MARKDOWN_INSERTION_CANCELLED");
88
+ } catch (error) {
89
+ if (typeof result !== "string") await result.rollback(error);
90
+ throw error;
91
+ }
92
+ }
93
+ var defaultToolbarGroups = Object.freeze([
94
+ { id: "history", label: "History", actions: [{ id: "undo", label: "Undo", shortcut: "Mod-z" }, { id: "redo", label: "Redo", shortcut: "Mod-Shift-z" }] },
95
+ { id: "inline", label: "Inline formatting", actions: [{ id: "bold", label: "Bold", command: "bold", shortcut: "Mod-b" }, { id: "italic", label: "Italic", command: "italic", shortcut: "Mod-i" }, { id: "code", label: "Code", command: "inline-code" }] },
96
+ { id: "blocks", label: "Blocks", actions: [{ id: "heading-2", label: "Heading", command: "heading-2" }, { id: "bullet-list", label: "Bullet list", command: "bullet-list" }, { id: "ordered-list", label: "Numbered list", command: "ordered-list" }, { id: "blockquote", label: "Quote", command: "blockquote" }, { id: "code-block", label: "Code block", command: "code-block" }] }
97
+ ]);
98
+ var defaultSlashCommands = Object.freeze([
99
+ { id: "paragraph", label: "Paragraph", description: "Start a text paragraph", keywords: ["text", "body"], kind: "command", command: "paragraph" },
100
+ { id: "heading-1", label: "Heading 1", description: "Insert a top-level heading", keywords: ["title", "h1"], kind: "command", command: "heading-1" },
101
+ { id: "heading-2", label: "Heading 2", description: "Insert a section heading", keywords: ["subtitle", "h2"], kind: "command", command: "heading-2" },
102
+ { id: "bullet-list", label: "Bullet list", description: "Create an unordered list", keywords: ["ul", "list"], kind: "command", command: "bullet-list" },
103
+ { id: "ordered-list", label: "Numbered list", description: "Create an ordered list", keywords: ["ol", "steps"], kind: "command", command: "ordered-list" },
104
+ { id: "blockquote", label: "Quote", description: "Create a block quote", keywords: ["quote", "citation"], kind: "command", command: "blockquote" },
105
+ { id: "code-block", label: "Code block", description: "Create a fenced code block", keywords: ["code", "fence"], kind: "command", command: "code-block" },
106
+ { id: "link", label: "Link", description: "Add or edit a safe link", keywords: ["url", "href"], kind: "link" },
107
+ { id: "table", label: "Table", description: "Insert a table", keywords: ["grid", "rows", "columns"], kind: "table" },
108
+ { id: "file", label: "File or image", description: "Select or upload an asset", keywords: ["asset", "upload", "image"], kind: "file" },
109
+ { id: "comment", label: "Comment", description: "Comment on the current selection", keywords: ["review", "note"], kind: "comment" }
110
+ ]);
111
+ function filterSlashCommands(query, commands = defaultSlashCommands) {
112
+ const terms = query.toLocaleLowerCase().trim().split(/\s+/).filter(Boolean);
113
+ if (terms.length === 0) return commands;
114
+ return commands.filter((command) => {
115
+ const haystack = [command.label, command.description, command.id, ...command.keywords].join(" ").toLocaleLowerCase();
116
+ return terms.every((term) => haystack.includes(term));
117
+ });
118
+ }
119
+ function plainText(node) {
120
+ if (node.type === "text" || node.type === "inlineCode") return node.text ?? "";
121
+ return (node.content ?? []).map(plainText).join("");
122
+ }
123
+ function createAuthoringModel(controller, options = {}) {
124
+ const state = controller.getState();
125
+ const selection = state.selection?.kind === "text" ? state.selection : null;
126
+ const selectionFrom = selection ? Math.min(selection.anchor, selection.head) : 0;
127
+ const selectionTo = selection ? Math.max(selection.anchor, selection.head) : 0;
128
+ const beforeCaret = selection && selectionFrom === selectionTo ? state.markdown.slice(state.markdown.lastIndexOf("\n", selectionFrom - 1) + 1, selectionFrom) : "";
129
+ const slashMatch = /^\s*\/([^\s/]*)$/.exec(beforeCaret);
130
+ const inlineGroups = defaultToolbarGroups.filter((group) => group.id === "inline");
131
+ const blockGroups = defaultToolbarGroups.filter((group) => group.id === "blocks");
132
+ return {
133
+ version: state.version,
134
+ mode: options.mode ?? "visual",
135
+ compact: options.compact ?? false,
136
+ toolbar: createToolbarModel(controller, defaultToolbarGroups, "Markdown authoring toolbar", options.commandTarget),
137
+ bubble: createToolbarModel(controller, inlineGroups, "Selection formatting", options.commandTarget),
138
+ floating: createToolbarModel(controller, blockGroups, "Block formatting", options.commandTarget),
139
+ bubbleVisible: Boolean(selection && selectionFrom !== selectionTo),
140
+ floatingVisible: Boolean(selection && selectionFrom === selectionTo),
141
+ slashOpen: options.slashOpen === true || Boolean(slashMatch),
142
+ slashCommands: filterSlashCommands(options.slashQuery ?? slashMatch?.[1] ?? ""),
143
+ outline: state.document.content.flatMap((node, index) => node.type === "heading" ? [{ id: node.id ?? `heading-${index}`, level: typeof node.attrs?.level === "number" ? node.attrs.level : 1, text: plainText(node), from: node.source?.from }] : []),
144
+ diagnostics: state.diagnostics,
145
+ comments: state.sidecar?.comments ?? [],
146
+ suggestions: state.sidecar?.suggestions ?? [],
147
+ reviewState: state.sidecar?.reviewState ?? "draft",
148
+ audit: state.sidecar?.audit ?? []
149
+ };
150
+ }
151
+ function createToolbarModel(controller, groups = defaultToolbarGroups, ariaLabel = "Markdown formatting", commandTarget) {
152
+ const canRun = (command) => commandTarget?.can?.(command) ?? controller.can(command);
153
+ return {
154
+ ariaLabel,
155
+ groups: groups.map((group) => ({
156
+ ...group,
157
+ actions: group.actions.map((action) => ({
158
+ ...action,
159
+ disabled: action.id === "undo" ? !(commandTarget?.can?.("undo") ?? controller.canUndo()) : action.id === "redo" ? !(commandTarget?.can?.("redo") ?? controller.canRedo()) : action.command ? !canRun(action.command) : action.disabled
160
+ }))
161
+ }))
162
+ };
163
+ }
164
+ function runToolbarAction(controller, action, commandTarget) {
165
+ if (action.disabled) return false;
166
+ if (commandTarget) return commandTarget.run(action.command ?? action.id);
167
+ if (action.id === "undo") return controller.undo();
168
+ if (action.id === "redo") return controller.redo();
169
+ return action.command ? controller.run(action.command) : false;
170
+ }
171
+ var MDFN_COMPONENTS_VERSION = "0.1.0";
172
+ // Annotate the CommonJS export names for ESM import in node:
173
+ 0 && (module.exports = {
174
+ MDFN_COMPONENTS_VERSION,
175
+ captureMarkdownInsertion,
176
+ createAuthoringModel,
177
+ createToolbarModel,
178
+ defaultSlashCommands,
179
+ defaultToolbarGroups,
180
+ filterSlashCommands,
181
+ insertMarkdownAtSelection,
182
+ insertUploadedMarkdown,
183
+ runToolbarAction
184
+ });
185
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { Transaction, mapSelection, type EditorController, type MdfnDiagnostic, type MdfnNode, type ReviewState, type TextSelection } from \"@mdfn/core\";\n\nexport type EditorMode = \"visual\" | \"source\" | \"split\" | \"preview\" | \"read-only\";\n\nexport interface ToolbarAction {\n readonly id: string;\n readonly label: string;\n readonly command?: string;\n readonly shortcut?: string;\n readonly pressed?: boolean;\n readonly disabled?: boolean;\n}\n\nexport interface ToolbarGroup {\n readonly id: string;\n readonly label: string;\n readonly actions: readonly ToolbarAction[];\n}\n\nexport interface ToolbarModel {\n readonly ariaLabel: string;\n readonly groups: readonly ToolbarGroup[];\n}\n\nexport interface ToolbarCommandTarget {\n can?(command: string): boolean;\n run(command: string): boolean;\n}\n\nexport interface MarkdownInsertionTarget {\n insertMarkdown(markdown: string): boolean;\n}\n\nexport interface UploadedMarkdown {\n readonly markdown: string;\n rollback(cause: unknown): void | Promise<void>;\n}\n\nexport type MarkdownUploadResult = string | UploadedMarkdown | undefined;\n\nexport interface CapturedMarkdownInsertion {\n insert(markdown: string): boolean;\n cancel(): void;\n}\n\n/** Insert through the active visual surface, or fall back to the canonical controller selection. */\nexport function insertMarkdownAtSelection(\n controller: EditorController,\n target: MarkdownInsertionTarget | null | undefined,\n markdown: string,\n selection?: TextSelection,\n): void {\n if (!selection && target?.insertMarkdown(markdown)) return;\n const state = controller.getState();\n const insertionSelection = selection ?? (state.selection?.kind === \"text\"\n ? state.selection\n : { kind: \"text\", anchor: state.markdown.length, head: state.markdown.length });\n const from = Math.min(insertionSelection.anchor, insertionSelection.head);\n const to = Math.max(insertionSelection.anchor, insertionSelection.head);\n controller.dispatch(\n new Transaction()\n .replaceSource(from, to, markdown)\n .setSelection({ kind: \"text\", anchor: from + markdown.length, head: from + markdown.length })\n .withSource(\"components:file-insert\"),\n );\n}\n\n/** Keep an asynchronous file insertion anchored to the selection where it started. */\nexport function captureMarkdownInsertion(controller: EditorController, signal?: AbortSignal): CapturedMarkdownInsertion {\n const state = controller.getState();\n let selection: TextSelection = state.selection?.kind === \"text\"\n ? state.selection\n : { kind: \"text\", anchor: state.markdown.length, head: state.markdown.length };\n let active = true;\n const unsubscribe = controller.subscribe((change) => {\n if (!active || change.changedRanges.length === 0) return;\n if (change.source.startsWith(\"history:\") && change.changedRanges.some((range) =>\n range.from === 0 && range.to === change.previous.markdown.length && range.insertedLength === change.current.markdown.length\n )) {\n cancel();\n return;\n }\n const mapped = mapSelection(selection, change.changedRanges);\n if (mapped?.kind === \"text\") selection = mapped;\n });\n const cancel = (): void => {\n if (!active) return;\n active = false;\n unsubscribe();\n signal?.removeEventListener(\"abort\", cancel);\n };\n if (signal?.aborted) cancel();\n else signal?.addEventListener(\"abort\", cancel, { once: true });\n return {\n insert(markdown) {\n if (!active) return false;\n cancel();\n insertMarkdownAtSelection(controller, null, markdown, selection);\n return true;\n },\n cancel,\n };\n}\n\n/** Complete an upload and compensate durable assets if canonical insertion fails or is cancelled. */\nexport async function insertUploadedMarkdown(\n insertion: CapturedMarkdownInsertion,\n upload: Promise<MarkdownUploadResult>,\n): Promise<void> {\n const result = await upload;\n if (!result) {\n insertion.cancel();\n return;\n }\n const markdown = typeof result === \"string\" ? result : result.markdown;\n try {\n if (!insertion.insert(markdown)) throw new Error(\"MDFN_MARKDOWN_INSERTION_CANCELLED\");\n } catch (error) {\n if (typeof result !== \"string\") await result.rollback(error);\n throw error;\n }\n}\n\nexport const defaultToolbarGroups: readonly ToolbarGroup[] = Object.freeze([\n { id: \"history\", label: \"History\", actions: [{ id: \"undo\", label: \"Undo\", shortcut: \"Mod-z\" }, { id: \"redo\", label: \"Redo\", shortcut: \"Mod-Shift-z\" }] },\n { id: \"inline\", label: \"Inline formatting\", actions: [{ id: \"bold\", label: \"Bold\", command: \"bold\", shortcut: \"Mod-b\" }, { id: \"italic\", label: \"Italic\", command: \"italic\", shortcut: \"Mod-i\" }, { id: \"code\", label: \"Code\", command: \"inline-code\" }] },\n { id: \"blocks\", label: \"Blocks\", actions: [{ id: \"heading-2\", label: \"Heading\", command: \"heading-2\" }, { id: \"bullet-list\", label: \"Bullet list\", command: \"bullet-list\" }, { id: \"ordered-list\", label: \"Numbered list\", command: \"ordered-list\" }, { id: \"blockquote\", label: \"Quote\", command: \"blockquote\" }, { id: \"code-block\", label: \"Code block\", command: \"code-block\" }] },\n]);\n\nexport interface SlashCommand {\n readonly id: string;\n readonly label: string;\n readonly description: string;\n readonly keywords: readonly string[];\n readonly kind: \"command\" | \"link\" | \"table\" | \"file\" | \"comment\";\n readonly command?: string;\n}\n\nexport const defaultSlashCommands: readonly SlashCommand[] = Object.freeze([\n { id: \"paragraph\", label: \"Paragraph\", description: \"Start a text paragraph\", keywords: [\"text\", \"body\"], kind: \"command\", command: \"paragraph\" },\n { id: \"heading-1\", label: \"Heading 1\", description: \"Insert a top-level heading\", keywords: [\"title\", \"h1\"], kind: \"command\", command: \"heading-1\" },\n { id: \"heading-2\", label: \"Heading 2\", description: \"Insert a section heading\", keywords: [\"subtitle\", \"h2\"], kind: \"command\", command: \"heading-2\" },\n { id: \"bullet-list\", label: \"Bullet list\", description: \"Create an unordered list\", keywords: [\"ul\", \"list\"], kind: \"command\", command: \"bullet-list\" },\n { id: \"ordered-list\", label: \"Numbered list\", description: \"Create an ordered list\", keywords: [\"ol\", \"steps\"], kind: \"command\", command: \"ordered-list\" },\n { id: \"blockquote\", label: \"Quote\", description: \"Create a block quote\", keywords: [\"quote\", \"citation\"], kind: \"command\", command: \"blockquote\" },\n { id: \"code-block\", label: \"Code block\", description: \"Create a fenced code block\", keywords: [\"code\", \"fence\"], kind: \"command\", command: \"code-block\" },\n { id: \"link\", label: \"Link\", description: \"Add or edit a safe link\", keywords: [\"url\", \"href\"], kind: \"link\" },\n { id: \"table\", label: \"Table\", description: \"Insert a table\", keywords: [\"grid\", \"rows\", \"columns\"], kind: \"table\" },\n { id: \"file\", label: \"File or image\", description: \"Select or upload an asset\", keywords: [\"asset\", \"upload\", \"image\"], kind: \"file\" },\n { id: \"comment\", label: \"Comment\", description: \"Comment on the current selection\", keywords: [\"review\", \"note\"], kind: \"comment\" },\n]);\n\nexport function filterSlashCommands(query: string, commands: readonly SlashCommand[] = defaultSlashCommands): readonly SlashCommand[] {\n const terms = query.toLocaleLowerCase().trim().split(/\\s+/).filter(Boolean);\n if (terms.length === 0) return commands;\n return commands.filter((command) => {\n const haystack = [command.label, command.description, command.id, ...command.keywords].join(\" \").toLocaleLowerCase();\n return terms.every((term) => haystack.includes(term));\n });\n}\n\nexport interface OutlineItem {\n readonly id: string;\n readonly level: number;\n readonly text: string;\n readonly from?: number;\n}\n\nfunction plainText(node: MdfnNode): string {\n if (node.type === \"text\" || node.type === \"inlineCode\") return node.text ?? \"\";\n return (node.content ?? []).map(plainText).join(\"\");\n}\n\nexport interface AuthoringModel {\n readonly version: number;\n readonly mode: EditorMode;\n readonly compact: boolean;\n readonly toolbar: ToolbarModel;\n readonly bubble: ToolbarModel;\n readonly floating: ToolbarModel;\n readonly bubbleVisible: boolean;\n readonly floatingVisible: boolean;\n readonly slashOpen: boolean;\n readonly slashCommands: readonly SlashCommand[];\n readonly outline: readonly OutlineItem[];\n readonly diagnostics: readonly MdfnDiagnostic[];\n readonly comments: NonNullable<ReturnType<EditorController[\"getState\"]>[\"sidecar\"]>[\"comments\"];\n readonly suggestions: NonNullable<ReturnType<EditorController[\"getState\"]>[\"sidecar\"]>[\"suggestions\"];\n readonly reviewState: ReviewState;\n readonly audit: NonNullable<ReturnType<EditorController[\"getState\"]>[\"sidecar\"]>[\"audit\"];\n}\n\nexport function createAuthoringModel(\n controller: EditorController,\n options: { readonly mode?: EditorMode; readonly compact?: boolean; readonly slashQuery?: string; readonly slashOpen?: boolean; readonly commandTarget?: ToolbarCommandTarget | null } = {},\n): AuthoringModel {\n const state = controller.getState();\n const selection = state.selection?.kind === \"text\" ? state.selection : null;\n const selectionFrom = selection ? Math.min(selection.anchor, selection.head) : 0;\n const selectionTo = selection ? Math.max(selection.anchor, selection.head) : 0;\n const beforeCaret = selection && selectionFrom === selectionTo ? state.markdown.slice(state.markdown.lastIndexOf(\"\\n\", selectionFrom - 1) + 1, selectionFrom) : \"\";\n const slashMatch = /^\\s*\\/([^\\s/]*)$/.exec(beforeCaret);\n const inlineGroups = defaultToolbarGroups.filter((group) => group.id === \"inline\");\n const blockGroups = defaultToolbarGroups.filter((group) => group.id === \"blocks\");\n return {\n version: state.version,\n mode: options.mode ?? \"visual\",\n compact: options.compact ?? false,\n toolbar: createToolbarModel(controller, defaultToolbarGroups, \"Markdown authoring toolbar\", options.commandTarget),\n bubble: createToolbarModel(controller, inlineGroups, \"Selection formatting\", options.commandTarget),\n floating: createToolbarModel(controller, blockGroups, \"Block formatting\", options.commandTarget),\n bubbleVisible: Boolean(selection && selectionFrom !== selectionTo),\n floatingVisible: Boolean(selection && selectionFrom === selectionTo),\n slashOpen: options.slashOpen === true || Boolean(slashMatch),\n slashCommands: filterSlashCommands(options.slashQuery ?? slashMatch?.[1] ?? \"\"),\n outline: state.document.content.flatMap((node, index) => node.type === \"heading\" ? [{ id: node.id ?? `heading-${index}`, level: typeof node.attrs?.level === \"number\" ? node.attrs.level : 1, text: plainText(node), from: node.source?.from }] : []),\n diagnostics: state.diagnostics,\n comments: state.sidecar?.comments ?? [],\n suggestions: state.sidecar?.suggestions ?? [],\n reviewState: state.sidecar?.reviewState ?? \"draft\",\n audit: state.sidecar?.audit ?? [],\n };\n}\n\n/** Framework-neutral version descriptor used by every authoring adapter. */\nexport interface AuthoringVersion {\n readonly version: number;\n readonly createdAt?: string;\n readonly authorId?: string;\n readonly changeSource?: string;\n}\n\nexport function createToolbarModel(controller: EditorController, groups: readonly ToolbarGroup[] = defaultToolbarGroups, ariaLabel = \"Markdown formatting\", commandTarget?: ToolbarCommandTarget | null): ToolbarModel {\n const canRun = (command: string): boolean => commandTarget?.can?.(command) ?? controller.can(command);\n return {\n ariaLabel,\n groups: groups.map((group) => ({\n ...group,\n actions: group.actions.map((action) => ({\n ...action,\n disabled: action.id === \"undo\" ? !(commandTarget?.can?.(\"undo\") ?? controller.canUndo()) : action.id === \"redo\" ? !(commandTarget?.can?.(\"redo\") ?? controller.canRedo()) : action.command ? !canRun(action.command) : action.disabled,\n })),\n })),\n };\n}\n\nexport function runToolbarAction(controller: EditorController, action: ToolbarAction, commandTarget?: ToolbarCommandTarget | null): boolean {\n if (action.disabled) return false;\n if (commandTarget) return commandTarget.run(action.command ?? action.id);\n if (action.id === \"undo\") return controller.undo();\n if (action.id === \"redo\") return controller.redo();\n return action.command ? controller.run(action.command) : false;\n}\n\nexport const MDFN_COMPONENTS_VERSION = \"0.1.0\" as const;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAA2I;AA8CpI,SAAS,0BACd,YACA,QACA,UACA,WACM;AACN,MAAI,CAAC,aAAa,QAAQ,eAAe,QAAQ,EAAG;AACpD,QAAM,QAAQ,WAAW,SAAS;AAClC,QAAM,qBAAqB,cAAc,MAAM,WAAW,SAAS,SAC/D,MAAM,YACN,EAAE,MAAM,QAAQ,QAAQ,MAAM,SAAS,QAAQ,MAAM,MAAM,SAAS,OAAO;AAC/E,QAAM,OAAO,KAAK,IAAI,mBAAmB,QAAQ,mBAAmB,IAAI;AACxE,QAAM,KAAK,KAAK,IAAI,mBAAmB,QAAQ,mBAAmB,IAAI;AACtE,aAAW;AAAA,IACT,IAAI,wBAAY,EACb,cAAc,MAAM,IAAI,QAAQ,EAChC,aAAa,EAAE,MAAM,QAAQ,QAAQ,OAAO,SAAS,QAAQ,MAAM,OAAO,SAAS,OAAO,CAAC,EAC3F,WAAW,wBAAwB;AAAA,EACxC;AACF;AAGO,SAAS,yBAAyB,YAA8B,QAAiD;AACtH,QAAM,QAAQ,WAAW,SAAS;AAClC,MAAI,YAA2B,MAAM,WAAW,SAAS,SACrD,MAAM,YACN,EAAE,MAAM,QAAQ,QAAQ,MAAM,SAAS,QAAQ,MAAM,MAAM,SAAS,OAAO;AAC/E,MAAI,SAAS;AACb,QAAM,cAAc,WAAW,UAAU,CAAC,WAAW;AACnD,QAAI,CAAC,UAAU,OAAO,cAAc,WAAW,EAAG;AAClD,QAAI,OAAO,OAAO,WAAW,UAAU,KAAK,OAAO,cAAc;AAAA,MAAK,CAAC,UACrE,MAAM,SAAS,KAAK,MAAM,OAAO,OAAO,SAAS,SAAS,UAAU,MAAM,mBAAmB,OAAO,QAAQ,SAAS;AAAA,IACvH,GAAG;AACD,aAAO;AACP;AAAA,IACF;AACA,UAAM,aAAS,0BAAa,WAAW,OAAO,aAAa;AAC3D,QAAI,QAAQ,SAAS,OAAQ,aAAY;AAAA,EAC3C,CAAC;AACD,QAAM,SAAS,MAAY;AACzB,QAAI,CAAC,OAAQ;AACb,aAAS;AACT,gBAAY;AACZ,YAAQ,oBAAoB,SAAS,MAAM;AAAA,EAC7C;AACA,MAAI,QAAQ,QAAS,QAAO;AAAA,MACvB,SAAQ,iBAAiB,SAAS,QAAQ,EAAE,MAAM,KAAK,CAAC;AAC7D,SAAO;AAAA,IACL,OAAO,UAAU;AACf,UAAI,CAAC,OAAQ,QAAO;AACpB,aAAO;AACP,gCAA0B,YAAY,MAAM,UAAU,SAAS;AAC/D,aAAO;AAAA,IACT;AAAA,IACA;AAAA,EACF;AACF;AAGA,eAAsB,uBACpB,WACA,QACe;AACf,QAAM,SAAS,MAAM;AACrB,MAAI,CAAC,QAAQ;AACX,cAAU,OAAO;AACjB;AAAA,EACF;AACA,QAAM,WAAW,OAAO,WAAW,WAAW,SAAS,OAAO;AAC9D,MAAI;AACF,QAAI,CAAC,UAAU,OAAO,QAAQ,EAAG,OAAM,IAAI,MAAM,mCAAmC;AAAA,EACtF,SAAS,OAAO;AACd,QAAI,OAAO,WAAW,SAAU,OAAM,OAAO,SAAS,KAAK;AAC3D,UAAM;AAAA,EACR;AACF;AAEO,IAAM,uBAAgD,OAAO,OAAO;AAAA,EACzE,EAAE,IAAI,WAAW,OAAO,WAAW,SAAS,CAAC,EAAE,IAAI,QAAQ,OAAO,QAAQ,UAAU,QAAQ,GAAG,EAAE,IAAI,QAAQ,OAAO,QAAQ,UAAU,cAAc,CAAC,EAAE;AAAA,EACvJ,EAAE,IAAI,UAAU,OAAO,qBAAqB,SAAS,CAAC,EAAE,IAAI,QAAQ,OAAO,QAAQ,SAAS,QAAQ,UAAU,QAAQ,GAAG,EAAE,IAAI,UAAU,OAAO,UAAU,SAAS,UAAU,UAAU,QAAQ,GAAG,EAAE,IAAI,QAAQ,OAAO,QAAQ,SAAS,cAAc,CAAC,EAAE;AAAA,EACzP,EAAE,IAAI,UAAU,OAAO,UAAU,SAAS,CAAC,EAAE,IAAI,aAAa,OAAO,WAAW,SAAS,YAAY,GAAG,EAAE,IAAI,eAAe,OAAO,eAAe,SAAS,cAAc,GAAG,EAAE,IAAI,gBAAgB,OAAO,iBAAiB,SAAS,eAAe,GAAG,EAAE,IAAI,cAAc,OAAO,SAAS,SAAS,aAAa,GAAG,EAAE,IAAI,cAAc,OAAO,cAAc,SAAS,aAAa,CAAC,EAAE;AACvX,CAAC;AAWM,IAAM,uBAAgD,OAAO,OAAO;AAAA,EACzE,EAAE,IAAI,aAAa,OAAO,aAAa,aAAa,0BAA0B,UAAU,CAAC,QAAQ,MAAM,GAAG,MAAM,WAAW,SAAS,YAAY;AAAA,EAChJ,EAAE,IAAI,aAAa,OAAO,aAAa,aAAa,8BAA8B,UAAU,CAAC,SAAS,IAAI,GAAG,MAAM,WAAW,SAAS,YAAY;AAAA,EACnJ,EAAE,IAAI,aAAa,OAAO,aAAa,aAAa,4BAA4B,UAAU,CAAC,YAAY,IAAI,GAAG,MAAM,WAAW,SAAS,YAAY;AAAA,EACpJ,EAAE,IAAI,eAAe,OAAO,eAAe,aAAa,4BAA4B,UAAU,CAAC,MAAM,MAAM,GAAG,MAAM,WAAW,SAAS,cAAc;AAAA,EACtJ,EAAE,IAAI,gBAAgB,OAAO,iBAAiB,aAAa,0BAA0B,UAAU,CAAC,MAAM,OAAO,GAAG,MAAM,WAAW,SAAS,eAAe;AAAA,EACzJ,EAAE,IAAI,cAAc,OAAO,SAAS,aAAa,wBAAwB,UAAU,CAAC,SAAS,UAAU,GAAG,MAAM,WAAW,SAAS,aAAa;AAAA,EACjJ,EAAE,IAAI,cAAc,OAAO,cAAc,aAAa,8BAA8B,UAAU,CAAC,QAAQ,OAAO,GAAG,MAAM,WAAW,SAAS,aAAa;AAAA,EACxJ,EAAE,IAAI,QAAQ,OAAO,QAAQ,aAAa,2BAA2B,UAAU,CAAC,OAAO,MAAM,GAAG,MAAM,OAAO;AAAA,EAC7G,EAAE,IAAI,SAAS,OAAO,SAAS,aAAa,kBAAkB,UAAU,CAAC,QAAQ,QAAQ,SAAS,GAAG,MAAM,QAAQ;AAAA,EACnH,EAAE,IAAI,QAAQ,OAAO,iBAAiB,aAAa,6BAA6B,UAAU,CAAC,SAAS,UAAU,OAAO,GAAG,MAAM,OAAO;AAAA,EACrI,EAAE,IAAI,WAAW,OAAO,WAAW,aAAa,oCAAoC,UAAU,CAAC,UAAU,MAAM,GAAG,MAAM,UAAU;AACpI,CAAC;AAEM,SAAS,oBAAoB,OAAe,WAAoC,sBAA+C;AACpI,QAAM,QAAQ,MAAM,kBAAkB,EAAE,KAAK,EAAE,MAAM,KAAK,EAAE,OAAO,OAAO;AAC1E,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,SAAO,SAAS,OAAO,CAAC,YAAY;AAClC,UAAM,WAAW,CAAC,QAAQ,OAAO,QAAQ,aAAa,QAAQ,IAAI,GAAG,QAAQ,QAAQ,EAAE,KAAK,GAAG,EAAE,kBAAkB;AACnH,WAAO,MAAM,MAAM,CAAC,SAAS,SAAS,SAAS,IAAI,CAAC;AAAA,EACtD,CAAC;AACH;AASA,SAAS,UAAU,MAAwB;AACzC,MAAI,KAAK,SAAS,UAAU,KAAK,SAAS,aAAc,QAAO,KAAK,QAAQ;AAC5E,UAAQ,KAAK,WAAW,CAAC,GAAG,IAAI,SAAS,EAAE,KAAK,EAAE;AACpD;AAqBO,SAAS,qBACd,YACA,UAAwL,CAAC,GACzK;AAChB,QAAM,QAAQ,WAAW,SAAS;AAClC,QAAM,YAAY,MAAM,WAAW,SAAS,SAAS,MAAM,YAAY;AACvE,QAAM,gBAAgB,YAAY,KAAK,IAAI,UAAU,QAAQ,UAAU,IAAI,IAAI;AAC/E,QAAM,cAAc,YAAY,KAAK,IAAI,UAAU,QAAQ,UAAU,IAAI,IAAI;AAC7E,QAAM,cAAc,aAAa,kBAAkB,cAAc,MAAM,SAAS,MAAM,MAAM,SAAS,YAAY,MAAM,gBAAgB,CAAC,IAAI,GAAG,aAAa,IAAI;AAChK,QAAM,aAAa,mBAAmB,KAAK,WAAW;AACtD,QAAM,eAAe,qBAAqB,OAAO,CAAC,UAAU,MAAM,OAAO,QAAQ;AACjF,QAAM,cAAc,qBAAqB,OAAO,CAAC,UAAU,MAAM,OAAO,QAAQ;AAChF,SAAO;AAAA,IACL,SAAS,MAAM;AAAA,IACf,MAAM,QAAQ,QAAQ;AAAA,IACtB,SAAS,QAAQ,WAAW;AAAA,IAC5B,SAAS,mBAAmB,YAAY,sBAAsB,8BAA8B,QAAQ,aAAa;AAAA,IACjH,QAAQ,mBAAmB,YAAY,cAAc,wBAAwB,QAAQ,aAAa;AAAA,IAClG,UAAU,mBAAmB,YAAY,aAAa,oBAAoB,QAAQ,aAAa;AAAA,IAC/F,eAAe,QAAQ,aAAa,kBAAkB,WAAW;AAAA,IACjE,iBAAiB,QAAQ,aAAa,kBAAkB,WAAW;AAAA,IACnE,WAAW,QAAQ,cAAc,QAAQ,QAAQ,UAAU;AAAA,IAC3D,eAAe,oBAAoB,QAAQ,cAAc,aAAa,CAAC,KAAK,EAAE;AAAA,IAC9E,SAAS,MAAM,SAAS,QAAQ,QAAQ,CAAC,MAAM,UAAU,KAAK,SAAS,YAAY,CAAC,EAAE,IAAI,KAAK,MAAM,WAAW,KAAK,IAAI,OAAO,OAAO,KAAK,OAAO,UAAU,WAAW,KAAK,MAAM,QAAQ,GAAG,MAAM,UAAU,IAAI,GAAG,MAAM,KAAK,QAAQ,KAAK,CAAC,IAAI,CAAC,CAAC;AAAA,IACpP,aAAa,MAAM;AAAA,IACnB,UAAU,MAAM,SAAS,YAAY,CAAC;AAAA,IACtC,aAAa,MAAM,SAAS,eAAe,CAAC;AAAA,IAC5C,aAAa,MAAM,SAAS,eAAe;AAAA,IAC3C,OAAO,MAAM,SAAS,SAAS,CAAC;AAAA,EAClC;AACF;AAUO,SAAS,mBAAmB,YAA8B,SAAkC,sBAAsB,YAAY,uBAAuB,eAA2D;AACrN,QAAM,SAAS,CAAC,YAA6B,eAAe,MAAM,OAAO,KAAK,WAAW,IAAI,OAAO;AACpG,SAAO;AAAA,IACL;AAAA,IACA,QAAQ,OAAO,IAAI,CAAC,WAAW;AAAA,MAC7B,GAAG;AAAA,MACH,SAAS,MAAM,QAAQ,IAAI,CAAC,YAAY;AAAA,QACtC,GAAG;AAAA,QACH,UAAU,OAAO,OAAO,SAAS,EAAE,eAAe,MAAM,MAAM,KAAK,WAAW,QAAQ,KAAK,OAAO,OAAO,SAAS,EAAE,eAAe,MAAM,MAAM,KAAK,WAAW,QAAQ,KAAK,OAAO,UAAU,CAAC,OAAO,OAAO,OAAO,IAAI,OAAO;AAAA,MAChO,EAAE;AAAA,IACJ,EAAE;AAAA,EACJ;AACF;AAEO,SAAS,iBAAiB,YAA8B,QAAuB,eAAsD;AAC1I,MAAI,OAAO,SAAU,QAAO;AAC5B,MAAI,cAAe,QAAO,cAAc,IAAI,OAAO,WAAW,OAAO,EAAE;AACvE,MAAI,OAAO,OAAO,OAAQ,QAAO,WAAW,KAAK;AACjD,MAAI,OAAO,OAAO,OAAQ,QAAO,WAAW,KAAK;AACjD,SAAO,OAAO,UAAU,WAAW,IAAI,OAAO,OAAO,IAAI;AAC3D;AAEO,IAAM,0BAA0B;","names":[]}
@@ -0,0 +1,96 @@
1
+ import { MdfnDiagnostic, EditorController, ReviewState, TextSelection } from '@mdfn/core';
2
+
3
+ type EditorMode = "visual" | "source" | "split" | "preview" | "read-only";
4
+ interface ToolbarAction {
5
+ readonly id: string;
6
+ readonly label: string;
7
+ readonly command?: string;
8
+ readonly shortcut?: string;
9
+ readonly pressed?: boolean;
10
+ readonly disabled?: boolean;
11
+ }
12
+ interface ToolbarGroup {
13
+ readonly id: string;
14
+ readonly label: string;
15
+ readonly actions: readonly ToolbarAction[];
16
+ }
17
+ interface ToolbarModel {
18
+ readonly ariaLabel: string;
19
+ readonly groups: readonly ToolbarGroup[];
20
+ }
21
+ interface ToolbarCommandTarget {
22
+ can?(command: string): boolean;
23
+ run(command: string): boolean;
24
+ }
25
+ interface MarkdownInsertionTarget {
26
+ insertMarkdown(markdown: string): boolean;
27
+ }
28
+ interface UploadedMarkdown {
29
+ readonly markdown: string;
30
+ rollback(cause: unknown): void | Promise<void>;
31
+ }
32
+ type MarkdownUploadResult = string | UploadedMarkdown | undefined;
33
+ interface CapturedMarkdownInsertion {
34
+ insert(markdown: string): boolean;
35
+ cancel(): void;
36
+ }
37
+ /** Insert through the active visual surface, or fall back to the canonical controller selection. */
38
+ declare function insertMarkdownAtSelection(controller: EditorController, target: MarkdownInsertionTarget | null | undefined, markdown: string, selection?: TextSelection): void;
39
+ /** Keep an asynchronous file insertion anchored to the selection where it started. */
40
+ declare function captureMarkdownInsertion(controller: EditorController, signal?: AbortSignal): CapturedMarkdownInsertion;
41
+ /** Complete an upload and compensate durable assets if canonical insertion fails or is cancelled. */
42
+ declare function insertUploadedMarkdown(insertion: CapturedMarkdownInsertion, upload: Promise<MarkdownUploadResult>): Promise<void>;
43
+ declare const defaultToolbarGroups: readonly ToolbarGroup[];
44
+ interface SlashCommand {
45
+ readonly id: string;
46
+ readonly label: string;
47
+ readonly description: string;
48
+ readonly keywords: readonly string[];
49
+ readonly kind: "command" | "link" | "table" | "file" | "comment";
50
+ readonly command?: string;
51
+ }
52
+ declare const defaultSlashCommands: readonly SlashCommand[];
53
+ declare function filterSlashCommands(query: string, commands?: readonly SlashCommand[]): readonly SlashCommand[];
54
+ interface OutlineItem {
55
+ readonly id: string;
56
+ readonly level: number;
57
+ readonly text: string;
58
+ readonly from?: number;
59
+ }
60
+ interface AuthoringModel {
61
+ readonly version: number;
62
+ readonly mode: EditorMode;
63
+ readonly compact: boolean;
64
+ readonly toolbar: ToolbarModel;
65
+ readonly bubble: ToolbarModel;
66
+ readonly floating: ToolbarModel;
67
+ readonly bubbleVisible: boolean;
68
+ readonly floatingVisible: boolean;
69
+ readonly slashOpen: boolean;
70
+ readonly slashCommands: readonly SlashCommand[];
71
+ readonly outline: readonly OutlineItem[];
72
+ readonly diagnostics: readonly MdfnDiagnostic[];
73
+ readonly comments: NonNullable<ReturnType<EditorController["getState"]>["sidecar"]>["comments"];
74
+ readonly suggestions: NonNullable<ReturnType<EditorController["getState"]>["sidecar"]>["suggestions"];
75
+ readonly reviewState: ReviewState;
76
+ readonly audit: NonNullable<ReturnType<EditorController["getState"]>["sidecar"]>["audit"];
77
+ }
78
+ declare function createAuthoringModel(controller: EditorController, options?: {
79
+ readonly mode?: EditorMode;
80
+ readonly compact?: boolean;
81
+ readonly slashQuery?: string;
82
+ readonly slashOpen?: boolean;
83
+ readonly commandTarget?: ToolbarCommandTarget | null;
84
+ }): AuthoringModel;
85
+ /** Framework-neutral version descriptor used by every authoring adapter. */
86
+ interface AuthoringVersion {
87
+ readonly version: number;
88
+ readonly createdAt?: string;
89
+ readonly authorId?: string;
90
+ readonly changeSource?: string;
91
+ }
92
+ declare function createToolbarModel(controller: EditorController, groups?: readonly ToolbarGroup[], ariaLabel?: string, commandTarget?: ToolbarCommandTarget | null): ToolbarModel;
93
+ declare function runToolbarAction(controller: EditorController, action: ToolbarAction, commandTarget?: ToolbarCommandTarget | null): boolean;
94
+ declare const MDFN_COMPONENTS_VERSION: "0.1.0";
95
+
96
+ export { type AuthoringModel, type AuthoringVersion, type CapturedMarkdownInsertion, type EditorMode, MDFN_COMPONENTS_VERSION, type MarkdownInsertionTarget, type MarkdownUploadResult, type OutlineItem, type SlashCommand, type ToolbarAction, type ToolbarCommandTarget, type ToolbarGroup, type ToolbarModel, type UploadedMarkdown, captureMarkdownInsertion, createAuthoringModel, createToolbarModel, defaultSlashCommands, defaultToolbarGroups, filterSlashCommands, insertMarkdownAtSelection, insertUploadedMarkdown, runToolbarAction };
@@ -0,0 +1,96 @@
1
+ import { MdfnDiagnostic, EditorController, ReviewState, TextSelection } from '@mdfn/core';
2
+
3
+ type EditorMode = "visual" | "source" | "split" | "preview" | "read-only";
4
+ interface ToolbarAction {
5
+ readonly id: string;
6
+ readonly label: string;
7
+ readonly command?: string;
8
+ readonly shortcut?: string;
9
+ readonly pressed?: boolean;
10
+ readonly disabled?: boolean;
11
+ }
12
+ interface ToolbarGroup {
13
+ readonly id: string;
14
+ readonly label: string;
15
+ readonly actions: readonly ToolbarAction[];
16
+ }
17
+ interface ToolbarModel {
18
+ readonly ariaLabel: string;
19
+ readonly groups: readonly ToolbarGroup[];
20
+ }
21
+ interface ToolbarCommandTarget {
22
+ can?(command: string): boolean;
23
+ run(command: string): boolean;
24
+ }
25
+ interface MarkdownInsertionTarget {
26
+ insertMarkdown(markdown: string): boolean;
27
+ }
28
+ interface UploadedMarkdown {
29
+ readonly markdown: string;
30
+ rollback(cause: unknown): void | Promise<void>;
31
+ }
32
+ type MarkdownUploadResult = string | UploadedMarkdown | undefined;
33
+ interface CapturedMarkdownInsertion {
34
+ insert(markdown: string): boolean;
35
+ cancel(): void;
36
+ }
37
+ /** Insert through the active visual surface, or fall back to the canonical controller selection. */
38
+ declare function insertMarkdownAtSelection(controller: EditorController, target: MarkdownInsertionTarget | null | undefined, markdown: string, selection?: TextSelection): void;
39
+ /** Keep an asynchronous file insertion anchored to the selection where it started. */
40
+ declare function captureMarkdownInsertion(controller: EditorController, signal?: AbortSignal): CapturedMarkdownInsertion;
41
+ /** Complete an upload and compensate durable assets if canonical insertion fails or is cancelled. */
42
+ declare function insertUploadedMarkdown(insertion: CapturedMarkdownInsertion, upload: Promise<MarkdownUploadResult>): Promise<void>;
43
+ declare const defaultToolbarGroups: readonly ToolbarGroup[];
44
+ interface SlashCommand {
45
+ readonly id: string;
46
+ readonly label: string;
47
+ readonly description: string;
48
+ readonly keywords: readonly string[];
49
+ readonly kind: "command" | "link" | "table" | "file" | "comment";
50
+ readonly command?: string;
51
+ }
52
+ declare const defaultSlashCommands: readonly SlashCommand[];
53
+ declare function filterSlashCommands(query: string, commands?: readonly SlashCommand[]): readonly SlashCommand[];
54
+ interface OutlineItem {
55
+ readonly id: string;
56
+ readonly level: number;
57
+ readonly text: string;
58
+ readonly from?: number;
59
+ }
60
+ interface AuthoringModel {
61
+ readonly version: number;
62
+ readonly mode: EditorMode;
63
+ readonly compact: boolean;
64
+ readonly toolbar: ToolbarModel;
65
+ readonly bubble: ToolbarModel;
66
+ readonly floating: ToolbarModel;
67
+ readonly bubbleVisible: boolean;
68
+ readonly floatingVisible: boolean;
69
+ readonly slashOpen: boolean;
70
+ readonly slashCommands: readonly SlashCommand[];
71
+ readonly outline: readonly OutlineItem[];
72
+ readonly diagnostics: readonly MdfnDiagnostic[];
73
+ readonly comments: NonNullable<ReturnType<EditorController["getState"]>["sidecar"]>["comments"];
74
+ readonly suggestions: NonNullable<ReturnType<EditorController["getState"]>["sidecar"]>["suggestions"];
75
+ readonly reviewState: ReviewState;
76
+ readonly audit: NonNullable<ReturnType<EditorController["getState"]>["sidecar"]>["audit"];
77
+ }
78
+ declare function createAuthoringModel(controller: EditorController, options?: {
79
+ readonly mode?: EditorMode;
80
+ readonly compact?: boolean;
81
+ readonly slashQuery?: string;
82
+ readonly slashOpen?: boolean;
83
+ readonly commandTarget?: ToolbarCommandTarget | null;
84
+ }): AuthoringModel;
85
+ /** Framework-neutral version descriptor used by every authoring adapter. */
86
+ interface AuthoringVersion {
87
+ readonly version: number;
88
+ readonly createdAt?: string;
89
+ readonly authorId?: string;
90
+ readonly changeSource?: string;
91
+ }
92
+ declare function createToolbarModel(controller: EditorController, groups?: readonly ToolbarGroup[], ariaLabel?: string, commandTarget?: ToolbarCommandTarget | null): ToolbarModel;
93
+ declare function runToolbarAction(controller: EditorController, action: ToolbarAction, commandTarget?: ToolbarCommandTarget | null): boolean;
94
+ declare const MDFN_COMPONENTS_VERSION: "0.1.0";
95
+
96
+ export { type AuthoringModel, type AuthoringVersion, type CapturedMarkdownInsertion, type EditorMode, MDFN_COMPONENTS_VERSION, type MarkdownInsertionTarget, type MarkdownUploadResult, type OutlineItem, type SlashCommand, type ToolbarAction, type ToolbarCommandTarget, type ToolbarGroup, type ToolbarModel, type UploadedMarkdown, captureMarkdownInsertion, createAuthoringModel, createToolbarModel, defaultSlashCommands, defaultToolbarGroups, filterSlashCommands, insertMarkdownAtSelection, insertUploadedMarkdown, runToolbarAction };
package/dist/index.js ADDED
@@ -0,0 +1,151 @@
1
+ // src/index.ts
2
+ import { Transaction, mapSelection } from "@mdfn/core";
3
+ function insertMarkdownAtSelection(controller, target, markdown, selection) {
4
+ if (!selection && target?.insertMarkdown(markdown)) return;
5
+ const state = controller.getState();
6
+ const insertionSelection = selection ?? (state.selection?.kind === "text" ? state.selection : { kind: "text", anchor: state.markdown.length, head: state.markdown.length });
7
+ const from = Math.min(insertionSelection.anchor, insertionSelection.head);
8
+ const to = Math.max(insertionSelection.anchor, insertionSelection.head);
9
+ controller.dispatch(
10
+ new Transaction().replaceSource(from, to, markdown).setSelection({ kind: "text", anchor: from + markdown.length, head: from + markdown.length }).withSource("components:file-insert")
11
+ );
12
+ }
13
+ function captureMarkdownInsertion(controller, signal) {
14
+ const state = controller.getState();
15
+ let selection = state.selection?.kind === "text" ? state.selection : { kind: "text", anchor: state.markdown.length, head: state.markdown.length };
16
+ let active = true;
17
+ const unsubscribe = controller.subscribe((change) => {
18
+ if (!active || change.changedRanges.length === 0) return;
19
+ if (change.source.startsWith("history:") && change.changedRanges.some(
20
+ (range) => range.from === 0 && range.to === change.previous.markdown.length && range.insertedLength === change.current.markdown.length
21
+ )) {
22
+ cancel();
23
+ return;
24
+ }
25
+ const mapped = mapSelection(selection, change.changedRanges);
26
+ if (mapped?.kind === "text") selection = mapped;
27
+ });
28
+ const cancel = () => {
29
+ if (!active) return;
30
+ active = false;
31
+ unsubscribe();
32
+ signal?.removeEventListener("abort", cancel);
33
+ };
34
+ if (signal?.aborted) cancel();
35
+ else signal?.addEventListener("abort", cancel, { once: true });
36
+ return {
37
+ insert(markdown) {
38
+ if (!active) return false;
39
+ cancel();
40
+ insertMarkdownAtSelection(controller, null, markdown, selection);
41
+ return true;
42
+ },
43
+ cancel
44
+ };
45
+ }
46
+ async function insertUploadedMarkdown(insertion, upload) {
47
+ const result = await upload;
48
+ if (!result) {
49
+ insertion.cancel();
50
+ return;
51
+ }
52
+ const markdown = typeof result === "string" ? result : result.markdown;
53
+ try {
54
+ if (!insertion.insert(markdown)) throw new Error("MDFN_MARKDOWN_INSERTION_CANCELLED");
55
+ } catch (error) {
56
+ if (typeof result !== "string") await result.rollback(error);
57
+ throw error;
58
+ }
59
+ }
60
+ var defaultToolbarGroups = Object.freeze([
61
+ { id: "history", label: "History", actions: [{ id: "undo", label: "Undo", shortcut: "Mod-z" }, { id: "redo", label: "Redo", shortcut: "Mod-Shift-z" }] },
62
+ { id: "inline", label: "Inline formatting", actions: [{ id: "bold", label: "Bold", command: "bold", shortcut: "Mod-b" }, { id: "italic", label: "Italic", command: "italic", shortcut: "Mod-i" }, { id: "code", label: "Code", command: "inline-code" }] },
63
+ { id: "blocks", label: "Blocks", actions: [{ id: "heading-2", label: "Heading", command: "heading-2" }, { id: "bullet-list", label: "Bullet list", command: "bullet-list" }, { id: "ordered-list", label: "Numbered list", command: "ordered-list" }, { id: "blockquote", label: "Quote", command: "blockquote" }, { id: "code-block", label: "Code block", command: "code-block" }] }
64
+ ]);
65
+ var defaultSlashCommands = Object.freeze([
66
+ { id: "paragraph", label: "Paragraph", description: "Start a text paragraph", keywords: ["text", "body"], kind: "command", command: "paragraph" },
67
+ { id: "heading-1", label: "Heading 1", description: "Insert a top-level heading", keywords: ["title", "h1"], kind: "command", command: "heading-1" },
68
+ { id: "heading-2", label: "Heading 2", description: "Insert a section heading", keywords: ["subtitle", "h2"], kind: "command", command: "heading-2" },
69
+ { id: "bullet-list", label: "Bullet list", description: "Create an unordered list", keywords: ["ul", "list"], kind: "command", command: "bullet-list" },
70
+ { id: "ordered-list", label: "Numbered list", description: "Create an ordered list", keywords: ["ol", "steps"], kind: "command", command: "ordered-list" },
71
+ { id: "blockquote", label: "Quote", description: "Create a block quote", keywords: ["quote", "citation"], kind: "command", command: "blockquote" },
72
+ { id: "code-block", label: "Code block", description: "Create a fenced code block", keywords: ["code", "fence"], kind: "command", command: "code-block" },
73
+ { id: "link", label: "Link", description: "Add or edit a safe link", keywords: ["url", "href"], kind: "link" },
74
+ { id: "table", label: "Table", description: "Insert a table", keywords: ["grid", "rows", "columns"], kind: "table" },
75
+ { id: "file", label: "File or image", description: "Select or upload an asset", keywords: ["asset", "upload", "image"], kind: "file" },
76
+ { id: "comment", label: "Comment", description: "Comment on the current selection", keywords: ["review", "note"], kind: "comment" }
77
+ ]);
78
+ function filterSlashCommands(query, commands = defaultSlashCommands) {
79
+ const terms = query.toLocaleLowerCase().trim().split(/\s+/).filter(Boolean);
80
+ if (terms.length === 0) return commands;
81
+ return commands.filter((command) => {
82
+ const haystack = [command.label, command.description, command.id, ...command.keywords].join(" ").toLocaleLowerCase();
83
+ return terms.every((term) => haystack.includes(term));
84
+ });
85
+ }
86
+ function plainText(node) {
87
+ if (node.type === "text" || node.type === "inlineCode") return node.text ?? "";
88
+ return (node.content ?? []).map(plainText).join("");
89
+ }
90
+ function createAuthoringModel(controller, options = {}) {
91
+ const state = controller.getState();
92
+ const selection = state.selection?.kind === "text" ? state.selection : null;
93
+ const selectionFrom = selection ? Math.min(selection.anchor, selection.head) : 0;
94
+ const selectionTo = selection ? Math.max(selection.anchor, selection.head) : 0;
95
+ const beforeCaret = selection && selectionFrom === selectionTo ? state.markdown.slice(state.markdown.lastIndexOf("\n", selectionFrom - 1) + 1, selectionFrom) : "";
96
+ const slashMatch = /^\s*\/([^\s/]*)$/.exec(beforeCaret);
97
+ const inlineGroups = defaultToolbarGroups.filter((group) => group.id === "inline");
98
+ const blockGroups = defaultToolbarGroups.filter((group) => group.id === "blocks");
99
+ return {
100
+ version: state.version,
101
+ mode: options.mode ?? "visual",
102
+ compact: options.compact ?? false,
103
+ toolbar: createToolbarModel(controller, defaultToolbarGroups, "Markdown authoring toolbar", options.commandTarget),
104
+ bubble: createToolbarModel(controller, inlineGroups, "Selection formatting", options.commandTarget),
105
+ floating: createToolbarModel(controller, blockGroups, "Block formatting", options.commandTarget),
106
+ bubbleVisible: Boolean(selection && selectionFrom !== selectionTo),
107
+ floatingVisible: Boolean(selection && selectionFrom === selectionTo),
108
+ slashOpen: options.slashOpen === true || Boolean(slashMatch),
109
+ slashCommands: filterSlashCommands(options.slashQuery ?? slashMatch?.[1] ?? ""),
110
+ outline: state.document.content.flatMap((node, index) => node.type === "heading" ? [{ id: node.id ?? `heading-${index}`, level: typeof node.attrs?.level === "number" ? node.attrs.level : 1, text: plainText(node), from: node.source?.from }] : []),
111
+ diagnostics: state.diagnostics,
112
+ comments: state.sidecar?.comments ?? [],
113
+ suggestions: state.sidecar?.suggestions ?? [],
114
+ reviewState: state.sidecar?.reviewState ?? "draft",
115
+ audit: state.sidecar?.audit ?? []
116
+ };
117
+ }
118
+ function createToolbarModel(controller, groups = defaultToolbarGroups, ariaLabel = "Markdown formatting", commandTarget) {
119
+ const canRun = (command) => commandTarget?.can?.(command) ?? controller.can(command);
120
+ return {
121
+ ariaLabel,
122
+ groups: groups.map((group) => ({
123
+ ...group,
124
+ actions: group.actions.map((action) => ({
125
+ ...action,
126
+ disabled: action.id === "undo" ? !(commandTarget?.can?.("undo") ?? controller.canUndo()) : action.id === "redo" ? !(commandTarget?.can?.("redo") ?? controller.canRedo()) : action.command ? !canRun(action.command) : action.disabled
127
+ }))
128
+ }))
129
+ };
130
+ }
131
+ function runToolbarAction(controller, action, commandTarget) {
132
+ if (action.disabled) return false;
133
+ if (commandTarget) return commandTarget.run(action.command ?? action.id);
134
+ if (action.id === "undo") return controller.undo();
135
+ if (action.id === "redo") return controller.redo();
136
+ return action.command ? controller.run(action.command) : false;
137
+ }
138
+ var MDFN_COMPONENTS_VERSION = "0.1.0";
139
+ export {
140
+ MDFN_COMPONENTS_VERSION,
141
+ captureMarkdownInsertion,
142
+ createAuthoringModel,
143
+ createToolbarModel,
144
+ defaultSlashCommands,
145
+ defaultToolbarGroups,
146
+ filterSlashCommands,
147
+ insertMarkdownAtSelection,
148
+ insertUploadedMarkdown,
149
+ runToolbarAction
150
+ };
151
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { Transaction, mapSelection, type EditorController, type MdfnDiagnostic, type MdfnNode, type ReviewState, type TextSelection } from \"@mdfn/core\";\n\nexport type EditorMode = \"visual\" | \"source\" | \"split\" | \"preview\" | \"read-only\";\n\nexport interface ToolbarAction {\n readonly id: string;\n readonly label: string;\n readonly command?: string;\n readonly shortcut?: string;\n readonly pressed?: boolean;\n readonly disabled?: boolean;\n}\n\nexport interface ToolbarGroup {\n readonly id: string;\n readonly label: string;\n readonly actions: readonly ToolbarAction[];\n}\n\nexport interface ToolbarModel {\n readonly ariaLabel: string;\n readonly groups: readonly ToolbarGroup[];\n}\n\nexport interface ToolbarCommandTarget {\n can?(command: string): boolean;\n run(command: string): boolean;\n}\n\nexport interface MarkdownInsertionTarget {\n insertMarkdown(markdown: string): boolean;\n}\n\nexport interface UploadedMarkdown {\n readonly markdown: string;\n rollback(cause: unknown): void | Promise<void>;\n}\n\nexport type MarkdownUploadResult = string | UploadedMarkdown | undefined;\n\nexport interface CapturedMarkdownInsertion {\n insert(markdown: string): boolean;\n cancel(): void;\n}\n\n/** Insert through the active visual surface, or fall back to the canonical controller selection. */\nexport function insertMarkdownAtSelection(\n controller: EditorController,\n target: MarkdownInsertionTarget | null | undefined,\n markdown: string,\n selection?: TextSelection,\n): void {\n if (!selection && target?.insertMarkdown(markdown)) return;\n const state = controller.getState();\n const insertionSelection = selection ?? (state.selection?.kind === \"text\"\n ? state.selection\n : { kind: \"text\", anchor: state.markdown.length, head: state.markdown.length });\n const from = Math.min(insertionSelection.anchor, insertionSelection.head);\n const to = Math.max(insertionSelection.anchor, insertionSelection.head);\n controller.dispatch(\n new Transaction()\n .replaceSource(from, to, markdown)\n .setSelection({ kind: \"text\", anchor: from + markdown.length, head: from + markdown.length })\n .withSource(\"components:file-insert\"),\n );\n}\n\n/** Keep an asynchronous file insertion anchored to the selection where it started. */\nexport function captureMarkdownInsertion(controller: EditorController, signal?: AbortSignal): CapturedMarkdownInsertion {\n const state = controller.getState();\n let selection: TextSelection = state.selection?.kind === \"text\"\n ? state.selection\n : { kind: \"text\", anchor: state.markdown.length, head: state.markdown.length };\n let active = true;\n const unsubscribe = controller.subscribe((change) => {\n if (!active || change.changedRanges.length === 0) return;\n if (change.source.startsWith(\"history:\") && change.changedRanges.some((range) =>\n range.from === 0 && range.to === change.previous.markdown.length && range.insertedLength === change.current.markdown.length\n )) {\n cancel();\n return;\n }\n const mapped = mapSelection(selection, change.changedRanges);\n if (mapped?.kind === \"text\") selection = mapped;\n });\n const cancel = (): void => {\n if (!active) return;\n active = false;\n unsubscribe();\n signal?.removeEventListener(\"abort\", cancel);\n };\n if (signal?.aborted) cancel();\n else signal?.addEventListener(\"abort\", cancel, { once: true });\n return {\n insert(markdown) {\n if (!active) return false;\n cancel();\n insertMarkdownAtSelection(controller, null, markdown, selection);\n return true;\n },\n cancel,\n };\n}\n\n/** Complete an upload and compensate durable assets if canonical insertion fails or is cancelled. */\nexport async function insertUploadedMarkdown(\n insertion: CapturedMarkdownInsertion,\n upload: Promise<MarkdownUploadResult>,\n): Promise<void> {\n const result = await upload;\n if (!result) {\n insertion.cancel();\n return;\n }\n const markdown = typeof result === \"string\" ? result : result.markdown;\n try {\n if (!insertion.insert(markdown)) throw new Error(\"MDFN_MARKDOWN_INSERTION_CANCELLED\");\n } catch (error) {\n if (typeof result !== \"string\") await result.rollback(error);\n throw error;\n }\n}\n\nexport const defaultToolbarGroups: readonly ToolbarGroup[] = Object.freeze([\n { id: \"history\", label: \"History\", actions: [{ id: \"undo\", label: \"Undo\", shortcut: \"Mod-z\" }, { id: \"redo\", label: \"Redo\", shortcut: \"Mod-Shift-z\" }] },\n { id: \"inline\", label: \"Inline formatting\", actions: [{ id: \"bold\", label: \"Bold\", command: \"bold\", shortcut: \"Mod-b\" }, { id: \"italic\", label: \"Italic\", command: \"italic\", shortcut: \"Mod-i\" }, { id: \"code\", label: \"Code\", command: \"inline-code\" }] },\n { id: \"blocks\", label: \"Blocks\", actions: [{ id: \"heading-2\", label: \"Heading\", command: \"heading-2\" }, { id: \"bullet-list\", label: \"Bullet list\", command: \"bullet-list\" }, { id: \"ordered-list\", label: \"Numbered list\", command: \"ordered-list\" }, { id: \"blockquote\", label: \"Quote\", command: \"blockquote\" }, { id: \"code-block\", label: \"Code block\", command: \"code-block\" }] },\n]);\n\nexport interface SlashCommand {\n readonly id: string;\n readonly label: string;\n readonly description: string;\n readonly keywords: readonly string[];\n readonly kind: \"command\" | \"link\" | \"table\" | \"file\" | \"comment\";\n readonly command?: string;\n}\n\nexport const defaultSlashCommands: readonly SlashCommand[] = Object.freeze([\n { id: \"paragraph\", label: \"Paragraph\", description: \"Start a text paragraph\", keywords: [\"text\", \"body\"], kind: \"command\", command: \"paragraph\" },\n { id: \"heading-1\", label: \"Heading 1\", description: \"Insert a top-level heading\", keywords: [\"title\", \"h1\"], kind: \"command\", command: \"heading-1\" },\n { id: \"heading-2\", label: \"Heading 2\", description: \"Insert a section heading\", keywords: [\"subtitle\", \"h2\"], kind: \"command\", command: \"heading-2\" },\n { id: \"bullet-list\", label: \"Bullet list\", description: \"Create an unordered list\", keywords: [\"ul\", \"list\"], kind: \"command\", command: \"bullet-list\" },\n { id: \"ordered-list\", label: \"Numbered list\", description: \"Create an ordered list\", keywords: [\"ol\", \"steps\"], kind: \"command\", command: \"ordered-list\" },\n { id: \"blockquote\", label: \"Quote\", description: \"Create a block quote\", keywords: [\"quote\", \"citation\"], kind: \"command\", command: \"blockquote\" },\n { id: \"code-block\", label: \"Code block\", description: \"Create a fenced code block\", keywords: [\"code\", \"fence\"], kind: \"command\", command: \"code-block\" },\n { id: \"link\", label: \"Link\", description: \"Add or edit a safe link\", keywords: [\"url\", \"href\"], kind: \"link\" },\n { id: \"table\", label: \"Table\", description: \"Insert a table\", keywords: [\"grid\", \"rows\", \"columns\"], kind: \"table\" },\n { id: \"file\", label: \"File or image\", description: \"Select or upload an asset\", keywords: [\"asset\", \"upload\", \"image\"], kind: \"file\" },\n { id: \"comment\", label: \"Comment\", description: \"Comment on the current selection\", keywords: [\"review\", \"note\"], kind: \"comment\" },\n]);\n\nexport function filterSlashCommands(query: string, commands: readonly SlashCommand[] = defaultSlashCommands): readonly SlashCommand[] {\n const terms = query.toLocaleLowerCase().trim().split(/\\s+/).filter(Boolean);\n if (terms.length === 0) return commands;\n return commands.filter((command) => {\n const haystack = [command.label, command.description, command.id, ...command.keywords].join(\" \").toLocaleLowerCase();\n return terms.every((term) => haystack.includes(term));\n });\n}\n\nexport interface OutlineItem {\n readonly id: string;\n readonly level: number;\n readonly text: string;\n readonly from?: number;\n}\n\nfunction plainText(node: MdfnNode): string {\n if (node.type === \"text\" || node.type === \"inlineCode\") return node.text ?? \"\";\n return (node.content ?? []).map(plainText).join(\"\");\n}\n\nexport interface AuthoringModel {\n readonly version: number;\n readonly mode: EditorMode;\n readonly compact: boolean;\n readonly toolbar: ToolbarModel;\n readonly bubble: ToolbarModel;\n readonly floating: ToolbarModel;\n readonly bubbleVisible: boolean;\n readonly floatingVisible: boolean;\n readonly slashOpen: boolean;\n readonly slashCommands: readonly SlashCommand[];\n readonly outline: readonly OutlineItem[];\n readonly diagnostics: readonly MdfnDiagnostic[];\n readonly comments: NonNullable<ReturnType<EditorController[\"getState\"]>[\"sidecar\"]>[\"comments\"];\n readonly suggestions: NonNullable<ReturnType<EditorController[\"getState\"]>[\"sidecar\"]>[\"suggestions\"];\n readonly reviewState: ReviewState;\n readonly audit: NonNullable<ReturnType<EditorController[\"getState\"]>[\"sidecar\"]>[\"audit\"];\n}\n\nexport function createAuthoringModel(\n controller: EditorController,\n options: { readonly mode?: EditorMode; readonly compact?: boolean; readonly slashQuery?: string; readonly slashOpen?: boolean; readonly commandTarget?: ToolbarCommandTarget | null } = {},\n): AuthoringModel {\n const state = controller.getState();\n const selection = state.selection?.kind === \"text\" ? state.selection : null;\n const selectionFrom = selection ? Math.min(selection.anchor, selection.head) : 0;\n const selectionTo = selection ? Math.max(selection.anchor, selection.head) : 0;\n const beforeCaret = selection && selectionFrom === selectionTo ? state.markdown.slice(state.markdown.lastIndexOf(\"\\n\", selectionFrom - 1) + 1, selectionFrom) : \"\";\n const slashMatch = /^\\s*\\/([^\\s/]*)$/.exec(beforeCaret);\n const inlineGroups = defaultToolbarGroups.filter((group) => group.id === \"inline\");\n const blockGroups = defaultToolbarGroups.filter((group) => group.id === \"blocks\");\n return {\n version: state.version,\n mode: options.mode ?? \"visual\",\n compact: options.compact ?? false,\n toolbar: createToolbarModel(controller, defaultToolbarGroups, \"Markdown authoring toolbar\", options.commandTarget),\n bubble: createToolbarModel(controller, inlineGroups, \"Selection formatting\", options.commandTarget),\n floating: createToolbarModel(controller, blockGroups, \"Block formatting\", options.commandTarget),\n bubbleVisible: Boolean(selection && selectionFrom !== selectionTo),\n floatingVisible: Boolean(selection && selectionFrom === selectionTo),\n slashOpen: options.slashOpen === true || Boolean(slashMatch),\n slashCommands: filterSlashCommands(options.slashQuery ?? slashMatch?.[1] ?? \"\"),\n outline: state.document.content.flatMap((node, index) => node.type === \"heading\" ? [{ id: node.id ?? `heading-${index}`, level: typeof node.attrs?.level === \"number\" ? node.attrs.level : 1, text: plainText(node), from: node.source?.from }] : []),\n diagnostics: state.diagnostics,\n comments: state.sidecar?.comments ?? [],\n suggestions: state.sidecar?.suggestions ?? [],\n reviewState: state.sidecar?.reviewState ?? \"draft\",\n audit: state.sidecar?.audit ?? [],\n };\n}\n\n/** Framework-neutral version descriptor used by every authoring adapter. */\nexport interface AuthoringVersion {\n readonly version: number;\n readonly createdAt?: string;\n readonly authorId?: string;\n readonly changeSource?: string;\n}\n\nexport function createToolbarModel(controller: EditorController, groups: readonly ToolbarGroup[] = defaultToolbarGroups, ariaLabel = \"Markdown formatting\", commandTarget?: ToolbarCommandTarget | null): ToolbarModel {\n const canRun = (command: string): boolean => commandTarget?.can?.(command) ?? controller.can(command);\n return {\n ariaLabel,\n groups: groups.map((group) => ({\n ...group,\n actions: group.actions.map((action) => ({\n ...action,\n disabled: action.id === \"undo\" ? !(commandTarget?.can?.(\"undo\") ?? controller.canUndo()) : action.id === \"redo\" ? !(commandTarget?.can?.(\"redo\") ?? controller.canRedo()) : action.command ? !canRun(action.command) : action.disabled,\n })),\n })),\n };\n}\n\nexport function runToolbarAction(controller: EditorController, action: ToolbarAction, commandTarget?: ToolbarCommandTarget | null): boolean {\n if (action.disabled) return false;\n if (commandTarget) return commandTarget.run(action.command ?? action.id);\n if (action.id === \"undo\") return controller.undo();\n if (action.id === \"redo\") return controller.redo();\n return action.command ? controller.run(action.command) : false;\n}\n\nexport const MDFN_COMPONENTS_VERSION = \"0.1.0\" as const;\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@mdfn/components",
3
+ "version": "0.1.0",
4
+ "description": "Framework-neutral editor toolbar and chrome models for mdfn",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.cjs" }, "./styles.css": "./styles.css" },
10
+ "scripts": {
11
+ "build": "tsup src/index.ts --format esm,cjs --dts --sourcemap --clean",
12
+ "test": "vitest run",
13
+ "typecheck": "tsc --noEmit -p tsconfig.json",
14
+ "lint": "tsc --noEmit -p tsconfig.json",
15
+ "prepack": "npm run build"
16
+ },
17
+ "dependencies": { "@mdfn/core": "0.1.0" },
18
+ "devDependencies": { "@mdfn/markdown": "0.1.0", "tsup": "^8.0.0", "typescript": "^5.3.0", "vitest": "3.2.4" },
19
+ "files": ["dist", "styles.css", "README.md"],
20
+ "sideEffects": ["styles.css"],
21
+ "license": "MIT",
22
+ "engines": { "node": ">=20 <25" },
23
+ "repository": { "type": "git", "url": "git+https://github.com/21nCo/super-functions.git", "directory": "mdfn/components" },
24
+ "publishConfig": { "access": "public" },
25
+ "mdfn": { "status": "stable-candidate", "layer": "components", "schemaVersion": 1 }
26
+ }
package/styles.css ADDED
@@ -0,0 +1,25 @@
1
+ [data-mdfn-component="toolbar"] { display: flex; flex-wrap: wrap; align-items: center; gap: .25rem; padding: .375rem; border-block-end: 1px solid var(--uifn-color-border, #d4d4d8); }
2
+ [data-mdfn-component="editor-shell"] { display: grid; min-block-size: 12rem; border: 1px solid var(--uifn-color-border, #d4d4d8); border-radius: .5rem; overflow: clip; background: var(--uifn-color-bg, #fff); color: var(--uifn-color-fg, #18181b); }
3
+ [data-mdfn-toolbar-group] { display: inline-flex; align-items: center; gap: .125rem; }
4
+ [data-mdfn-editor="visual"] { white-space: pre-wrap; }
5
+ [data-mdfn-surface] { min-inline-size: 0; padding: .75rem; }
6
+ [data-mode="split"] { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
7
+ [data-mdfn-component="authoring-chrome"] { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr)); gap: .5rem; padding: .5rem; border-block-end: 1px solid var(--uifn-color-border, #d4d4d8); }
8
+ [data-mdfn-component="authoring-chrome"] > [data-mdfn-surface="mode-switcher"], [data-mdfn-component="authoring-chrome"] > [data-mdfn-surface="bubble-toolbar"] { grid-column: 1 / -1; }
9
+ [data-mdfn-surface="bubble-toolbar"] { position: sticky; inset-block-start: 0; z-index: 2; background: var(--uifn-color-bg, #fff); box-shadow: 0 .25rem .75rem color-mix(in srgb, currentColor 12%, transparent); }
10
+ [data-mdfn-surface="outline"] [data-level="2"] { padding-inline-start: 1rem; }
11
+ [data-mdfn-surface="outline"] [data-level="3"] { padding-inline-start: 2rem; }
12
+ [data-mdfn-surface="diagnostics"] [data-severity="error"] { color: var(--uifn-color-danger, #b91c1c); }
13
+ [data-mdfn-surface="diagnostics"] [data-severity="warning"] { color: var(--uifn-color-warning, #92400e); }
14
+ [data-mdfn-component="authoring-chrome"] :focus-visible, [data-mdfn-editor]:focus-visible { outline: 2px solid var(--uifn-color-focus, #2563eb); outline-offset: 2px; }
15
+ @media (max-width: 48rem) {
16
+ [data-mode="split"] { grid-template-columns: 1fr; }
17
+ [data-mdfn-component="authoring-chrome"] { grid-template-columns: 1fr; max-block-size: 45vh; overflow: auto; overscroll-behavior: contain; }
18
+ [data-mdfn-component="toolbar"] { overflow-x: auto; flex-wrap: nowrap; }
19
+ }
20
+ @media (prefers-contrast: more) {
21
+ [data-mdfn-component="editor-shell"], [data-mdfn-component="toolbar"] { border-width: 2px; }
22
+ }
23
+ @media (prefers-reduced-motion: reduce) {
24
+ [data-mdfn-component="editor-shell"] * { scroll-behavior: auto !important; transition-duration: .01ms !important; animation-duration: .01ms !important; }
25
+ }