@open-wysiwyg-editor/vue 0.0.0-stage → 1.0.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/LICENSE +21 -0
- package/README.md +152 -2
- package/dist/content.css +323 -0
- package/dist/content.min.css +2 -0
- package/dist/index.cjs +107 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +82 -0
- package/dist/index.d.ts +82 -0
- package/dist/index.js +86 -0
- package/dist/index.js.map +1 -0
- package/dist/style.css +1165 -0
- package/dist/style.min.css +2 -0
- package/package.json +73 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 alhassan-ahmed and open-wysiwyg-editor contributors
|
|
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,153 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center"><a href="https://alhassan73.github.io/open-wysiwyg-editor/"><img src="https://raw.githubusercontent.com/alhassan73/open-wysiwyg-editor/main/.github/assets/banner.png" alt="Open WYSIWYG Editor — accessible, RTL-first rich text editor for every framework" width="100%"></a></p>
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# @open-wysiwyg-editor/vue
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@open-wysiwyg-editor/vue)
|
|
6
|
+
[](https://github.com/alhassan73/open-wysiwyg-editor/blob/main/LICENSE)
|
|
7
|
+
|
|
8
|
+
**[Live demo →](https://alhassan73.github.io/open-wysiwyg-editor/)** · [GitHub](https://github.com/alhassan73/open-wysiwyg-editor) · [npm](https://www.npmjs.com/package/@open-wysiwyg-editor/vue)
|
|
9
|
+
|
|
10
|
+
A Vue 3 component for [open-wysiwyg-editor](https://github.com/alhassan73/open-wysiwyg-editor#readme): an accessible (WCAG 2.2 AA), RTL-first, CSP-safe rich text editor. `<RichTextEditor v-model="html" />` gives you the full editor with a toolbar, dialogs and a status bar. Needs Vue 3.3+.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @open-wysiwyg-editor/vue
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
One install is enough. The package depends on the core editor and re-exports its whole API (`createEditor`, `StarterKit`, types…).
|
|
19
|
+
|
|
20
|
+
Using Nuxt? Use [`@open-wysiwyg-editor/nuxt`](https://www.npmjs.com/package/@open-wysiwyg-editor/nuxt) instead.
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```vue
|
|
25
|
+
<script setup lang="ts">
|
|
26
|
+
import { ref } from "vue";
|
|
27
|
+
import { RichTextEditor } from "@open-wysiwyg-editor/vue";
|
|
28
|
+
import "@open-wysiwyg-editor/vue/style.css";
|
|
29
|
+
|
|
30
|
+
const html = ref("<p>Hello <strong>world</strong></p>");
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<RichTextEditor v-model="html" placeholder="Write something…" />
|
|
35
|
+
</template>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`v-model` is HTML. Typing updates your ref without resetting the cursor. Assigning a new value to the ref loads it into the editor.
|
|
39
|
+
|
|
40
|
+
### Options
|
|
41
|
+
|
|
42
|
+
`options` takes everything `createEditor()` accepts, except `element`. See the [core docs](https://github.com/alhassan73/open-wysiwyg-editor#readme) for the full list.
|
|
43
|
+
|
|
44
|
+
```vue
|
|
45
|
+
<RichTextEditor v-model="html" :options="{ language: 'ar' }" />
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Callbacks (`onUpdate`, `onFocus`…) and the runtime options (`editable`, `placeholder`, `aria*`, `dir`, `contentLang`) follow changes. Everything else is read once when the component mounts. To change those, remount the component with a new `:key`.
|
|
49
|
+
|
|
50
|
+
You can write the `options` object inline. The editor only calls `setOptions` when a runtime option really changed.
|
|
51
|
+
|
|
52
|
+
### Custom toolbar
|
|
53
|
+
|
|
54
|
+
```vue
|
|
55
|
+
<RichTextEditor
|
|
56
|
+
v-model="html"
|
|
57
|
+
:options="{ ui: { toolbar: ['bold', 'italic', '|', 'link', '|', 'undo', 'redo'] } }"
|
|
58
|
+
/>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Forms
|
|
62
|
+
|
|
63
|
+
Set `name` and the HTML is kept in a hidden `<input>`, so a plain form post (or `FormData`) receives it.
|
|
64
|
+
|
|
65
|
+
```vue
|
|
66
|
+
<form method="post" action="/save">
|
|
67
|
+
<RichTextEditor v-model="html" name="body" />
|
|
68
|
+
<button type="submit">Save</button>
|
|
69
|
+
</form>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Access the editor
|
|
73
|
+
|
|
74
|
+
Use `@ready` or a template ref. Both give you the core `Editor` (`getHTML()`, `getJSON()`, `commands`, `focus()`…).
|
|
75
|
+
|
|
76
|
+
```vue
|
|
77
|
+
<script setup lang="ts">
|
|
78
|
+
import { ref } from "vue";
|
|
79
|
+
import { RichTextEditor, type Editor } from "@open-wysiwyg-editor/vue";
|
|
80
|
+
|
|
81
|
+
const html = ref("");
|
|
82
|
+
const field = ref<{ editor: Editor | null } | null>(null);
|
|
83
|
+
|
|
84
|
+
function onReady(editor: Editor) {
|
|
85
|
+
editor.focus();
|
|
86
|
+
}
|
|
87
|
+
function bold() {
|
|
88
|
+
field.value?.editor?.commands.toggleBold();
|
|
89
|
+
}
|
|
90
|
+
</script>
|
|
91
|
+
|
|
92
|
+
<template>
|
|
93
|
+
<button type="button" @click="bold">Bold</button>
|
|
94
|
+
<RichTextEditor ref="field" v-model="html" @ready="onReady" />
|
|
95
|
+
</template>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Headless
|
|
99
|
+
|
|
100
|
+
Set `options.ui` to `false` for the engine only, without toolbar or status bar. Build your own controls with the editor you get from `@ready`.
|
|
101
|
+
|
|
102
|
+
```vue
|
|
103
|
+
<RichTextEditor v-model="html" :options="{ ui: false }" @ready="(e) => (editor = e)" />
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## API
|
|
107
|
+
|
|
108
|
+
### Props
|
|
109
|
+
|
|
110
|
+
| Prop | Type | Default | Notes |
|
|
111
|
+
| --- | --- | --- | --- |
|
|
112
|
+
| `modelValue` | `string` | none | The content as HTML (`v-model`). Falls back to `options.content` when not set. |
|
|
113
|
+
| `options` | `Omit<EditorOptions, "element">` | `{}` | Everything `createEditor()` accepts. |
|
|
114
|
+
| `editable` | `boolean` | `true` | Also off when `options.editable` is `false`. |
|
|
115
|
+
| `placeholder` | `string` | none | Overrides `options.placeholder`. |
|
|
116
|
+
| `name` | `string` | none | Form field name. Adds a hidden input with the HTML. |
|
|
117
|
+
|
|
118
|
+
### Events
|
|
119
|
+
|
|
120
|
+
| Event | Payload | When |
|
|
121
|
+
| --- | --- | --- |
|
|
122
|
+
| `update:modelValue` | `html: string` | Every content change. |
|
|
123
|
+
| `ready` | `editor: Editor` | The editor was created (in the browser, after mount). |
|
|
124
|
+
|
|
125
|
+
### Exposed (template ref)
|
|
126
|
+
|
|
127
|
+
| Name | Type | Notes |
|
|
128
|
+
| --- | --- | --- |
|
|
129
|
+
| `editor` | `Editor \| null` | `null` until mounted and after unmount. |
|
|
130
|
+
|
|
131
|
+
## Styling
|
|
132
|
+
|
|
133
|
+
Import the stylesheet once, in your app entry or in the component:
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
import "@open-wysiwyg-editor/vue/style.css"; // or style.min.css
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
To show saved HTML on a page without an editor, import `@open-wysiwyg-editor/vue/content.css` (or `content.min.css`) and wrap the HTML in `<div class="owe-content-root">`.
|
|
140
|
+
|
|
141
|
+
## Server-side rendering
|
|
142
|
+
|
|
143
|
+
The editor starts in `onMounted`, which never runs on the server. The component renders an empty container (and the hidden input when `name` is set), so it works in Nuxt and Vite SSR without `<ClientOnly>`.
|
|
144
|
+
|
|
145
|
+
## Links
|
|
146
|
+
|
|
147
|
+
- [Core docs and options](https://github.com/alhassan73/open-wysiwyg-editor#readme)
|
|
148
|
+
- [Website and live demo](https://alhassan73.github.io/open-wysiwyg-editor/)
|
|
149
|
+
- [Issues](https://github.com/alhassan73/open-wysiwyg-editor/issues)
|
|
150
|
+
|
|
151
|
+
## License
|
|
152
|
+
|
|
153
|
+
MIT © alhassan-ahmed
|
package/dist/content.css
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/*! open-wysiwyg-editor | MIT License */
|
|
2
|
+
/*
|
|
3
|
+
* Design tokens. Everything lives in the `owe` cascade layer, so any unlayered host CSS wins
|
|
4
|
+
* without !important. Override tokens on .owe (or :root) to theme the editor.
|
|
5
|
+
* Contrast: text >= 4.5:1, borders/focus/states >= 3:1 against --owe-bg (WCAG 1.4.3 / 1.4.11).
|
|
6
|
+
*
|
|
7
|
+
* Tokens added in 1.0 (existing names keep working):
|
|
8
|
+
* --owe-radius card radius (24px) --owe-radius-sm inner controls (10px)
|
|
9
|
+
* --owe-radius-md menus (12px) --owe-radius-lg dialogs (16px)
|
|
10
|
+
* --owe-btn-size toolbar button (40px) --owe-pad-x/-y editing-area padding
|
|
11
|
+
* --owe-chrome toolbar / status bar surface
|
|
12
|
+
* --owe-field-bg inputs and source view --owe-popup-border menu / dialog edge
|
|
13
|
+
* --owe-body paragraph text --owe-text-2 secondary text (quotes)
|
|
14
|
+
* --owe-icon toolbar icon color --owe-caret caret color
|
|
15
|
+
* --owe-primary(-hover) solid fills: pressed buttons, primary buttons (white text, 4.8:1)
|
|
16
|
+
* --owe-gradient bottom edge of the card --owe-backdrop dialog backdrop
|
|
17
|
+
* --owe-accent is the TEXT/link/focus blue; --owe-primary is the FILL blue.
|
|
18
|
+
* Set --owe-font (e.g. "Almarai", system-ui) to change the typeface; no web fonts are bundled.
|
|
19
|
+
*/
|
|
20
|
+
@layer owe {
|
|
21
|
+
.owe,
|
|
22
|
+
.owe-content-root {
|
|
23
|
+
--owe-font: system-ui, -apple-system, "Segoe UI", Roboto, "Noto Sans Arabic", "Noto Sans", Tahoma, sans-serif;
|
|
24
|
+
--owe-font-mono: ui-monospace, "JetBrains Mono", "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace;
|
|
25
|
+
--owe-font-size: 1rem;
|
|
26
|
+
--owe-line-height: 1.65;
|
|
27
|
+
--owe-radius: 24px;
|
|
28
|
+
--owe-radius-sm: 10px;
|
|
29
|
+
--owe-radius-md: 12px;
|
|
30
|
+
--owe-radius-lg: 16px;
|
|
31
|
+
--owe-space: 8px;
|
|
32
|
+
--owe-target: 32px; /* >= 24px (WCAG 2.5.8); 32px is comfortable */
|
|
33
|
+
--owe-btn-size: 40px;
|
|
34
|
+
--owe-pad-x: clamp(16px, 4vw, 32px);
|
|
35
|
+
--owe-pad-y: 28px;
|
|
36
|
+
--owe-gradient: linear-gradient(90deg, #0066ff, #9644ff, #af47c0);
|
|
37
|
+
|
|
38
|
+
--owe-bg: #ffffff;
|
|
39
|
+
--owe-chrome: #fafbfc;
|
|
40
|
+
--owe-surface: #f4f6f8;
|
|
41
|
+
--owe-surface-2: #f1f3f5;
|
|
42
|
+
--owe-field-bg: #ffffff;
|
|
43
|
+
--owe-text: #101112;
|
|
44
|
+
--owe-body: #1b1f24;
|
|
45
|
+
--owe-text-2: #3d434a;
|
|
46
|
+
--owe-muted: #54595f; /* 7:1 on white */
|
|
47
|
+
--owe-icon: #3a3f45;
|
|
48
|
+
--owe-border: #8a929a; /* 3.2:1 on white */
|
|
49
|
+
--owe-border-subtle: #e3e6ea;
|
|
50
|
+
--owe-popup-border: #d5d9de;
|
|
51
|
+
--owe-accent: #0057d9; /* 6.2:1 on white */
|
|
52
|
+
--owe-on-accent: #ffffff;
|
|
53
|
+
--owe-primary: #0066ff;
|
|
54
|
+
--owe-primary-hover: #0057d9;
|
|
55
|
+
--owe-accent-soft: #d3e3fd;
|
|
56
|
+
--owe-focus: #0066ff;
|
|
57
|
+
--owe-caret: #0066ff;
|
|
58
|
+
--owe-danger: #b3261e;
|
|
59
|
+
--owe-warning: #8a5300;
|
|
60
|
+
--owe-success: #146c2e;
|
|
61
|
+
--owe-selection: rgb(0 102 255 / 0.22);
|
|
62
|
+
--owe-code-bg: #f4f6f8;
|
|
63
|
+
--owe-mark: #fff2a8;
|
|
64
|
+
--owe-shadow: 0 16px 40px rgb(16 17 18 / 0.16), 0 2px 8px rgb(16 17 18 / 0.08);
|
|
65
|
+
--owe-backdrop: rgb(16 17 18 / 0.45);
|
|
66
|
+
--owe-z-popover: 1000;
|
|
67
|
+
|
|
68
|
+
color-scheme: light;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@media (prefers-color-scheme: dark) {
|
|
72
|
+
.owe:not([data-theme="light"]),
|
|
73
|
+
.owe-content-root:not([data-theme="light"]) {
|
|
74
|
+
--owe-bg: #17191b;
|
|
75
|
+
--owe-chrome: #131517;
|
|
76
|
+
--owe-surface: #1e2124;
|
|
77
|
+
--owe-surface-2: #25282b;
|
|
78
|
+
--owe-field-bg: #101214;
|
|
79
|
+
--owe-text: #ffffff;
|
|
80
|
+
--owe-body: #dcdedf;
|
|
81
|
+
--owe-text-2: #c3c7cb;
|
|
82
|
+
--owe-muted: #a1a7ad;
|
|
83
|
+
--owe-icon: #d2d4d5;
|
|
84
|
+
--owe-border: #6b7279;
|
|
85
|
+
--owe-border-subtle: #25282b;
|
|
86
|
+
--owe-popup-border: #33383d;
|
|
87
|
+
--owe-accent: #4d94ff;
|
|
88
|
+
--owe-on-accent: #ffffff;
|
|
89
|
+
--owe-primary: #0066ff;
|
|
90
|
+
--owe-primary-hover: #0057d9;
|
|
91
|
+
--owe-accent-soft: #14315e;
|
|
92
|
+
--owe-focus: #4d94ff;
|
|
93
|
+
--owe-caret: #5b9dff;
|
|
94
|
+
--owe-danger: #f2b8b5;
|
|
95
|
+
--owe-warning: #f6c66b;
|
|
96
|
+
--owe-success: #8bd6a0;
|
|
97
|
+
--owe-selection: rgb(0 102 255 / 0.55);
|
|
98
|
+
--owe-code-bg: #0f1112;
|
|
99
|
+
--owe-mark: #4a35b8;
|
|
100
|
+
--owe-shadow: 0 16px 40px rgb(0 0 0 / 0.55), 0 2px 8px rgb(0 0 0 / 0.4);
|
|
101
|
+
--owe-backdrop: rgb(4 5 16 / 0.65);
|
|
102
|
+
color-scheme: dark;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.owe[data-theme="dark"],
|
|
107
|
+
.owe-content-root[data-theme="dark"] {
|
|
108
|
+
--owe-bg: #17191b;
|
|
109
|
+
--owe-chrome: #131517;
|
|
110
|
+
--owe-surface: #1e2124;
|
|
111
|
+
--owe-surface-2: #25282b;
|
|
112
|
+
--owe-field-bg: #101214;
|
|
113
|
+
--owe-text: #ffffff;
|
|
114
|
+
--owe-body: #dcdedf;
|
|
115
|
+
--owe-text-2: #c3c7cb;
|
|
116
|
+
--owe-muted: #a1a7ad;
|
|
117
|
+
--owe-icon: #d2d4d5;
|
|
118
|
+
--owe-border: #6b7279;
|
|
119
|
+
--owe-border-subtle: #25282b;
|
|
120
|
+
--owe-popup-border: #33383d;
|
|
121
|
+
--owe-accent: #4d94ff;
|
|
122
|
+
--owe-on-accent: #ffffff;
|
|
123
|
+
--owe-primary: #0066ff;
|
|
124
|
+
--owe-primary-hover: #0057d9;
|
|
125
|
+
--owe-accent-soft: #14315e;
|
|
126
|
+
--owe-focus: #4d94ff;
|
|
127
|
+
--owe-caret: #5b9dff;
|
|
128
|
+
--owe-danger: #f2b8b5;
|
|
129
|
+
--owe-warning: #f6c66b;
|
|
130
|
+
--owe-success: #8bd6a0;
|
|
131
|
+
--owe-selection: rgb(0 102 255 / 0.55);
|
|
132
|
+
--owe-code-bg: #0f1112;
|
|
133
|
+
--owe-mark: #4a35b8;
|
|
134
|
+
--owe-shadow: 0 16px 40px rgb(0 0 0 / 0.55), 0 2px 8px rgb(0 0 0 / 0.4);
|
|
135
|
+
--owe-backdrop: rgb(4 5 16 / 0.65);
|
|
136
|
+
color-scheme: dark;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
@media (prefers-contrast: more) {
|
|
140
|
+
.owe {
|
|
141
|
+
--owe-border: currentColor;
|
|
142
|
+
--owe-border-subtle: var(--owe-border);
|
|
143
|
+
--owe-popup-border: var(--owe-border);
|
|
144
|
+
--owe-muted: var(--owe-text);
|
|
145
|
+
--owe-text-2: var(--owe-text);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/*
|
|
151
|
+
* Content styles — shared by the editing area (.owe-content) and published pages
|
|
152
|
+
* (wrap saved HTML in <div class="owe-content-root">), so what authors see is what readers get.
|
|
153
|
+
*/
|
|
154
|
+
@layer owe {
|
|
155
|
+
:is(.owe-content, .owe-content-root) {
|
|
156
|
+
font-family: var(--owe-font);
|
|
157
|
+
font-size: var(--owe-font-size);
|
|
158
|
+
line-height: var(--owe-line-height);
|
|
159
|
+
color: var(--owe-body);
|
|
160
|
+
overflow-wrap: break-word;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
:is(.owe-content, .owe-content-root) > :first-child {
|
|
164
|
+
margin-block-start: 0;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
:is(.owe-content, .owe-content-root) :is(p, ul, ol, blockquote, pre, table, figure, hr) {
|
|
168
|
+
margin-block: 0 0.85em;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
:is(.owe-content, .owe-content-root) :is(h1, h2, h3, h4, h5, h6) {
|
|
172
|
+
line-height: 1.2;
|
|
173
|
+
margin-block: 1.3em 0.5em;
|
|
174
|
+
font-weight: 700;
|
|
175
|
+
letter-spacing: -0.02em;
|
|
176
|
+
color: var(--owe-text);
|
|
177
|
+
}
|
|
178
|
+
/* Negative tracking breaks Arabic joining: reset for RTL / Arabic-script content. */
|
|
179
|
+
:is(.owe-content, .owe-content-root) :is(h1, h2, h3, h4, h5, h6):is([dir="rtl"], :lang(ar), :lang(fa), :lang(ur), :lang(he)),
|
|
180
|
+
:is([dir="rtl"], :lang(ar), :lang(fa), :lang(ur), :lang(he)) :is(.owe-content, .owe-content-root) :is(h1, h2, h3, h4, h5, h6),
|
|
181
|
+
:is(.owe-content, .owe-content-root):is([dir="rtl"], :lang(ar), :lang(fa), :lang(ur), :lang(he)) :is(h1, h2, h3, h4, h5, h6) {
|
|
182
|
+
letter-spacing: 0;
|
|
183
|
+
}
|
|
184
|
+
:is(.owe-content, .owe-content-root) h1 { font-size: 2.1em; }
|
|
185
|
+
:is(.owe-content, .owe-content-root) h2 { font-size: 1.5em; }
|
|
186
|
+
:is(.owe-content, .owe-content-root) h3 { font-size: 1.25em; }
|
|
187
|
+
:is(.owe-content, .owe-content-root) h4 { font-size: 1.1em; }
|
|
188
|
+
:is(.owe-content, .owe-content-root) h5 { font-size: 1em; }
|
|
189
|
+
:is(.owe-content, .owe-content-root) h6 { font-size: 0.9em; }
|
|
190
|
+
|
|
191
|
+
:is(.owe-content, .owe-content-root) a {
|
|
192
|
+
color: var(--owe-accent);
|
|
193
|
+
text-decoration: underline; /* never colour alone (WCAG 1.4.1) */
|
|
194
|
+
text-underline-offset: 0.15em;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
:is(.owe-content, .owe-content-root) :is(ul, ol) {
|
|
198
|
+
padding-inline-start: 1.6em;
|
|
199
|
+
}
|
|
200
|
+
:is(.owe-content, .owe-content-root) li > :is(p, ul, ol) {
|
|
201
|
+
margin-block: 0.15em;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
:is(.owe-content, .owe-content-root) ul[data-type="taskList"] {
|
|
205
|
+
list-style: none;
|
|
206
|
+
padding-inline-start: 0.25em;
|
|
207
|
+
}
|
|
208
|
+
:is(.owe-content, .owe-content-root) li[data-type="taskItem"] {
|
|
209
|
+
display: flex;
|
|
210
|
+
align-items: flex-start;
|
|
211
|
+
gap: 0.5em;
|
|
212
|
+
}
|
|
213
|
+
:is(.owe-content, .owe-content-root) li[data-type="taskItem"] > input,
|
|
214
|
+
.owe-task-control > input {
|
|
215
|
+
inline-size: 1.1em;
|
|
216
|
+
block-size: 1.1em;
|
|
217
|
+
margin-block-start: 0.3em;
|
|
218
|
+
accent-color: var(--owe-primary);
|
|
219
|
+
flex: none;
|
|
220
|
+
}
|
|
221
|
+
:is(.owe-content, .owe-content-root) li[data-type="taskItem"] > div {
|
|
222
|
+
flex: 1;
|
|
223
|
+
min-inline-size: 0;
|
|
224
|
+
}
|
|
225
|
+
:is(.owe-content, .owe-content-root) li[data-checked="true"] > div {
|
|
226
|
+
text-decoration: line-through;
|
|
227
|
+
color: var(--owe-muted);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
:is(.owe-content, .owe-content-root) blockquote {
|
|
231
|
+
margin-inline: 0;
|
|
232
|
+
padding-inline-start: 1em;
|
|
233
|
+
border-inline-start: 3px solid var(--owe-accent);
|
|
234
|
+
border-start-start-radius: 2px;
|
|
235
|
+
border-end-start-radius: 2px;
|
|
236
|
+
color: var(--owe-text-2);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
:is(.owe-content, .owe-content-root) code {
|
|
240
|
+
font-family: var(--owe-font-mono);
|
|
241
|
+
font-size: 0.9em;
|
|
242
|
+
background: var(--owe-code-bg);
|
|
243
|
+
border: 1px solid var(--owe-border-subtle);
|
|
244
|
+
border-radius: 6px;
|
|
245
|
+
padding: 0.1em 0.35em;
|
|
246
|
+
}
|
|
247
|
+
:is(.owe-content, .owe-content-root) pre {
|
|
248
|
+
font-family: var(--owe-font-mono);
|
|
249
|
+
background: var(--owe-code-bg);
|
|
250
|
+
border: 1px solid var(--owe-border-subtle);
|
|
251
|
+
border-radius: var(--owe-radius-md);
|
|
252
|
+
padding: 1em 1.25em;
|
|
253
|
+
overflow-x: auto;
|
|
254
|
+
direction: ltr; /* code is left-to-right even in RTL documents */
|
|
255
|
+
text-align: start;
|
|
256
|
+
tab-size: 2;
|
|
257
|
+
}
|
|
258
|
+
:is(.owe-content, .owe-content-root) pre code {
|
|
259
|
+
background: none;
|
|
260
|
+
border: 0;
|
|
261
|
+
padding: 0;
|
|
262
|
+
font-size: 0.9em;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
:is(.owe-content, .owe-content-root) hr {
|
|
266
|
+
border: none;
|
|
267
|
+
border-block-start: 2px solid var(--owe-border-subtle);
|
|
268
|
+
margin-block: 1.5em;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
:is(.owe-content, .owe-content-root) img {
|
|
272
|
+
max-inline-size: 100%;
|
|
273
|
+
block-size: auto;
|
|
274
|
+
display: block;
|
|
275
|
+
}
|
|
276
|
+
:is(.owe-content, .owe-content-root) figure {
|
|
277
|
+
margin-inline: 0;
|
|
278
|
+
}
|
|
279
|
+
:is(.owe-content, .owe-content-root) figcaption {
|
|
280
|
+
color: var(--owe-muted);
|
|
281
|
+
font-size: 0.9em;
|
|
282
|
+
margin-block-start: 0.4em;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
:is(.owe-content, .owe-content-root) table {
|
|
286
|
+
border-collapse: collapse;
|
|
287
|
+
inline-size: 100%;
|
|
288
|
+
table-layout: fixed;
|
|
289
|
+
}
|
|
290
|
+
:is(.owe-content, .owe-content-root) caption {
|
|
291
|
+
caption-side: top;
|
|
292
|
+
text-align: start;
|
|
293
|
+
font-weight: 600;
|
|
294
|
+
padding-block-end: 0.4em;
|
|
295
|
+
}
|
|
296
|
+
:is(.owe-content, .owe-content-root) :is(td, th) {
|
|
297
|
+
border: 1px solid var(--owe-border-subtle);
|
|
298
|
+
padding: 0.55em 0.8em;
|
|
299
|
+
vertical-align: top;
|
|
300
|
+
text-align: start;
|
|
301
|
+
min-inline-size: 3em;
|
|
302
|
+
}
|
|
303
|
+
:is(.owe-content, .owe-content-root) th {
|
|
304
|
+
background: var(--owe-surface);
|
|
305
|
+
color: var(--owe-text);
|
|
306
|
+
font-weight: 700;
|
|
307
|
+
}
|
|
308
|
+
:is(.owe-content, .owe-content-root) :is(td, th) > :last-child {
|
|
309
|
+
margin-block-end: 0;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
:is(.owe-content, .owe-content-root) mark {
|
|
313
|
+
background-color: var(--owe-mark);
|
|
314
|
+
color: inherit;
|
|
315
|
+
border-radius: 0.2em;
|
|
316
|
+
padding-inline: 0.15em;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/* Alignment classes (used by the editor always, and by output when TextAlign output: "class"). */
|
|
320
|
+
.owe-align-center { text-align: center; }
|
|
321
|
+
.owe-align-end { text-align: end; }
|
|
322
|
+
.owe-align-justify { text-align: justify; }
|
|
323
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! open-wysiwyg-editor | MIT License */
|
|
2
|
+
/*! open-wysiwyg-editor | MIT License */ @layer owe{.owe,.owe-content-root{--owe-font:system-ui,-apple-system,"Segoe UI",Roboto,"Noto Sans Arabic","Noto Sans",Tahoma,sans-serif;--owe-font-mono:ui-monospace,"JetBrains Mono","SFMono-Regular",Menlo,Consolas,"Liberation Mono",monospace;--owe-font-size:1rem;--owe-line-height:1.65;--owe-radius:24px;--owe-radius-sm:10px;--owe-radius-md:12px;--owe-radius-lg:16px;--owe-space:8px;--owe-target:32px;--owe-btn-size:40px;--owe-pad-x:clamp(16px,4vw,32px);--owe-pad-y:28px;--owe-gradient:linear-gradient(90deg,#0066ff,#9644ff,#af47c0);--owe-bg:#ffffff;--owe-chrome:#fafbfc;--owe-surface:#f4f6f8;--owe-surface-2:#f1f3f5;--owe-field-bg:#ffffff;--owe-text:#101112;--owe-body:#1b1f24;--owe-text-2:#3d434a;--owe-muted:#54595f;--owe-icon:#3a3f45;--owe-border:#8a929a;--owe-border-subtle:#e3e6ea;--owe-popup-border:#d5d9de;--owe-accent:#0057d9;--owe-on-accent:#ffffff;--owe-primary:#0066ff;--owe-primary-hover:#0057d9;--owe-accent-soft:#d3e3fd;--owe-focus:#0066ff;--owe-caret:#0066ff;--owe-danger:#b3261e;--owe-warning:#8a5300;--owe-success:#146c2e;--owe-selection:rgb(0 102 255 / 0.22);--owe-code-bg:#f4f6f8;--owe-mark:#fff2a8;--owe-shadow:0 16px 40px rgb(16 17 18 / 0.16),0 2px 8px rgb(16 17 18 / 0.08);--owe-backdrop:rgb(16 17 18 / 0.45);--owe-z-popover:1000;color-scheme:light}@media (prefers-color-scheme:dark){.owe:not([data-theme="light"]),.owe-content-root:not([data-theme="light"]){--owe-bg:#17191b;--owe-chrome:#131517;--owe-surface:#1e2124;--owe-surface-2:#25282b;--owe-field-bg:#101214;--owe-text:#ffffff;--owe-body:#dcdedf;--owe-text-2:#c3c7cb;--owe-muted:#a1a7ad;--owe-icon:#d2d4d5;--owe-border:#6b7279;--owe-border-subtle:#25282b;--owe-popup-border:#33383d;--owe-accent:#4d94ff;--owe-on-accent:#ffffff;--owe-primary:#0066ff;--owe-primary-hover:#0057d9;--owe-accent-soft:#14315e;--owe-focus:#4d94ff;--owe-caret:#5b9dff;--owe-danger:#f2b8b5;--owe-warning:#f6c66b;--owe-success:#8bd6a0;--owe-selection:rgb(0 102 255 / 0.55);--owe-code-bg:#0f1112;--owe-mark:#4a35b8;--owe-shadow:0 16px 40px rgb(0 0 0 / 0.55),0 2px 8px rgb(0 0 0 / 0.4);--owe-backdrop:rgb(4 5 16 / 0.65);color-scheme:dark}}.owe[data-theme="dark"],.owe-content-root[data-theme="dark"]{--owe-bg:#17191b;--owe-chrome:#131517;--owe-surface:#1e2124;--owe-surface-2:#25282b;--owe-field-bg:#101214;--owe-text:#ffffff;--owe-body:#dcdedf;--owe-text-2:#c3c7cb;--owe-muted:#a1a7ad;--owe-icon:#d2d4d5;--owe-border:#6b7279;--owe-border-subtle:#25282b;--owe-popup-border:#33383d;--owe-accent:#4d94ff;--owe-on-accent:#ffffff;--owe-primary:#0066ff;--owe-primary-hover:#0057d9;--owe-accent-soft:#14315e;--owe-focus:#4d94ff;--owe-caret:#5b9dff;--owe-danger:#f2b8b5;--owe-warning:#f6c66b;--owe-success:#8bd6a0;--owe-selection:rgb(0 102 255 / 0.55);--owe-code-bg:#0f1112;--owe-mark:#4a35b8;--owe-shadow:0 16px 40px rgb(0 0 0 / 0.55),0 2px 8px rgb(0 0 0 / 0.4);--owe-backdrop:rgb(4 5 16 / 0.65);color-scheme:dark}@media (prefers-contrast:more){.owe{--owe-border:currentColor;--owe-border-subtle:var(--owe-border);--owe-popup-border:var(--owe-border);--owe-muted:var(--owe-text);--owe-text-2:var(--owe-text)}}}@layer owe{:is(.owe-content,.owe-content-root){font-family:var(--owe-font);font-size:var(--owe-font-size);line-height:var(--owe-line-height);color:var(--owe-body);overflow-wrap:break-word}:is(.owe-content,.owe-content-root)>:first-child{margin-block-start:0}:is(.owe-content,.owe-content-root) :is(p,ul,ol,blockquote,pre,table,figure,hr){margin-block:0 0.85em}:is(.owe-content,.owe-content-root) :is(h1,h2,h3,h4,h5,h6){line-height:1.2;margin-block:1.3em 0.5em;font-weight:700;letter-spacing:-0.02em;color:var(--owe-text)}:is(.owe-content,.owe-content-root) :is(h1,h2,h3,h4,h5,h6):is([dir="rtl"],:lang(ar),:lang(fa),:lang(ur),:lang(he)),:is([dir="rtl"],:lang(ar),:lang(fa),:lang(ur),:lang(he)) :is(.owe-content,.owe-content-root) :is(h1,h2,h3,h4,h5,h6),:is(.owe-content,.owe-content-root):is([dir="rtl"],:lang(ar),:lang(fa),:lang(ur),:lang(he)) :is(h1,h2,h3,h4,h5,h6){letter-spacing:0}:is(.owe-content,.owe-content-root) h1{font-size:2.1em}:is(.owe-content,.owe-content-root) h2{font-size:1.5em}:is(.owe-content,.owe-content-root) h3{font-size:1.25em}:is(.owe-content,.owe-content-root) h4{font-size:1.1em}:is(.owe-content,.owe-content-root) h5{font-size:1em}:is(.owe-content,.owe-content-root) h6{font-size:0.9em}:is(.owe-content,.owe-content-root) a{color:var(--owe-accent);text-decoration:underline;text-underline-offset:0.15em}:is(.owe-content,.owe-content-root) :is(ul,ol){padding-inline-start:1.6em}:is(.owe-content,.owe-content-root) li>:is(p,ul,ol){margin-block:0.15em}:is(.owe-content,.owe-content-root) ul[data-type="taskList"]{list-style:none;padding-inline-start:0.25em}:is(.owe-content,.owe-content-root) li[data-type="taskItem"]{display:flex;align-items:flex-start;gap:0.5em}:is(.owe-content,.owe-content-root) li[data-type="taskItem"]>input,.owe-task-control>input{inline-size:1.1em;block-size:1.1em;margin-block-start:0.3em;accent-color:var(--owe-primary);flex:none}:is(.owe-content,.owe-content-root) li[data-type="taskItem"]>div{flex:1;min-inline-size:0}:is(.owe-content,.owe-content-root) li[data-checked="true"]>div{text-decoration:line-through;color:var(--owe-muted)}:is(.owe-content,.owe-content-root) blockquote{margin-inline:0;padding-inline-start:1em;border-inline-start:3px solid var(--owe-accent);border-start-start-radius:2px;border-end-start-radius:2px;color:var(--owe-text-2)}:is(.owe-content,.owe-content-root) code{font-family:var(--owe-font-mono);font-size:0.9em;background:var(--owe-code-bg);border:1px solid var(--owe-border-subtle);border-radius:6px;padding:0.1em 0.35em}:is(.owe-content,.owe-content-root) pre{font-family:var(--owe-font-mono);background:var(--owe-code-bg);border:1px solid var(--owe-border-subtle);border-radius:var(--owe-radius-md);padding:1em 1.25em;overflow-x:auto;direction:ltr;text-align:start;tab-size:2}:is(.owe-content,.owe-content-root) pre code{background:none;border:0;padding:0;font-size:0.9em}:is(.owe-content,.owe-content-root) hr{border:none;border-block-start:2px solid var(--owe-border-subtle);margin-block:1.5em}:is(.owe-content,.owe-content-root) img{max-inline-size:100%;block-size:auto;display:block}:is(.owe-content,.owe-content-root) figure{margin-inline:0}:is(.owe-content,.owe-content-root) figcaption{color:var(--owe-muted);font-size:0.9em;margin-block-start:0.4em}:is(.owe-content,.owe-content-root) table{border-collapse:collapse;inline-size:100%;table-layout:fixed}:is(.owe-content,.owe-content-root) caption{caption-side:top;text-align:start;font-weight:600;padding-block-end:0.4em}:is(.owe-content,.owe-content-root) :is(td,th){border:1px solid var(--owe-border-subtle);padding:0.55em 0.8em;vertical-align:top;text-align:start;min-inline-size:3em}:is(.owe-content,.owe-content-root) th{background:var(--owe-surface);color:var(--owe-text);font-weight:700}:is(.owe-content,.owe-content-root) :is(td,th)>:last-child{margin-block-end:0}:is(.owe-content,.owe-content-root) mark{background-color:var(--owe-mark);color:inherit;border-radius:0.2em;padding-inline:0.15em}.owe-align-center{text-align:center}.owe-align-end{text-align:end}.owe-align-justify{text-align:justify}}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
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 __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
22
|
+
var src_exports = {};
|
|
23
|
+
__export(src_exports, {
|
|
24
|
+
RichTextEditor: () => RichTextEditor
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(src_exports);
|
|
27
|
+
var import_vue = require("vue");
|
|
28
|
+
var import_open_wysiwyg_editor = require("open-wysiwyg-editor");
|
|
29
|
+
__reExport(src_exports, require("open-wysiwyg-editor"), module.exports);
|
|
30
|
+
var RichTextEditor = (0, import_vue.defineComponent)({
|
|
31
|
+
name: "RichTextEditor",
|
|
32
|
+
props: {
|
|
33
|
+
/** The content as HTML (`v-model`). */
|
|
34
|
+
modelValue: { type: String, default: void 0 },
|
|
35
|
+
/** Everything else `createEditor()` accepts. */
|
|
36
|
+
options: { type: Object, default: () => ({}) },
|
|
37
|
+
editable: { type: Boolean, default: true },
|
|
38
|
+
placeholder: { type: String, default: void 0 },
|
|
39
|
+
/** Form field name: the HTML is kept in a hidden input for plain form posts. */
|
|
40
|
+
name: { type: String, default: void 0 }
|
|
41
|
+
},
|
|
42
|
+
emits: {
|
|
43
|
+
"update:modelValue": (html) => typeof html === "string",
|
|
44
|
+
ready: (editor) => !!editor
|
|
45
|
+
},
|
|
46
|
+
setup(props, { emit, expose }) {
|
|
47
|
+
const host = (0, import_vue.shallowRef)();
|
|
48
|
+
const field = (0, import_vue.shallowRef)();
|
|
49
|
+
const editor = (0, import_vue.shallowRef)(null);
|
|
50
|
+
let applied = props.modelValue;
|
|
51
|
+
const resolved = () => ({
|
|
52
|
+
...props.options,
|
|
53
|
+
editable: props.editable && props.options.editable !== false,
|
|
54
|
+
placeholder: props.placeholder ?? props.options.placeholder
|
|
55
|
+
});
|
|
56
|
+
const syncField = (html) => {
|
|
57
|
+
if (field.value) field.value.value = html;
|
|
58
|
+
};
|
|
59
|
+
(0, import_vue.onMounted)(() => {
|
|
60
|
+
const instance = (0, import_open_wysiwyg_editor.createEditor)({
|
|
61
|
+
...resolved(),
|
|
62
|
+
element: host.value,
|
|
63
|
+
content: props.modelValue ?? props.options.content,
|
|
64
|
+
...(0, import_open_wysiwyg_editor.forwardCallbacks)(resolved),
|
|
65
|
+
onUpdate: (e) => {
|
|
66
|
+
applied = e.getHTML();
|
|
67
|
+
syncField(applied);
|
|
68
|
+
props.options.onUpdate?.(e);
|
|
69
|
+
emit("update:modelValue", applied);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
editor.value = instance;
|
|
73
|
+
syncField(instance.getHTML());
|
|
74
|
+
emit("ready", instance);
|
|
75
|
+
});
|
|
76
|
+
(0, import_vue.watch)(
|
|
77
|
+
() => props.modelValue,
|
|
78
|
+
(value) => {
|
|
79
|
+
if (!editor.value || value === void 0 || value === applied) return;
|
|
80
|
+
applied = value;
|
|
81
|
+
(0, import_open_wysiwyg_editor.syncContent)(editor.value, value);
|
|
82
|
+
syncField(editor.value.getHTML());
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
(0, import_vue.watch)(
|
|
86
|
+
() => (0, import_open_wysiwyg_editor.pickRuntimeOptions)(resolved()),
|
|
87
|
+
(next, previous) => {
|
|
88
|
+
if (!(0, import_open_wysiwyg_editor.sameRuntimeOptions)(next, previous)) editor.value?.setOptions(next);
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
(0, import_vue.onBeforeUnmount)(() => {
|
|
92
|
+
editor.value?.destroy();
|
|
93
|
+
editor.value = null;
|
|
94
|
+
});
|
|
95
|
+
expose({ editor });
|
|
96
|
+
return () => (0, import_vue.h)("div", null, [
|
|
97
|
+
(0, import_vue.h)("div", { ref: host }),
|
|
98
|
+
props.name ? (0, import_vue.h)("input", { ref: field, type: "hidden", name: props.name, value: props.modelValue ?? "" }) : null
|
|
99
|
+
]);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
103
|
+
0 && (module.exports = {
|
|
104
|
+
RichTextEditor,
|
|
105
|
+
...require("open-wysiwyg-editor")
|
|
106
|
+
});
|
|
107
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Vue 3 adapter (Vue 3.3+, Nuxt, Vite, Quasar). The editor is created in onMounted, which never\n// runs on the server, so the component is SSR-safe without <ClientOnly>.\nimport { defineComponent, h, onBeforeUnmount, onMounted, shallowRef, watch, type PropType } from \"vue\";\nimport {\n createEditor,\n forwardCallbacks,\n pickRuntimeOptions,\n sameRuntimeOptions,\n syncContent,\n type Editor,\n type EditorOptions,\n} from \"open-wysiwyg-editor\";\n\n// One install gives the whole API: StarterKit, getUI, types…\nexport * from \"open-wysiwyg-editor\";\n\nexport type RichTextEditorOptions = Omit<EditorOptions, \"element\">;\n\n/**\n * The full editor (toolbar, dialogs, status bar) as a component with `v-model` (HTML).\n *\n * ```vue\n * <RichTextEditor v-model=\"html\" placeholder=\"Write…\" :options=\"{ language: 'ar' }\" />\n * ```\n *\n * `options` takes everything `createEditor()` accepts. Callbacks and the runtime options\n * (`editable`, `placeholder`, `aria*`, `dir`, `contentLang`) follow changes; the rest is read once at\n * mount. The template ref exposes `editor`.\n */\nexport const RichTextEditor = defineComponent({\n name: \"RichTextEditor\",\n props: {\n /** The content as HTML (`v-model`). */\n modelValue: { type: String, default: undefined },\n /** Everything else `createEditor()` accepts. */\n options: { type: Object as PropType<RichTextEditorOptions>, default: () => ({}) },\n editable: { type: Boolean, default: true },\n placeholder: { type: String, default: undefined },\n /** Form field name: the HTML is kept in a hidden input for plain form posts. */\n name: { type: String, default: undefined },\n },\n emits: {\n \"update:modelValue\": (html: string) => typeof html === \"string\",\n ready: (editor: Editor) => !!editor,\n },\n setup(props, { emit, expose }) {\n const host = shallowRef<HTMLElement>();\n const field = shallowRef<HTMLInputElement>();\n const editor = shallowRef<Editor | null>(null);\n let applied = props.modelValue;\n\n const resolved = (): EditorOptions => ({\n ...props.options,\n editable: props.editable && props.options.editable !== false,\n placeholder: props.placeholder ?? props.options.placeholder,\n });\n const syncField = (html: string) => {\n if (field.value) field.value.value = html;\n };\n\n onMounted(() => {\n const instance = createEditor({\n ...resolved(),\n element: host.value,\n content: props.modelValue ?? props.options.content,\n ...forwardCallbacks(resolved),\n onUpdate: (e) => {\n applied = e.getHTML();\n syncField(applied);\n props.options.onUpdate?.(e);\n emit(\"update:modelValue\", applied);\n },\n });\n editor.value = instance;\n syncField(instance.getHTML());\n emit(\"ready\", instance);\n });\n\n watch(\n () => props.modelValue,\n (value) => {\n if (!editor.value || value === undefined || value === applied) return;\n applied = value;\n syncContent(editor.value, value);\n syncField(editor.value.getHTML());\n },\n );\n\n watch(\n () => pickRuntimeOptions(resolved()),\n (next, previous) => {\n if (!sameRuntimeOptions(next, previous)) editor.value?.setOptions(next);\n },\n );\n\n onBeforeUnmount(() => {\n editor.value?.destroy();\n editor.value = null;\n });\n\n expose({ editor });\n return () =>\n h(\"div\", null, [\n h(\"div\", { ref: host }),\n props.name\n ? h(\"input\", { ref: field, type: \"hidden\", name: props.name, value: props.modelValue ?? \"\" })\n : null,\n ]);\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,iBAAiG;AACjG,iCAQO;AAGP,wBAAc,gCAdd;AA6BO,IAAM,qBAAiB,4BAAgB;AAAA,EAC5C,MAAM;AAAA,EACN,OAAO;AAAA;AAAA,IAEL,YAAY,EAAE,MAAM,QAAQ,SAAS,OAAU;AAAA;AAAA,IAE/C,SAAS,EAAE,MAAM,QAA2C,SAAS,OAAO,CAAC,GAAG;AAAA,IAChF,UAAU,EAAE,MAAM,SAAS,SAAS,KAAK;AAAA,IACzC,aAAa,EAAE,MAAM,QAAQ,SAAS,OAAU;AAAA;AAAA,IAEhD,MAAM,EAAE,MAAM,QAAQ,SAAS,OAAU;AAAA,EAC3C;AAAA,EACA,OAAO;AAAA,IACL,qBAAqB,CAAC,SAAiB,OAAO,SAAS;AAAA,IACvD,OAAO,CAAC,WAAmB,CAAC,CAAC;AAAA,EAC/B;AAAA,EACA,MAAM,OAAO,EAAE,MAAM,OAAO,GAAG;AAC7B,UAAM,WAAO,uBAAwB;AACrC,UAAM,YAAQ,uBAA6B;AAC3C,UAAM,aAAS,uBAA0B,IAAI;AAC7C,QAAI,UAAU,MAAM;AAEpB,UAAM,WAAW,OAAsB;AAAA,MACrC,GAAG,MAAM;AAAA,MACT,UAAU,MAAM,YAAY,MAAM,QAAQ,aAAa;AAAA,MACvD,aAAa,MAAM,eAAe,MAAM,QAAQ;AAAA,IAClD;AACA,UAAM,YAAY,CAAC,SAAiB;AAClC,UAAI,MAAM,MAAO,OAAM,MAAM,QAAQ;AAAA,IACvC;AAEA,8BAAU,MAAM;AACd,YAAM,eAAW,yCAAa;AAAA,QAC5B,GAAG,SAAS;AAAA,QACZ,SAAS,KAAK;AAAA,QACd,SAAS,MAAM,cAAc,MAAM,QAAQ;AAAA,QAC3C,OAAG,6CAAiB,QAAQ;AAAA,QAC5B,UAAU,CAAC,MAAM;AACf,oBAAU,EAAE,QAAQ;AACpB,oBAAU,OAAO;AACjB,gBAAM,QAAQ,WAAW,CAAC;AAC1B,eAAK,qBAAqB,OAAO;AAAA,QACnC;AAAA,MACF,CAAC;AACD,aAAO,QAAQ;AACf,gBAAU,SAAS,QAAQ,CAAC;AAC5B,WAAK,SAAS,QAAQ;AAAA,IACxB,CAAC;AAED;AAAA,MACE,MAAM,MAAM;AAAA,MACZ,CAAC,UAAU;AACT,YAAI,CAAC,OAAO,SAAS,UAAU,UAAa,UAAU,QAAS;AAC/D,kBAAU;AACV,oDAAY,OAAO,OAAO,KAAK;AAC/B,kBAAU,OAAO,MAAM,QAAQ,CAAC;AAAA,MAClC;AAAA,IACF;AAEA;AAAA,MACE,UAAM,+CAAmB,SAAS,CAAC;AAAA,MACnC,CAAC,MAAM,aAAa;AAClB,YAAI,KAAC,+CAAmB,MAAM,QAAQ,EAAG,QAAO,OAAO,WAAW,IAAI;AAAA,MACxE;AAAA,IACF;AAEA,oCAAgB,MAAM;AACpB,aAAO,OAAO,QAAQ;AACtB,aAAO,QAAQ;AAAA,IACjB,CAAC;AAED,WAAO,EAAE,OAAO,CAAC;AACjB,WAAO,UACL,cAAE,OAAO,MAAM;AAAA,UACb,cAAE,OAAO,EAAE,KAAK,KAAK,CAAC;AAAA,MACtB,MAAM,WACF,cAAE,SAAS,EAAE,KAAK,OAAO,MAAM,UAAU,MAAM,MAAM,MAAM,OAAO,MAAM,cAAc,GAAG,CAAC,IAC1F;AAAA,IACN,CAAC;AAAA,EACL;AACF,CAAC;","names":[]}
|