@rcarls/rc-markdown-editor 0.2.0 → 0.3.1

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.
@@ -1,5 +1,5 @@
1
1
  import "@rcarls/rc-textarea/define";
2
- import { R as o, a as t } from "./rc-editor-toolbar-B74gMsna.js";
2
+ import { R as o, a as t } from "./rc-editor-toolbar-CcTLdYIt.js";
3
3
  customElements.get("rc-editor-toolbar") ?? customElements.define("rc-editor-toolbar", o);
4
4
  customElements.get("rc-markdown-editor") ?? customElements.define("rc-markdown-editor", t);
5
5
  //# sourceMappingURL=rc-markdown-editor-define.js.map
@@ -1,4 +1,4 @@
1
- import { R as a, a as d } from "./rc-editor-toolbar-B74gMsna.js";
1
+ import { R as a, a as d } from "./rc-editor-toolbar-CcTLdYIt.js";
2
2
  export {
3
3
  a as RcEditorToolbar,
4
4
  d as RcMarkdownEditor
@@ -1,5 +1,15 @@
1
1
  import { Decoration } from '@rcarls/rc-textarea';
2
2
  import { ActiveFormats } from './types.ts';
3
+ /**
4
+ * Replaces the language token on the opening fence of the code block containing
5
+ * `selectionStart`. Returns the updated string, or `null` if the cursor is not
6
+ * inside a fenced code block.
7
+ *
8
+ * @param value - Full source editor value.
9
+ * @param selectionStart - Cursor offset used to locate the containing fence.
10
+ * @param language - Language string to write (empty string removes the token).
11
+ */
12
+ export declare function setCodeBlockLanguage(value: string, selectionStart: number, language: string): string | null;
3
13
  /**
4
14
  * Derives active inline and block formats from the decoration array at a given
5
15
  * selection range in the markdown source editor. Used for toolbar state sync in
@@ -1,12 +1,12 @@
1
1
  import { LitElement } from 'lit';
2
- import { HeadingLevel } from './types.ts';
2
+ import { EditorToolbarAction, EditorToolbarActionDetail, HeadingLevel } from './types.ts';
3
3
  /**
4
4
  * Formatting toolbar for `<rc-markdown-editor>`. Renders into its own light DOM
5
5
  * so the parent shadow stylesheet can reach `rc-editor-toolbar button` directly.
6
6
  *
7
7
  * Active-format properties are set by the parent editor and reflected as
8
- * `aria-pressed` on the corresponding buttons. Heading level is shown via a
9
- * `<select>`. Code-block language is shown via a `<input>` when active.
8
+ * `aria-pressed` on the corresponding buttons. Heading level is shown via an
9
+ * `<rc-select>`. Code-block language is shown via a `<input>` when active.
10
10
  *
11
11
  * @fires rc-toolbar-action - When a formatting button or select is activated.
12
12
  */
@@ -18,6 +18,7 @@ export declare class RcEditorToolbar extends LitElement {
18
18
  activeUnderline: boolean;
19
19
  activeStrikethrough: boolean;
20
20
  activeCode: boolean;
21
+ activeLink: boolean;
21
22
  activeHeading: HeadingLevel | null;
22
23
  activeBlockquote: boolean;
23
24
  activeBulletList: boolean;
@@ -30,15 +31,14 @@ export declare class RcEditorToolbar extends LitElement {
30
31
  codeLanguage: string | null;
31
32
  /** Whether the editor is in source mode. */
32
33
  sourceMode: boolean;
33
- /** Tracks the in-progress language input value before commit. */
34
- private _langInputValue;
34
+ protected _langInputValue: string;
35
35
  createRenderRoot(): this;
36
36
  updated(changed: Map<string, unknown>): void;
37
- private _dispatch;
38
- private _onClick;
39
- private _onHeadingChange;
40
- private _onLangInput;
41
- private _onLangCommit;
42
- private _onLangKeyDown;
37
+ protected _dispatch(action: EditorToolbarAction, extra?: Partial<EditorToolbarActionDetail>): void;
38
+ protected _onClick: (e: MouseEvent) => void;
39
+ protected _onHeadingChange: (e: Event) => void;
40
+ protected _onLangInput: (e: Event) => void;
41
+ protected _onLangCommit: () => void;
42
+ protected _onLangKeyDown: (e: KeyboardEvent) => void;
43
43
  render(): import('lit').TemplateResult<1>;
44
44
  }
@@ -1,6 +1,9 @@
1
1
  import { LitElement } from 'lit';
2
+ import { fromMarkdown } from 'mdast-util-from-markdown';
3
+ import { RCTextarea, RCTextareaPluginAPI } from '@rcarls/rc-textarea';
4
+ import { ActiveFormats, EditorMode, EditorToolbarAction, EditorToolbarActionDetail, HeadingLevel } from './types.ts';
2
5
  /**
3
- * Markdown-backed rich-text editor with two first-class modes:
6
+ * Rich/source Markdown editor with a formatting toolbar, backed by rc-textarea.
4
7
  *
5
8
  * - **Rich mode (default)**: A `contenteditable` div shows micromark-rendered
6
9
  * HTML. DOM mutations are converted back to markdown via turndown and
@@ -18,6 +21,8 @@ import { LitElement } from 'lit';
18
21
  * </rc-markdown-editor>
19
22
  * ```
20
23
  *
24
+ * @see {@link https://richardcarls.github.io/rc-webcomponents/components/rc-markdown-editor rc-markdown-editor docs}
25
+ *
21
26
  * @fires rc-change - Value changed. Detail: `{ value: string }`.
22
27
  * @fires rc-mode-change - Mode toggled. Detail: `{ mode: EditorMode }`.
23
28
  * @fires rc-formatting-change - Active formats at cursor changed. Detail: `ActiveFormats`.
@@ -47,52 +52,64 @@ export declare class RcMarkdownEditor extends LitElement {
47
52
  toolbar: boolean;
48
53
  /** Make the editor read-only. */
49
54
  readOnly: boolean;
50
- private _richView;
51
- private _sourceEditor;
52
- private _toolbarEl?;
53
- private _observer;
54
- private _savedRichRange;
55
- private _activeFormats;
56
- private _syncTimer;
57
- private _pluginApi;
58
- private _ignoreRichMutations;
59
- private _richViewRendered;
60
- private _skipNextModeSwitch;
55
+ protected _$richView: HTMLDivElement;
56
+ protected _$sourceEditor: RCTextarea;
57
+ protected _$toolbar?: HTMLElement;
58
+ protected _observer: MutationObserver | null;
59
+ protected _$savedRange: Range | null;
60
+ protected _activeFormats: ActiveFormats;
61
+ protected _syncTimer: ReturnType<typeof setTimeout> | null;
62
+ protected _pluginApi: RCTextareaPluginAPI | null;
63
+ protected _ignoreRichMutations: boolean;
64
+ protected _richViewRendered: boolean;
65
+ protected _cachedTree: ReturnType<typeof fromMarkdown> | null;
66
+ protected _firstUpdatedDone: boolean;
67
+ protected _$linkPopover?: HTMLDivElement;
68
+ protected _$linkPopoverInput?: HTMLInputElement;
69
+ protected _linkPopoverOpen: boolean;
70
+ protected _linkPopoverHref: string;
61
71
  connectedCallback(): void;
62
72
  disconnectedCallback(): void;
63
73
  firstUpdated(): void;
64
74
  updated(changed: Map<string, unknown>): void;
65
75
  render(): import('lit').TemplateResult<1>;
66
- private _setRichViewContent;
67
- private _renderMarkdown;
68
- private _scheduleMarkdownSync;
69
- private _syncMarkdownFromRichView;
70
- private _switchToSource;
71
- private _switchToRich;
72
- private _cachedTree;
73
- private _setupSourcePlugin;
74
- private _getCodeBlockLanguage;
75
- private _onSourceChange;
76
- private _onToolbarAction;
77
- private _applyRichFormat;
78
- private _applySourceFormat;
76
+ protected _setRichViewContent(markdown: string): void;
77
+ protected _renderMarkdown(markdown: string): string;
78
+ protected _scheduleMarkdownSync(): void;
79
+ protected _syncMarkdownFromRichView(): void;
80
+ protected _switchToSource(): void;
81
+ protected _switchToRich(): void;
82
+ protected _setupSourcePlugin(): void;
83
+ protected _getCodeBlockLanguage(tree: ReturnType<typeof fromMarkdown>, offset: number): string | null;
84
+ protected _onSourceChange: (_e: Event) => void;
85
+ protected _onToolbarAction: (e: CustomEvent<EditorToolbarActionDetail>) => void;
86
+ protected _applyRichFormat(action: EditorToolbarAction): void;
87
+ protected _applySourceFormat(action: EditorToolbarAction): void;
79
88
  /** Sets the heading level on the cursor's line in source mode. null = paragraph. */
80
- private _setSourceHeadingLevel;
89
+ protected _setSourceHeadingLevel(level: HeadingLevel | null): void;
81
90
  /** Updates the language of the code block at the cursor in source mode. */
82
- private _setSourceCodeBlockLanguage;
83
- /** Updates the data-lang attribute on the <pre> at the cursor in rich mode. */
84
- private _setRichCodeBlockLanguage;
85
- /** Toggles a line prefix (e.g. `'# '`, `'> '`, `'- '`) on the cursor's line. */
86
- private _toggleLinePrefix;
91
+ protected _setSourceCodeBlockLanguage(lang: string): void;
92
+ /** Updates the `data-lang` attribute on the `<pre>` at the cursor in rich mode. */
93
+ protected _setRichCodeBlockLanguage(lang: string): void;
94
+ /** Toggles a line prefix (e.g. `'> '`, `'- '`) on the cursor's line. */
95
+ protected _toggleLinePrefix(prefix: string): void;
87
96
  /** Wraps the current selection (or cursor line) with a fenced code block. */
88
- private _toggleSourceCodeFence;
89
- private _wrapRichSelection;
90
- private _toggleRichCodeBlock;
91
- private _onKeyDown;
92
- private _onSelectionChange;
93
- private _pushToolbarState;
94
- private _syncNativeTextarea;
95
- private _connectLabel;
96
- private _dispatchChange;
97
- private _dispatchModeChange;
97
+ protected _toggleSourceCodeFence(): void;
98
+ protected _wrapRichSelection(tag: string): void;
99
+ protected _toggleRichCodeBlock(): void;
100
+ protected _onKeyDown: (e: KeyboardEvent) => void;
101
+ protected _onSelectionChange: () => void;
102
+ protected _pushToolbarState(): void;
103
+ protected _syncNativeTextarea(): void;
104
+ protected _connectLabel(): void;
105
+ protected _onRichViewClick: (e: MouseEvent) => void;
106
+ protected _onDocumentClick: (e: MouseEvent) => void;
107
+ protected _expandRichSelectionToWord(): void;
108
+ protected _positionLinkPopover(): void;
109
+ protected _applyLink: () => void;
110
+ protected _removeLink: () => void;
111
+ protected _openLinkHref: () => void;
112
+ protected _onLinkPopoverKeyDown: (e: KeyboardEvent) => void;
113
+ protected _dispatchChange(value: string): void;
114
+ protected _dispatchModeChange(mode: EditorMode): void;
98
115
  }
package/package.json CHANGED
@@ -3,14 +3,14 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.2.0",
7
- "description": "Headless markdown editor web component extending rc-textarea, built with Lit",
6
+ "version": "0.3.1",
7
+ "description": "Rich/source Markdown editor with a formatting toolbar, backed by rc-textarea.",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "git+https://github.com/richardcarls/rc-webcomponents.git",
11
11
  "directory": "packages/rc-markdown-editor"
12
12
  },
