@mikedown/web-component 0.0.0-stage → 0.3.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 ADDED
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0
4
+
5
+ First release. `<mikedown-editor>`, the MikeDown markdown editor as a custom element built on `@mikedown/core`'s `bindMikeDownValue`.
6
+
7
+ - Light DOM element; `@mikedown/web-component` exports `MikeDownElement` and `defineMikeDownElement(tag)`, `@mikedown/web-component/define` registers `<mikedown-editor>`.
8
+ - Form-associated: `name`, `required` (`valueMissing`), reset, disabled fieldsets, state restore, `checkValidity()` / `reportValidity()`.
9
+ - Reflected attributes `value`, `theme`, `readonly`, `disabled`, `placeholder`, `name`, `required`, `min-height`, `dark-selector`, `spellcheck-dictionary-url`, `features`, `debounce`; properties `value`, `settings`, `host`, `handle`.
10
+ - `change` (debounced, at once on blur) and `ready` events.
11
+ - Progressive enhancement from a child `<textarea>` or `<script type="text/markdown">`.
12
+ - `<label for>`, `aria-label` and `aria-labelledby` name the editable surface.
13
+ - `dist/mikedown-editor.standalone.js`: a no-build CDN bundle with core, its peers and CSS; KaTeX and Mermaid load from jsDelivr on first use. Third-party licenses in `THIRD_PARTY_NOTICES.md`.
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Michael Joseph
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 CHANGED
@@ -1,3 +1,263 @@
1
- # Temporary Holding Version
1
+ # @mikedown/web-component
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `<mikedown-editor>`: the MikeDown WYSIWYG markdown editor as a form-associated custom element. It is the route for plain HTML pages, CDN and no-build pages, htmx, server-rendered templates (Rails, Django, Razor, Laravel), and frameworks without a dedicated MikeDown package (Lit, Svelte, Solid, Preact, Blazor). React, Vue and Angular have their own packages.
4
+
5
+ ```html
6
+ <form method="post" action="/posts">
7
+ <label for="body">Body</label>
8
+ <mikedown-editor id="body" name="body" required></mikedown-editor>
9
+ <button>Save</button>
10
+ </form>
11
+ ```
12
+
13
+ It submits like a `<textarea>`: the markdown goes out under its `name`, `required` blocks an empty submit, and a reset puts the initial text back.
14
+
15
+ - Light DOM (no shadow root), so page CSS and `--mikedown-*` tokens reach the editor.
16
+ - ESM only, typed, telemetry-free.
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ npm install @mikedown/web-component @mikedown/core
22
+ ```
23
+
24
+ `@mikedown/core` is a peer dependency, and its own peers (TipTap, CodeMirror, lowlight, KaTeX) install with it on npm 7+ and pnpm 8+. Yarn does not install peers, so list them:
25
+
26
+ ```bash
27
+ yarn add @mikedown/web-component @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
28
+ ```
29
+
30
+ Or skip all of it with the CDN bundle below.
31
+
32
+ ## Use with a bundler
33
+
34
+ ```js
35
+ import '@mikedown/core/styles.css';
36
+ import '@mikedown/web-component/define'; // registers <mikedown-editor>
37
+ ```
38
+
39
+ Or register it yourself, under another tag if you like:
40
+
41
+ ```js
42
+ import '@mikedown/core/styles.css';
43
+ import { defineMikeDownElement, MikeDownElement } from '@mikedown/web-component';
44
+
45
+ defineMikeDownElement(); // <mikedown-editor>
46
+ defineMikeDownElement('md-field'); // also <md-field>
47
+ ```
48
+
49
+ `defineMikeDownElement` is safe to call more than once. Only `@mikedown/web-component/define` has side effects; importing the package root defines nothing.
50
+
51
+ ## Use from a CDN (no build step)
52
+
53
+ One `<script type="module">` loads a self-contained bundle: core, its peers and its CSS, and it defines `<mikedown-editor>` on load. Pin the version; never use `@latest`.
54
+
55
+ ```html
56
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@mikedown/web-component@0.2.0/dist/mikedown-editor.standalone.js"></script>
57
+
58
+ <mikedown-editor name="notes" value="# Hello"></mikedown-editor>
59
+ ```
60
+
61
+ unpkg works the same way:
62
+
63
+ ```html
64
+ <script type="module" src="https://unpkg.com/@mikedown/web-component@0.2.0/dist/mikedown-editor.standalone.js"></script>
65
+ ```
66
+
67
+ About the bundle:
68
+
69
+ - It is about 815 kB gzipped up front (the budget is 850 kB). Most of that is syntax highlighting for every language highlight.js knows.
70
+ - It is split into a few files next to each other in `dist/`: the entry, a chunk it imports at once, and the AI UI (about 56 kB gzipped), which loads only when an element gets an `ai` host. The one `<script>` tag above is all a page needs; the browser fetches the chunks from the same folder. To self-host, copy every `dist/mikedown-editor.*.js` file, not only the entry.
71
+ - The stylesheet goes into one `<style id="mikedown-styles">` at the start of `<head>`, once per page, so your own stylesheets come later and win. Don't also load `@mikedown/core/styles.css`.
72
+ - KaTeX and Mermaid are not bundled. The first formula loads KaTeX (and one `<link>` to its CSS) from jsDelivr, the first diagram loads Mermaid, both pinned to the versions the bundle was built with. Turn either off with `features='{"math":false}'` or `features='{"mermaid":false}'` and nothing is fetched.
73
+ - To use the module's exports (`MikeDownElement`, `defineMikeDownElement`), import the same URL: `import { MikeDownElement } from 'https://cdn.jsdelivr.net/npm/@mikedown/web-component@0.2.0/dist/mikedown-editor.standalone.js'`.
74
+ - It redistributes TipTap, ProseMirror, CodeMirror, lowlight, highlight.js, markdown-it and friends; their licenses are in [`THIRD_PARTY_NOTICES.md`](./THIRD_PARTY_NOTICES.md), shipped in the package.
75
+
76
+ Spell checking needs dictionaries. Point `spellcheck-dictionary-url` at the folder that holds `en/` and `en-GB/` (core appends the language), for example core's copy on jsDelivr:
77
+
78
+ ```html
79
+ <mikedown-editor spellcheck-dictionary-url="https://cdn.jsdelivr.net/npm/@mikedown/core@0.2.0/dist/dictionaries"></mikedown-editor>
80
+ ```
81
+
82
+ ## Attributes
83
+
84
+ Every attribute has a matching property; setting either updates the other.
85
+
86
+ | Attribute | Property | Default | |
87
+ |---|---|---|---|
88
+ | `value` | `defaultValue` | `''` | Initial markdown and the form-reset target. Changing it later replaces the content. |
89
+ | `name` | `name` | | Form field name. |
90
+ | `required` | `required` | off | Empty (or whitespace only) is `valueMissing`. |
91
+ | `disabled` | `disabled` | off | Read-only and left out of the form submission. A disabled `<fieldset>` does the same. |
92
+ | `readonly` | `readOnly` | off | Read-only; still submitted. |
93
+ | `placeholder` | `placeholder` | core's | Text shown in an empty editor. |
94
+ | `theme` | `theme` | `auto` | `light`, `dark` or `auto`. |
95
+ | `dark-selector` | `darkSelector` | | With `auto`: dark when `element.closest(selector)` matches (`html.dark`, `html[data-mode=dark]`, `.dark-theme`); without it `auto` follows `prefers-color-scheme`. |
96
+ | `min-height` | `minHeight` | `240px` | Any CSS length; a bare number means pixels. Sets `--mikedown-min-height`. |
97
+ | `spellcheck-dictionary-url` | `spellcheckDictionaryUrl` | | Folder base of the Hunspell dictionaries. Spell checking stays off without it. |
98
+ | `features` | `features` | all on but `sidebar` | JSON object of feature switches: `toolbar`, `sidebar`, `sourceMode`, `slashCommands`, `spellcheck`, `math`, `mermaid`, `findReplace`, `frontmatter`. |
99
+ | `debounce` | `debounce` | `150` | Milliseconds of quiet before `change` fires. |
100
+ | `aria-label`, `aria-labelledby` | | | Forwarded to the editable surface. |
101
+
102
+ `placeholder`, `features`, `dark-selector`, `spellcheck-dictionary-url`, `debounce` and the ARIA attributes are read when the editor is created; changing one rebuilds the editor and keeps the text.
103
+
104
+ ## Properties
105
+
106
+ | Property | Type | |
107
+ |---|---|---|
108
+ | `value` | `string` | The current markdown, updated on every keystroke. Setting it loads the text as the clean baseline and fires no `change`. |
109
+ | `settings` | `Partial<MikeDownSettings>` | Core settings (`fontFamily`, `fontSize`, `contentWidth`, `spellCheckLanguage`, ...), applied live. |
110
+ | `host` | `Partial<MikeDownHost>` | Host methods: `uploadImage`, `openLink`, `resolveImageSrc`, `pickImage`, `getLinkSuggestions`, ... Core turns a feature on only when its method is present. Setting it rebuilds the editor. |
111
+ | `ai` | `MikeDownAiHost` | The AI host group (`complete`, and for tier 2 `listEngines`, ...), passed to the core untouched. A property only, no attribute; `host.ai` works too. Setting it rebuilds the editor. See [Bring your own AI](https://github.com/mikejoseph23/mikedown/tree/main/packages/core#bring-your-own-ai). |
112
+ | `handle` | `MikeDownEditorHandle \| null` | Read only. The core editor handle while connected (`command`, `getHtml`, `insertImage`, ...). |
113
+ | `form`, `labels`, `validity`, `validationMessage`, `willValidate` | | As on native form controls. |
114
+
115
+ Methods: `focus()`, `checkValidity()`, `reportValidity()`.
116
+
117
+ **Toolbar layout.** Users can reorder, remove and add toolbar buttons in the editor (Settings, then Toolbar). The result is one JSON value, `settings.toolbarLayout` (`{ version, items, seen }`, typed `ToolbarLayout`). Pass it in `settings` to preset or restore a layout, and store the value your `host.saveSettings` receives; `null` means Reset to Default, and an absent or invalid value shows the default toolbar. Set `features.customizeToolbar: false` to hide the customize controls; a `toolbarLayout` you pass still applies. `ToolbarLayout`, `ToolbarItemId`, `TOOLBAR_ITEM_IDS`, `DEFAULT_TOOLBAR_LAYOUT` and `normalizeToolbarLayout` are exported from `@mikedown/core`.
118
+
119
+ Function-valued options (`host`) and objects (`settings`) are properties only:
120
+
121
+ ```js
122
+ const editor = document.querySelector('mikedown-editor');
123
+ editor.settings = { fontFamily: 'Georgia, serif', contentWidth: '720px' };
124
+ editor.host = {
125
+ async uploadImage({ bytes, mime, name }) {
126
+ const res = await fetch('/uploads', { method: 'POST', headers: { 'Content-Type': mime }, body: bytes });
127
+ return { src: (await res.json()).url, alt: name };
128
+ },
129
+ openLink(href) { window.open(href, '_blank', 'noopener'); },
130
+ };
131
+ ```
132
+
133
+ ## Events
134
+
135
+ Both bubble and are composed.
136
+
137
+ | Event | `detail` | When |
138
+ |---|---|---|
139
+ | `change` | `{ value }` | After the user stops typing (`debounce`, 150 ms), and at once when focus leaves the editor. Not for programmatic `value` writes. |
140
+ | `ready` | `{ handle }` | A microtask after the editor is created: on connect, and again after a rebuild or a move in the DOM. |
141
+
142
+ ```js
143
+ editor.addEventListener('change', (e) => save(e.detail.value));
144
+ ```
145
+
146
+ Native `change` events from inputs inside the editor (the find bar, dialogs) are stopped at the element, so a `change` listener only hears the element's own.
147
+
148
+ ## Forms
149
+
150
+ The element is form-associated (`ElementInternals`), so `new FormData(form)`, a plain submit, `form.reset()`, `form.reportValidity()`, `:invalid`, a disabled `<fieldset>` and the browser's back/forward form restore all work as they do for a `<textarea>`.
151
+
152
+ - **Value:** the form value is kept current on every keystroke, so a submit right after typing sends the latest text.
153
+ - **Reset:** goes back to the `value` attribute, or to the server-rendered text below.
154
+ - **Required:** an empty editor reports `valueMissing`; `reportValidity()` points the browser's message at the editor.
155
+ - **Without form internals** (jsdom in tests, very old browsers) the element falls back to a hidden `<input>` with the same name, so `FormData` still sees the value.
156
+
157
+ ## Server-rendered pages (progressive enhancement)
158
+
159
+ Render a `<textarea>` inside the element. Without JavaScript the textarea is the field. Once the element upgrades, it takes the textarea's text as its value, takes over its `name`, `required` and `placeholder`, and hides and disables it so the value is submitted once.
160
+
161
+ ```erb
162
+ <%# Rails %>
163
+ <mikedown-editor>
164
+ <%= f.text_area :body %>
165
+ </mikedown-editor>
166
+ ```
167
+
168
+ ```django
169
+ {# Django #}
170
+ <mikedown-editor>{{ form.body }}</mikedown-editor>
171
+ ```
172
+
173
+ ```cshtml
174
+ @* Razor *@
175
+ <mikedown-editor>
176
+ <textarea asp-for="Body"></textarea>
177
+ </mikedown-editor>
178
+ ```
179
+
180
+ For read-mostly pages, `<script type="text/markdown">` works too; its template indentation is removed:
181
+
182
+ ```html
183
+ <mikedown-editor name="notes">
184
+ <script type="text/markdown">
185
+ # Meeting notes
186
+
187
+ - Decide on the launch date
188
+ </script>
189
+ </mikedown-editor>
190
+ ```
191
+
192
+ A `value` attribute wins over child content. Use a textarea when the text may contain `</script>`.
193
+
194
+ ## htmx
195
+
196
+ `change` bubbles, so a form can save as the user types:
197
+
198
+ ```html
199
+ <form hx-post="/notes/42" hx-trigger="change delay:500ms" hx-swap="none">
200
+ <mikedown-editor name="body" value="# Notes"></mikedown-editor>
201
+ </form>
202
+ ```
203
+
204
+ htmx reads the form with `FormData`, which includes the element's value. Swapping in HTML that contains `<mikedown-editor>` creates the editor when it connects and destroys the one that was swapped out.
205
+
206
+ ## Labels and accessibility
207
+
208
+ - `<label for="id">` names the editor and focuses it on click. The element keeps its own `id`; the label is linked to the editable surface through `aria-labelledby`. Prefer `<label for>` to a wrapping `<label>`.
209
+ - The accessible name of the editable surface follows ARIA's order: `aria-labelledby`, then `aria-label`, then the element's `<label>`s.
210
+ - The surface has `role="textbox"` and `aria-multiline="true"`. The editor is not a page landmark.
211
+
212
+ ## Styling
213
+
214
+ - The element is `display: block` (set at zero specificity, so any rule of yours wins). The editor fills the element; give it a height, or rely on `min-height` (240px by default).
215
+ - Theme the editor with the `--mikedown-*` tokens from `@mikedown/core/tokens.css`, for example `mikedown-editor { --mikedown-font-ui: Inter, sans-serif; --mikedown-surface: #fff; --mikedown-text: #111; }`. See the [core README](https://github.com/mikejoseph23/mikedown/tree/main/packages/core) for the full list. The document font and size come from `settings` (`fontFamily`, `headingFontFamily`, `fontSize`).
216
+ - `dark-selector` follows your app's dark mode: `<mikedown-editor dark-selector="html.dark">`.
217
+ - CSS resets: the editor sets its own list markers, heading sizes and margins, so a reset such as Tailwind's preflight (`ul, ol { list-style: none }`) does not reach editor content. If a reset still changes colors or type, override the tokens above.
218
+
219
+ ## Frameworks
220
+
221
+ The element works anywhere custom elements do. Set `value`, `settings`, `host` and `features` as properties where the framework allows it:
222
+
223
+ ```html
224
+ <!-- Lit -->
225
+ <mikedown-editor .value=${this.body} .host=${this.host} @change=${(e) => (this.body = e.detail.value)}></mikedown-editor>
226
+
227
+ <!-- Svelte -->
228
+ <mikedown-editor value={body} on:change={(e) => (body = e.detail.value)}></mikedown-editor>
229
+ ```
230
+
231
+ React, Vue and Angular: use `@mikedown/react`, `@mikedown/vue` or `@mikedown/angular`, which wrap core directly.
232
+
233
+ ## Testing
234
+
235
+ Use vitest with jsdom (the element falls back to a hidden `<input>` without form internals). 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.
236
+
237
+ ## Known limitations
238
+
239
+ - **Several editors on one page share some state.** Core's menus and dialogs live in one `.mikedown-portal` that follows the last-focused editor, Mermaid's theme is set page-wide, and turning `math` off in one editor's `features` turns KaTeX rendering off for every editor on the page. Give editors on the same page the same `math` switch.
240
+ - **Moving the element** in the DOM destroys and recreates the editor (keeping the text, and reporting a pending edit as `change` first). Undo history and the caret do not survive the move.
241
+
242
+ ## Theming
243
+
244
+ Colors, fonts and radii come from `--mikedown-*` custom properties, set on the editor or any ancestor. The token list, light and dark defaults and the portal note are in the [`@mikedown/core` README](https://www.npmjs.com/package/@mikedown/core#theming).
245
+
246
+ ## Browser support
247
+
248
+ Current Chrome, Edge, Firefox and Safari (ES2020 output, ESM only). The element itself needs form-associated custom elements: Chrome 77+, Firefox 98+, Safari 16.4+.
249
+
250
+ ## Privacy
251
+
252
+ No telemetry. The editor makes no network calls except the ones you configure: spell check dictionaries (when you set a dictionary URL), your `uploadImage` / `host` callbacks, and the lazy `import()` of KaTeX and Mermaid from your own bundle (the CDN bundle loads KaTeX and Mermaid from jsDelivr instead; turn them off in `features` and nothing is fetched).
253
+
254
+ ## Links
255
+
256
+ - [Live demo](https://www.iadev.net/mikedown/developers/web-component/)
257
+ - [Developers hub](https://www.iadev.net/mikedown/developers/)
258
+ - [npm](https://www.npmjs.com/package/@mikedown/web-component)
259
+ - [GitHub](https://github.com/mikejoseph23/mikedown/tree/main/packages/web-component)
260
+
261
+ ## License
262
+
263
+ MIT. The standalone bundle includes third-party code under MIT, ISC, BSD-2-Clause and BSD-3-Clause licenses; see [`THIRD_PARTY_NOTICES.md`](./THIRD_PARTY_NOTICES.md).