@mikedown/core 0.1.0 → 0.2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0
4
+
5
+ - `bindMikeDownValue` (`@mikedown/core/binding`): the framework-neutral value binding the React, Vue, Angular and web component wrappers are built on (pristine writes, debounced change, flush on blur, read-only, disabled and touched state).
6
+ - Editor options for `placeholder`, `id` and ARIA naming, and a landmark role on the editor.
7
+ - The bound theme is re-applied only when it changes, which ends a theme update loop.
8
+ - List markers are set explicitly, so page CSS resets can no longer strip bullets and numbers.
9
+ - Completed task text uses the muted text color, keeping at least 4.5:1 contrast in light and dark themes.
10
+ - Task checkboxes are named after their item for screen readers and have a 24 by 24px click and touch target.
11
+ - Toolbar buttons that show text have screen reader names that start with that text; toolbar icons are hidden from assistive technology.
12
+ - The floating table toolbar shows only while a focused cursor is in a table.
13
+ - Toolbar buttons that don't fit move into a More (⋯) menu at the end of the toolbar instead of being cut off.
14
+
3
15
  ## 0.1.0
4
16
 
5
17
  First public release. The MikeDown WYSIWYG markdown editor as a framework-agnostic package, the same editor that ships in the MikeDown VS Code extension and MikeDown Desktop.