13
- "homepage": "https://github.com/richardcarls/rc-webcomponents#readme",
13
+ "homepage": "https://richardcarls.github.io/rc-webcomponents/components/rc-markdown-editor",
14
14
  "license": "MIT",
15
15
  "type": "module",
16
16
  "files": [
@@ -37,21 +37,19 @@
37
37
  ],
38
38
  "customElements": "dist/custom-elements.json",
39
39
  "scripts": {
40
- "dev": "vite",
41
40
  "build": "tsc && vite build && cem analyze",
42
41
  "cem:analyze": "cem analyze",
43
42
  "preview": "vite preview",
44
- "test:browser": "vitest",
45
- "test:browser:chrome": "vitest --project=chromium",
46
- "test:browser:firefox": "vitest --project=firefox",
47
- "yalc:publish": "yalc publish --push"
43
+ "test:browser": "vitest --run",
44
+ "test:browser:chrome": "vitest --run --project=chromium",
45
+ "test:browser:firefox": "vitest --run --project=firefox"
48
46
  },
49
47
  "dependencies": {
50
- "@rcarls/rc-textarea": "workspace:^"
48
+ "@rcarls/rc-select": "workspace:*",
49
+ "@rcarls/rc-textarea": "workspace:*"
51
50
  },
52
51
  "devDependencies": {
53
52
  "@custom-elements-manifest/analyzer": "0.11.0",
54
- "@guanghechen/rollup-plugin-copy": "^6.0.9",
55
53
  "@types/turndown": "^5.0.5",
56
54
  "@vitest/browser-playwright": "4.1.5",
57
55
  "lit": "^3.0.0",
package/dist/demo.css DELETED
@@ -1,85 +0,0 @@
1
- *, *::before, *::after {
2
- box-sizing: border-box;
3
- }
4
-
5
- :root {
6
- color-scheme: light dark;
7
- --rc-accent: Highlight; /* Chrome doesn't support AccentColor on the open web */
8
- }
9
-
10
- @supports (color: AccentColor) {
11
- :root {
12
- --rc-accent: AccentColor;
13
- }
14
- }
15
-
16
- [data-theme="light"] { color-scheme: light; }
17
- [data-theme="dark"] { color-scheme: dark; }
18
-
19
- [data-theme="solarized-light"] {
20
- color-scheme: light;
21
- --rc-surface: #fdf6e3;
22
- --rc-text: #657b83;
23
- --rc-border: 1px solid #93a1a1;
24
- --rc-shadow: 0 2px 8px rgba(0, 0, 0, .12);
25
- }
26
-
27
- [data-theme="solarized-dark"] {
28
- color-scheme: dark;
29
- --rc-surface: #002b36;
30
- --rc-text: #839496;
31
- --rc-border: 1px solid #586e75;
32
- --rc-shadow: 0 2px 8px rgba(0, 0, 0, .3);
33
- }
34
-
35
- body {
36
- font-family: system-ui, sans-serif;
37
- margin: 0;
38
- }
39
-
40
- .demo-page {
41
- max-width: 60rem;
42
- margin: 0 auto;
43
- padding: 2rem 1.5rem;
44
- }
45
-
46
- .demo-controls {
47
- display: flex;
48
- align-items: center;
49
- gap: 1rem;
50
- margin-bottom: 2rem;
51
- padding-bottom: 1rem;
52
- border-bottom: 1px solid ButtonBorder;
53
- }
54
-
55
- .demo-controls .demo-title {
56
- flex: 1;
57
- }
58
-
59
- .demo-controls h1 {
60
- margin: 0 0 0.15rem;
61
- font-size: 1.5rem;
62
- }
63
-
64
- .demo-controls a {
65
- font-size: 0.8rem;
66
- }
67
-
68
- .demo-section {
69
- margin-bottom: 2rem;
70
- }
71
-
72
- .demo-section h2 {
73
- margin: 0 0 0.5rem;
74
- }
75
-
76
- .theme-picker {
77
- padding: 0.35em 0.75em;
78
- font-family: inherit;
79
- font-size: 0.8rem;
80
- cursor: pointer;
81
- border: 1px solid ButtonBorder;
82
- border-radius: 4px;
83
- background: Canvas;
84
- color: CanvasText;
85
- }
package/dist/demo.js DELETED
@@ -1,40 +0,0 @@
1
- const THEMES = ['', 'light', 'dark', 'solarized-light', 'solarized-dark'];
2
- const LABELS = ['Auto', 'Light', 'Dark', 'Solarized ☀', 'Solarized ☾'];
3
-
4
- const stored = localStorage.getItem('rc-demo-theme') ?? '';
5
- applyTheme(stored);
6
-
7
- function applyTheme(theme) {
8
- if (theme) {
9
- document.documentElement.dataset.theme = theme;
10
- } else {
11
- delete document.documentElement.dataset.theme;
12
- }
13
- }
14
-
15
- function currentIndex() {
16
- const current = document.documentElement.dataset.theme ?? '';
17
- const idx = THEMES.indexOf(current);
18
- return idx === -1 ? 0 : idx;
19
- }
20
-
21
- function updateButtons() {
22
- const label = LABELS[currentIndex()];
23
- document.querySelectorAll('.theme-picker').forEach((btn) => {
24
- btn.textContent = label;
25
- });
26
- }
27
-
28
- window.cycleTheme = function () {
29
- const next = THEMES[(currentIndex() + 1) % THEMES.length];
30
- applyTheme(next);
31
- localStorage.setItem('rc-demo-theme', next);
32
- updateButtons();
33
- };
34
-
35
- document.addEventListener('DOMContentLoaded', () => {
36
- updateButtons();
37
- document.querySelectorAll('.theme-picker').forEach((btn) => {
38
- btn.addEventListener('click', window.cycleTheme);
39
- });
40
- });