@dojo-ng/rich-text 0.1.0 → 0.1.2

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 CHANGED
@@ -6,7 +6,7 @@ Part of [Dojo NG](../../README.md), a framework-agnostic web component library b
6
6
 
7
7
  A form-associated WYSIWYG editor built on the Lexical core. The editable region renders in LIGHT DOM (Lexical's selection handling is not reliable inside a shadow root yet), so this component overrides `createRenderRoot`; theming still works because `--dj-*` tokens cascade in light DOM. The editor is a PLUGIN HOST: bold/italic/underline and undo/redo ship as the default plugin set (`default-plugins.ts`) and flow through the same {@link RichTextPlugin} API third-party plugins use. Foundational behavior (`registerRichText`, value sync, root-element setup) stays as core. Toolbar controls, node registration, and output formats all come from plugins. Constraint: Lexical needs node classes at creation, so a `plugins` change after creation rebuilds the editor (serialize → recreate → deserialize). Value is HTML by default; the `format` property selects an alternate serializer contributed by a plugin. Event: `dj-change`.
8
8
 
9
- > Pasted HTML is sanitized against an allowlist by default (scripts, styles, event handlers, inline styles, and unsafe `javascript:`/`data:` URLs are stripped; unknown tags are unwrapped, keeping their text) — a security and consistency hook, not a nicety. Set `sanitizePaste = false` in JS to turn it off, or supply your own `pasteSanitizer(html) => html`. Plain-text pastes bypass it. The exported `sanitizeHtml(html)` is the default and can be reused. Formatting, headings/lists/links, and other node types come from plugins; setting `plugins` replaces the default set, so spread `...defaultPlugins` to keep the basics.
9
+ > Pasted HTML is sanitized against an allowlist by default (scripts, styles, event handlers, inline styles, and unsafe `javascript:`/`data:` URLs are stripped; unknown tags are unwrapped, keeping their text) — a security and consistency hook, not a nicety. Set `sanitizePaste = false` in JS to turn it off, or supply your own `pasteSanitizer(html) => html`. Plain-text pastes bypass it. The exported `sanitizeHtml(html)` is the default and can be reused. Formatting, headings/lists/links, and other node types come from plugins; setting `plugins` replaces the default set, so spread `...defaultPlugins` to keep the basics. `value` is readable and writable at any time, not just at construction: assigning it replaces the whole document (discarding the selection and undo history) and emits no `dj-change`, the same as setting a native input's `value`.
10
10
 
11
11
  ## Install
12
12
 
@@ -58,4 +58,4 @@ Follows the project's WCAG 2.2 AA and localization conventions.
58
58
 
59
59
  ## More
60
60
 
61
- Live, interactive examples are in the playground (`playground/index.html`). For the full API reference, theming, accessibility, and localization guides, see the [Dojo NG documentation](../../README.md).
61
+ Live, interactive examples are at [play.dojo-ng.com](https://play.dojo-ng.com). For the full API reference, theming, accessibility, and localization guides, see the [Dojo NG documentation](../../README.md).
@@ -26,6 +26,10 @@ export declare class DjRichText extends DjRichText_base implements Partial<DojoF
26
26
  static formAssociated: boolean;
27
27
  protected createRenderRoot(): this;
28
28
  private editable;
29
+ /** The document, serialized through the active `format` (HTML by default). Readable and writable
30
+ * at any time, not just at construction: assigning after the editor is built replaces the whole
31
+ * document, discarding the selection and undo history, and emits no `dj-change` — the same as
32
+ * setting a native input's `value`. */
29
33
  value: string;
30
34
  name?: string;
31
35
  label?: string;
@@ -15,7 +15,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
15
15
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
16
16
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
17
17
  };
18
- var _DjRichText_instances, _DjRichText_internals, _DjRichText_editor, _DjRichText_applyingValue, _DjRichText_built, _DjRichText_disposers, _DjRichText_active, _DjRichText_selectionSubs, _DjRichText_serializers, _DjRichText_plugins, _DjRichText_ctx, _DjRichText_resolvePlugins, _DjRichText_currentFormat, _DjRichText_buildEditor, _DjRichText_teardown, _DjRichText_serialize, _DjRichText_syncValue, _DjRichText_applyValue, _DjRichText_icon, _DjRichText_renderItem, _DjRichText_toolbarItems;
18
+ var _DjRichText_instances, _DjRichText_internals, _DjRichText_editor, _DjRichText_applyingValue, _DjRichText_appliedValue, _DjRichText_built, _DjRichText_disposers, _DjRichText_active, _DjRichText_selectionSubs, _DjRichText_serializers, _DjRichText_plugins, _DjRichText_ctx, _DjRichText_resolvePlugins, _DjRichText_currentFormat, _DjRichText_buildEditor, _DjRichText_teardown, _DjRichText_serialize, _DjRichText_syncValue, _DjRichText_applyValue, _DjRichText_icon, _DjRichText_renderItem, _DjRichText_toolbarItems;
19
19
  import { html, nothing } from "lit";
20
20
  import { property, state, query } from "lit/decorators.js";
21
21
  import DojoElement, { FormControl } from "@dojo-ng/dojo-element";
@@ -66,6 +66,10 @@ export class DjRichText extends FormControl(DojoElement) {
66
66
  _DjRichText_internals.set(this, void 0);
67
67
  _DjRichText_editor.set(this, void 0);
68
68
  _DjRichText_applyingValue.set(this, false);
69
+ /** The value the editor last LOADED or last serialized OUT. `#syncValue()` writes `this.value` on
70
+ * every editor change, so `value` is in the changed-properties map on every keystroke; comparing
71
+ * against this is what tells an outside assignment apart from the editor's own echo. */
72
+ _DjRichText_appliedValue.set(this, "");
69
73
  _DjRichText_built.set(this, false);
70
74
  _DjRichText_disposers.set(this, []);
71
75
  _DjRichText_active.set(this, new Set());
@@ -73,6 +77,10 @@ export class DjRichText extends FormControl(DojoElement) {
73
77
  _DjRichText_serializers.set(this, { html: HTML_FORMAT });
74
78
  _DjRichText_plugins.set(this, []);
75
79
  _DjRichText_ctx.set(this, void 0);
80
+ /** The document, serialized through the active `format` (HTML by default). Readable and writable
81
+ * at any time, not just at construction: assigning after the editor is built replaces the whole
82
+ * document, discarding the selection and undo history, and emits no `dj-change` — the same as
83
+ * setting a native input's `value`. */
76
84
  this.value = "";
77
85
  this.placeholder = "";
78
86
  this.disabled = false;
@@ -121,6 +129,7 @@ export class DjRichText extends FormControl(DojoElement) {
121
129
  __classPrivateFieldSet(this, _DjRichText_built, false, "f");
122
130
  }
123
131
  updated(c) {
132
+ let appliedValue = false;
124
133
  if (!__classPrivateFieldGet(this, _DjRichText_editor, "f")) {
125
134
  __classPrivateFieldGet(this, _DjRichText_instances, "m", _DjRichText_buildEditor).call(this);
126
135
  }
@@ -130,12 +139,21 @@ export class DjRichText extends FormControl(DojoElement) {
130
139
  __classPrivateFieldGet(this, _DjRichText_instances, "m", _DjRichText_teardown).call(this);
131
140
  __classPrivateFieldGet(this, _DjRichText_instances, "m", _DjRichText_buildEditor).call(this);
132
141
  }
142
+ else if (c.has("value") && this.value !== __classPrivateFieldGet(this, _DjRichText_appliedValue, "f")) {
143
+ // A value set from OUTSIDE the editor. An equal value here is the editor's own echo and
144
+ // must not be re-deserialized: doing so would rebuild the document under the caret on
145
+ // every character typed.
146
+ __classPrivateFieldGet(this, _DjRichText_instances, "m", _DjRichText_applyValue).call(this, this.value, __classPrivateFieldGet(this, _DjRichText_instances, "m", _DjRichText_currentFormat).call(this));
147
+ appliedValue = true;
148
+ }
133
149
  if (c.has("disabled")) {
134
150
  __classPrivateFieldGet(this, _DjRichText_editor, "f")?.setEditable(!this.isDisabled);
135
151
  if (this.editable)
136
152
  this.editable.contentEditable = this.isDisabled ? "false" : "true";
137
153
  }
138
- if (c.has("format") && !c.has("plugins") && __classPrivateFieldGet(this, _DjRichText_built, "f"))
154
+ // Skipped when the value branch just ran: that already loaded the document through the new
155
+ // format, and serializing straight back out would emit a spurious dj-change.
156
+ if (c.has("format") && !c.has("plugins") && !appliedValue && __classPrivateFieldGet(this, _DjRichText_built, "f"))
139
157
  __classPrivateFieldGet(this, _DjRichText_instances, "m", _DjRichText_syncValue).call(this);
140
158
  }
141
159
  /** Replace the document with the given HTML (regardless of the active `format`). */
@@ -154,7 +172,7 @@ export class DjRichText extends FormControl(DojoElement) {
154
172
  return html `
155
173
  <style>
156
174
  dj-rich-text { display: block; font-family: var(--dj-font-family, inherit); }
157
- dj-rich-text .dj-rt-toolbar { display: flex; align-items: center; gap: .15rem; padding: .25rem; border: 1px solid var(--dj-color-border, #d1d5db); border-bottom: none; border-radius: var(--dj-input-border-radius-medium, .25rem) var(--dj-input-border-radius-medium, .25rem) 0 0; background: var(--dj-color-neutral-50, #f9fafb); }
175
+ dj-rich-text .dj-rt-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: .15rem; padding: .25rem; border: 1px solid var(--dj-color-border, #d1d5db); border-bottom: none; border-radius: var(--dj-input-border-radius-medium, .25rem) var(--dj-input-border-radius-medium, .25rem) 0 0; background: var(--dj-color-neutral-50, #f9fafb); }
158
176
  dj-rich-text .dj-rt-sep { align-self: stretch; width: 1px; margin: .15rem .25rem; background: var(--dj-color-border, #d1d5db); }
159
177
  dj-rich-text .dj-rt-editable { min-height: 8rem; padding: var(--dj-spacing-small, .75rem); border: 1px solid var(--dj-color-border, #d1d5db); border-radius: 0 0 var(--dj-input-border-radius-medium, .25rem) var(--dj-input-border-radius-medium, .25rem); outline: none; background: var(--dj-color-background, #fff); color: var(--dj-color-text, #1f2937); }
160
178
  dj-rich-text .dj-rt-editable:focus { border-color: var(--dj-color-primary-600, #2563eb); box-shadow: 0 0 0 1px var(--dj-color-primary-600, #2563eb); }
@@ -171,7 +189,7 @@ export class DjRichText extends FormControl(DojoElement) {
171
189
  `;
172
190
  }
173
191
  }
174
- _DjRichText_internals = new WeakMap(), _DjRichText_editor = new WeakMap(), _DjRichText_applyingValue = new WeakMap(), _DjRichText_built = new WeakMap(), _DjRichText_disposers = new WeakMap(), _DjRichText_active = new WeakMap(), _DjRichText_selectionSubs = new WeakMap(), _DjRichText_serializers = new WeakMap(), _DjRichText_plugins = new WeakMap(), _DjRichText_ctx = new WeakMap(), _DjRichText_instances = new WeakSet(), _DjRichText_resolvePlugins = function _DjRichText_resolvePlugins() {
192
+ _DjRichText_internals = new WeakMap(), _DjRichText_editor = new WeakMap(), _DjRichText_applyingValue = new WeakMap(), _DjRichText_appliedValue = new WeakMap(), _DjRichText_built = new WeakMap(), _DjRichText_disposers = new WeakMap(), _DjRichText_active = new WeakMap(), _DjRichText_selectionSubs = new WeakMap(), _DjRichText_serializers = new WeakMap(), _DjRichText_plugins = new WeakMap(), _DjRichText_ctx = new WeakMap(), _DjRichText_instances = new WeakSet(), _DjRichText_resolvePlugins = function _DjRichText_resolvePlugins() {
175
193
  return this.plugins.length ? this.plugins : defaultPlugins;
176
194
  }, _DjRichText_currentFormat = function _DjRichText_currentFormat() {
177
195
  return __classPrivateFieldGet(this, _DjRichText_serializers, "f")[this.format] ?? HTML_FORMAT;
@@ -261,6 +279,7 @@ _DjRichText_internals = new WeakMap(), _DjRichText_editor = new WeakMap(), _DjRi
261
279
  }
262
280
  __classPrivateFieldSet(this, _DjRichText_built, true, "f");
263
281
  this.ready = true; // re-render so the toolbar shows now that ctx.editor is live
282
+ __classPrivateFieldSet(this, _DjRichText_appliedValue, this.value, "f");
264
283
  if (this.value)
265
284
  __classPrivateFieldGet(this, _DjRichText_instances, "m", _DjRichText_applyValue).call(this, this.value, __classPrivateFieldGet(this, _DjRichText_instances, "m", _DjRichText_currentFormat).call(this));
266
285
  }, _DjRichText_teardown = function _DjRichText_teardown() {
@@ -277,12 +296,14 @@ _DjRichText_internals = new WeakMap(), _DjRichText_editor = new WeakMap(), _DjRi
277
296
  return editor.getEditorState().read(() => fmt.serialize(editor));
278
297
  }, _DjRichText_syncValue = function _DjRichText_syncValue() {
279
298
  const out = __classPrivateFieldGet(this, _DjRichText_instances, "m", _DjRichText_serialize).call(this);
299
+ __classPrivateFieldSet(this, _DjRichText_appliedValue, out, "f");
280
300
  if (out !== this.value) {
281
301
  this.value = out;
282
302
  __classPrivateFieldGet(this, _DjRichText_internals, "f").setFormValue(out);
283
303
  this.emit("dj-change");
284
304
  }
285
305
  }, _DjRichText_applyValue = function _DjRichText_applyValue(data, fmt) {
306
+ __classPrivateFieldSet(this, _DjRichText_appliedValue, data, "f");
286
307
  const editor = __classPrivateFieldGet(this, _DjRichText_editor, "f");
287
308
  if (!editor) {
288
309
  this.value = data;
@@ -322,7 +343,7 @@ _DjRichText_internals = new WeakMap(), _DjRichText_editor = new WeakMap(), _DjRi
322
343
  }
323
344
  return items;
324
345
  };
325
- DjRichText.version = "0.1.0";
346
+ DjRichText.version = "0.1.2";
326
347
  DjRichText.focusable = true;
327
348
  DjRichText.formAssociated = true;
328
349
  __decorate([
package/package.json CHANGED
@@ -1,5 +1,29 @@
1
- { "name": "@dojo-ng/rich-text", "version": "0.1.0", "license": "BSD-3-Clause", "description": "Dojo NG rich-text (WYSIWYG) web component on Lexical", "type": "module",
2
- "main": "./dist/index.js", "module": "./dist/index.js", "types": "./dist/index.d.ts",
3
- "files": ["dist"],
4
- "scripts": { "build": "tsc -p tsconfig.json", "clean": "rm -rf dist" },
5
- "dependencies": { "@dojo-ng/dojo-element": "^0.1.0", "@dojo-ng/button": "^0.1.0", "lexical": "^0.21.0", "@lexical/rich-text": "^0.21.0", "@lexical/history": "^0.21.0", "@lexical/html": "^0.21.0", "@lexical/clipboard": "^0.21.0", "@lexical/utils": "^0.21.0", "lit": "^3.3.0" } }
1
+ {
2
+ "name": "@dojo-ng/rich-text",
3
+ "version": "0.1.2",
4
+ "license": "BSD-3-Clause",
5
+ "description": "Dojo NG rich-text (WYSIWYG) web component on Lexical",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "files": [
11
+ "dist",
12
+ "!dist/**/*.map"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsc -p tsconfig.json",
16
+ "clean": "rm -rf dist"
17
+ },
18
+ "dependencies": {
19
+ "@dojo-ng/dojo-element": "^0.1.0",
20
+ "@dojo-ng/button": "^0.1.1",
21
+ "lexical": "^0.21.0",
22
+ "@lexical/rich-text": "^0.21.0",
23
+ "@lexical/history": "^0.21.0",
24
+ "@lexical/html": "^0.21.0",
25
+ "@lexical/clipboard": "^0.21.0",
26
+ "@lexical/utils": "^0.21.0",
27
+ "lit": "^3.3.0"
28
+ }
29
+ }