package/README.md CHANGED
@@ -8,7 +8,7 @@ The MikeDown WYSIWYG markdown editor as a framework-agnostic package: TipTap (Pr
8
8
  npm install @mikedown/core
9
9
  ```
10
10
 
11
- Install the peer dependencies too (see [Peer dependencies](#peer-dependencies)). The package is ESM only.
11
+ npm 7+ and pnpm 8+ install the peer dependencies for you (see [Peer dependencies](#peer-dependencies)); Yarn does not, so use the `yarn add` line under [Framework wrappers](#framework-wrappers). The package is ESM only.
12
12
 
13
13
  ## Minimal example
14
14
 
@@ -33,6 +33,36 @@ handle.destroy();
33
33
 
34
34
  The editor fills its container (`height: 100%`), so give the container a height. Without one the editor falls back to a `240px` minimum height; set `--mikedown-min-height` to change that floor.
35
35
 
36
+ ## Binding a value
37
+
38
+ `bindMikeDownValue` is the glue a form field or framework wrapper needs, in one call. Import it from the package root or from `@mikedown/core/binding`.
39
+
40
+ ```ts
41
+ import { bindMikeDownValue } from '@mikedown/core/binding';
42
+
43
+ const field = bindMikeDownValue(container, {
44
+ initialValue: '# Hello',
45
+ debounceMs: 150,
46
+ onValue: (markdown) => save(markdown),
47
+ theme: 'auto',
48
+ darkSelector: 'html[data-mode=dark]',
49
+ id: 'notes',
50
+ ariaLabel: 'Meeting notes',
51
+ placeholder: 'Start writing…',
52
+ });
53
+
54
+ field.setValue(fromServer); // loads pristine; skipped when equal to the current value
55
+ field.flush(); // report a pending change now
56
+ field.setTheme('dark');
57
+ field.focus();
58
+ field.handle; // the MikeDownEditorHandle
59
+ field.destroy(); // flushes, then removes observers and listeners
60
+ ```
61
+
62
+ - **Change reporting.** `onValue` fires after `debounceMs` of quiet and at once on blur. `outputTransform(markdown)` maps the text on its way out; `setValue` compares against its output.
63
+ - **Theme.** `auto` is dark when the container or an ancestor matches `darkSelector` (it uses `closest()`, so both `html[data-mode=dark]` and a `.dark-theme` wrapper work), re-checked when attributes change. Without a selector it follows `prefers-color-scheme`.
64
+ - **Accessibility.** `id`, `ariaLabel` and `ariaLabelledBy` go on the editable surface in both views (rich text and source). With only an `id`, the binding looks up `label[for=id]`, uses it as the name and focuses the editor when it is clicked. `ariaLabelledBy` is the sturdier choice. The accessible name follows ARIA's order: `aria-labelledby`, then `aria-label`, then `<label for>` elements. A bound editor drops `role="main"` (`landmark: false`), since a field is not a page landmark; the same `landmark`, `placeholder`, `id`, `ariaLabel` and `ariaLabelledBy` options exist on `createMikeDownEditor`.
65
+
36
66
  ## Host interface
37
67
 
38
68
  `options.host` is a `MikeDownHost`. Only `onChange` is required. Every other method is optional, and leaving one out turns its feature off and hides its UI. `NoopHost` is a ready-made host with just `onChange`.
@@ -121,7 +151,7 @@ The app must have exactly one copy of ProseMirror and CodeMirror, so these are p
121
151
 
122
152
  `tiptap-markdown`, `markdown-it-emoji`, `markdown-it-footnote`, `markdown-it-mark` and `nspell` are regular dependencies and install with the package.
123
153
 
124
- `katex` is a required peer because `styles.css` imports its stylesheet. `mermaid` is optional. Both are loaded with dynamic `import()` the first time a diagram or formula appears, so they stay out of your initial bundle. Without `mermaid` the diagram block shows an error or the raw source.
154
+ `katex` is a required peer because `styles.css` imports its stylesheet. `mermaid` is optional. Both are loaded with dynamic `import()` the first time a diagram or formula appears, so they stay out of your initial bundle. Without `mermaid` the diagram block shows an error or the raw source. Install it anyway for Angular and Next.js (Turbopack) builds, which resolve the `import('mermaid')` at build time and fail without it; Vite builds pass.
125
155
 
126
156
  ## Bundler notes
127
157
 
@@ -134,6 +164,23 @@ The app must have exactly one copy of ProseMirror and CodeMirror, so these are p
134
164
 
135
165
  Mermaid renders SVG with `securityLevel: 'strict'` and loads its diagram types by dynamic `import()`. Your CSP needs `script-src 'self'` (or wherever your chunks are served from) and must allow inline styles for the SVG output (`style-src 'unsafe-inline'`, or a nonce on the style elements your bundler injects). A page that cannot allow this should set `features: { mermaid: false }`. KaTeX needs no script or eval permissions, only its stylesheet and fonts.
136
166
 
167
+ ## Framework wrappers
168
+
169
+ - [`@mikedown/web-component`](https://www.npmjs.com/package/@mikedown/web-component): `<mikedown-editor>`, a form-associated custom element for plain HTML, CDN pages and server-rendered templates.
170
+ - [`@mikedown/react`](https://www.npmjs.com/package/@mikedown/react): `MikeDownEditor` for React 18 and 19.
171
+ - [`@mikedown/vue`](https://www.npmjs.com/package/@mikedown/vue): `MikeDownEditor` with `v-model` for Vue 3.3+.
172
+ - [`@mikedown/angular`](https://www.npmjs.com/package/@mikedown/angular): a form control for Angular 17+.
173
+
174
+ Each wrapper depends only on core and its framework. Yarn does not install peers, so list core's:
175
+
176
+ ```bash
177
+ yarn add @mikedown/core @codemirror/commands @codemirror/lang-markdown @codemirror/language @codemirror/state @codemirror/theme-one-dark @codemirror/view @tiptap/core @tiptap/extension-code-block-lowlight @tiptap/extension-hard-break @tiptap/extension-highlight @tiptap/extension-image @tiptap/extension-link @tiptap/extension-placeholder @tiptap/extension-table @tiptap/extension-table-cell @tiptap/extension-table-header @tiptap/extension-table-row @tiptap/extension-task-item @tiptap/extension-task-list @tiptap/extension-text @tiptap/pm @tiptap/starter-kit katex lowlight mermaid
178
+ ```
179
+
180
+ ## Testing
181
+
182
+ Use vitest with jsdom, stubbing `Range.prototype.getBoundingClientRect` and `getClientRects`. Jest: add `transformIgnorePatterns: ['/node_modules/(?!(@mikedown|lowlight|devlop)/)']` so the ESM-only packages in core's import graph get transformed (TipTap, CodeMirror and `tiptap-markdown` ship `require` builds). vitest is the recommended runner.
183
+
137
184
  ## Demo
138
185
 
139
186
  From a clone of the repository:
@@ -144,6 +191,20 @@ npm run demo:core
144
191
 
145
192
  This builds the package and serves `packages/core/demo/` with two editors side by side, a theme toggle, a "Log markdown" button and an `uploadImage` stub that returns an object URL.
146
193
 
194
+ ## Browser support
195
+
196
+ Current Chrome, Edge, Firefox and Safari (ES2020 output, ESM only).
197
+
198
+ ## Privacy
199
+
200
+ No telemetry. The editor makes no network calls except the ones you configure: spell check dictionaries (when `spellcheckDictionaryUrl` is set), your `host` callbacks, and the lazy `import()` of KaTeX and Mermaid from your own bundle.
201
+
202
+ ## Links
203
+
204
+ - [Developers hub](https://www.iadev.net/mikedown/developers/)
205
+ - [npm](https://www.npmjs.com/package/@mikedown/core)
206
+ - [GitHub](https://github.com/mikejoseph23/mikedown/tree/main/packages/core)
207
+
147
208
  ## Build
148
209
 
149
210
  ```bash
package/dist/index.d.ts CHANGED
@@ -353,6 +353,19 @@ interface MikeDownOptions {
353
353
  * the rich editor and source mode.
354
354
  */
355
355
  hostOwnsShortcuts?: boolean;
356
+ /** Empty-document placeholder. Default: "Type / for commands…" or "Start writing…". */
357
+ placeholder?: string;
358
+ /** `id` of the editable surface (the contenteditable, and the source view's content). */
359
+ id?: string;
360
+ /** Accessible name of the editable surface. Default "Document text". */
361
+ ariaLabel?: string;
362
+ /** Id of the element that names the editable surface. Wins over `ariaLabel`'s default. */
363
+ ariaLabelledBy?: string;
364
+ /**
365
+ * Put `role="main"` on the editor container. Default true (VS Code, Desktop).
366
+ * Embedded form fields pass false, since a field is not a page landmark.
367
+ */
368
+ landmark?: boolean;
356
369
  }
357
370
  type MikeDownCommand = 'toggleBold' | 'toggleItalic' | 'toggleStrike' | 'toggleHighlight' | 'toggleCode' | 'toggleBulletList' | 'toggleOrderedList' | 'toggleTaskList' | 'pasteWithoutFormatting' | 'openEmojiPicker' | 'undo' | 'redo' | 'toggleSource' | 'toggleSidebar' | 'openSettings' | 'copyAsRichText' | 'heading1' | 'heading2' | 'heading3' | 'heading4' | 'heading5' | 'heading6' | 'paragraph' | 'openFind' | 'openFindReplace' | 'insertTable' | 'insertLink' | 'insertImage' | 'zoomIn' | 'zoomOut' | 'zoomReset';
358
371
  type MikeDownEvent = 'change' | 'focus' | 'blur' | 'ready';
@@ -455,5 +468,57 @@ interface MikeDownEditorHandle {
455
468
 
456
469
  declare function createMikeDownEditor(container: HTMLElement, options: MikeDownOptions): MikeDownEditorHandle;
457
470
 
458
- export { DEFAULT_FEATURES, NoopHost, createMikeDownEditor };
459
- export type { BacklinkItem, DiffBaseline, ImageInsertResult, ImagePathMapping, ImageResizeRequest, ImageResizeResult, ImageUploadRequest, LinkCheckRequest, LinkOpenBehavior, LinkSuggestion, MikeDownCommand, MikeDownEditorHandle, MikeDownEvent, MikeDownFeatures, MikeDownHost, MikeDownOptions, MikeDownSettings, MikeDownSettingsPatch, MikeDownSettingsSection, MikeDownSettingsSectionBuild, MikeDownSettingsSectionContext, MikeDownSidebarPrefs, MikeDownStats, MikeDownViewState, NormalizationStyle, SidebarFooterLink, SidebarStateUpdate, TagCount, WikilinkResolution };
471
+ /**
472
+ * Value binding for framework wrappers. Every wrapper (React, Vue, Angular,
473
+ * the web component) needs the same glue around the editor: a debounced change
474
+ * callback that reports at once on blur, external writes that load pristine and
475
+ * skip when nothing changed, a theme that can follow the page, and an accessible
476
+ * name for the editable surface. `bindMikeDownValue` holds it once.
477
+ */
478
+
479
+ type BindingTheme = 'light' | 'dark' | 'auto';
480
+ interface MikeDownBindingOptions extends Omit<MikeDownOptions, 'host' | 'initialMarkdown' | 'theme'> {
481
+ /** Loaded as the pristine baseline. Default ''. */
482
+ initialValue?: string;
483
+ /** Quiet time after the last keystroke before `onValue` fires. Default 150. 0 reports at once. */
484
+ debounceMs?: number;
485
+ /** Called with the markdown after the debounce, and at once on blur. Not called when the text equals the last value. */
486
+ onValue?: (markdown: string) => void;
487
+ /**
488
+ * Called when focus leaves the whole control (editor and toolbar), like a native input's blur. Moving into the
489
+ * core's menus and dialogs (`.mikedown-portal`) does not count. A pending change is flushed first.
490
+ */
491
+ onTouched?: () => void;
492
+ /** Start disabled. A disabled binding is read-only too. */
493
+ disabled?: boolean;
494
+ /** Default 'auto'. */
495
+ theme?: BindingTheme;
496
+ /**
497
+ * CSS selector for 'auto': dark when the container, or an ancestor, matches
498
+ * (`html[data-mode=dark]`, `.dark-theme`). Without it 'auto' follows
499
+ * `prefers-color-scheme`.
500
+ */
501
+ darkSelector?: string;
502
+ /** Maps the markdown on its way out (e.g. to preserve brackets). `setValue` compares against its output. */
503
+ outputTransform?: (markdown: string) => string;
504
+ /** Optional host methods (image upload, wikilinks, ...). `onChange` is run before the binding's own. */
505
+ host?: Partial<MikeDownHost>;
506
+ }
507
+ interface MikeDownBinding {
508
+ /** The underlying editor handle. */
509
+ readonly handle: MikeDownEditorHandle;
510
+ /** Load an external value as the pristine baseline. Skipped when it equals the current value. */
511
+ setValue(markdown: string): void;
512
+ /** Report a pending change now. */
513
+ flush(): void;
514
+ setTheme(theme: BindingTheme): void;
515
+ /** Read-only is `readOnly || disabled`. */
516
+ setReadOnly(readOnly: boolean): void;
517
+ setDisabled(disabled: boolean): void;
518
+ focus(): void;
519
+ destroy(): void;
520
+ }
521
+ declare function bindMikeDownValue(container: HTMLElement, options?: MikeDownBindingOptions): MikeDownBinding;
522
+
523
+ export { DEFAULT_FEATURES, NoopHost, bindMikeDownValue, createMikeDownEditor };
524
+ export type { BacklinkItem, BindingTheme, DiffBaseline, ImageInsertResult, ImagePathMapping, ImageResizeRequest, ImageResizeResult, ImageUploadRequest, LinkCheckRequest, LinkOpenBehavior, LinkSuggestion, MikeDownBinding, MikeDownBindingOptions, MikeDownCommand, MikeDownEditorHandle, MikeDownEvent, MikeDownFeatures, MikeDownHost, MikeDownOptions, MikeDownSettings, MikeDownSettingsPatch, MikeDownSettingsSection, MikeDownSettingsSectionBuild, MikeDownSettingsSectionContext, MikeDownSidebarPrefs, MikeDownStats, MikeDownViewState, NormalizationStyle, SidebarFooterLink, SidebarStateUpdate, TagCount, WikilinkResolution };