u2ui 1.5.3 → 1.5.7

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.
Files changed (146) hide show
  1. package/el/ico/ico.js +5 -5
  2. package/el/rte/README.md +53 -13
  3. package/el/rte/manifest.json +19 -4
  4. package/el/rte/rte.css +31 -6
  5. package/el/rte/rte.js +174 -44
  6. package/el/rte0/README.md +37 -0
  7. package/el/rte0/manifest.json +33 -0
  8. package/el/rte0/rte0.css +9 -0
  9. package/el/rte0/rte0.js +56 -0
  10. package/el/system/styler.js +2 -0
  11. package/el/tooltip/tooltip.css +2 -2
  12. package/js/Placer/Placer.js +2 -1
  13. package/js/rte/AGENTS.md +142 -0
  14. package/js/rte/PLAN.md +487 -0
  15. package/js/rte/README.md +319 -25
  16. package/js/rte/blocks.js +7 -0
  17. package/js/rte/breaks.js +6 -0
  18. package/js/rte/classes.js +7 -0
  19. package/js/rte/docs/usage.md +845 -0
  20. package/js/rte/images.js +7 -0
  21. package/js/rte/link.js +8 -0
  22. package/js/rte/playground/README.md +95 -0
  23. package/js/rte/playground/hosts.html +553 -0
  24. package/js/rte/playground/index.html +835 -0
  25. package/js/rte/rte.js +56 -5
  26. package/js/rte/run.out +17 -0
  27. package/js/rte/source.js +12 -0
  28. package/js/rte/src/browser/README.md +40 -0
  29. package/js/rte/src/browser/interactive.js +17 -0
  30. package/js/rte/src/browser/range-rect.js +82 -0
  31. package/js/rte/src/client/README.md +385 -0
  32. package/js/rte/src/client/blocks.js +38 -0
  33. package/js/rte/src/client/breaks.js +212 -0
  34. package/js/rte/src/client/classes.js +48 -0
  35. package/js/rte/src/client/editor.js +472 -0
  36. package/js/rte/src/client/external-input.js +47 -0
  37. package/js/rte/src/client/history.js +13 -0
  38. package/js/rte/src/client/images.js +285 -0
  39. package/js/rte/src/client/link.js +567 -0
  40. package/js/rte/src/client/marks.js +25 -0
  41. package/js/rte/src/client/source.js +182 -0
  42. package/js/rte/src/client/structure.js +27 -0
  43. package/js/rte/src/client/tables.js +165 -0
  44. package/js/rte/src/client/unstyle.js +32 -0
  45. package/js/rte/src/command/README.md +520 -0
  46. package/js/rte/src/command/block-boundary.js +43 -0
  47. package/js/rte/src/command/block-style.js +120 -0
  48. package/js/rte/src/command/commands.js +159 -0
  49. package/js/rte/src/command/delete.js +119 -0
  50. package/js/rte/src/command/edit.js +78 -0
  51. package/js/rte/src/command/element.js +45 -0
  52. package/js/rte/src/command/enter.js +175 -0
  53. package/js/rte/src/command/fragment.js +76 -0
  54. package/js/rte/src/command/insert.js +52 -0
  55. package/js/rte/src/command/list.js +284 -0
  56. package/js/rte/src/command/mark.js +583 -0
  57. package/js/rte/src/command/pending-marks.js +216 -0
  58. package/js/rte/src/command/table.js +207 -0
  59. package/js/rte/src/command/unstyle.js +248 -0
  60. package/js/rte/src/config/README.md +96 -0
  61. package/js/rte/src/config/config.js +134 -0
  62. package/js/rte/src/core/README.md +63 -0
  63. package/js/rte/src/core/core.js +247 -0
  64. package/js/rte/src/history/README.md +83 -0
  65. package/js/rte/src/history/history.js +283 -0
  66. package/js/rte/src/input/README.md +173 -0
  67. package/js/rte/src/input/external-input.js +130 -0
  68. package/js/rte/src/input/input-pipeline.js +459 -0
  69. package/js/rte/src/mark/README.md +219 -0
  70. package/js/rte/src/mark/dom-adapter.js +100 -0
  71. package/js/rte/src/mark/mark.js +133 -0
  72. package/js/rte/src/mark/standard.js +76 -0
  73. package/js/rte/src/model/README.md +71 -0
  74. package/js/rte/src/model/content-model.js +213 -0
  75. package/js/rte/src/model/html/README.md +51 -0
  76. package/js/rte/src/model/html/html-model.js +90 -0
  77. package/js/rte/src/normalize/execute/README.md +33 -0
  78. package/js/rte/src/normalize/execute/repair-executor.js +129 -0
  79. package/js/rte/src/normalize/normalizer/README.md +48 -0
  80. package/js/rte/src/normalize/normalizer/normalizer.js +141 -0
  81. package/js/rte/src/normalize/repair/README.md +70 -0
  82. package/js/rte/src/normalize/repair/repair-planner.js +208 -0
  83. package/js/rte/src/sanitize/README.md +108 -0
  84. package/js/rte/src/sanitize/native.js +43 -0
  85. package/js/rte/src/sanitize/policy.js +206 -0
  86. package/js/rte/src/selection/README.md +39 -0
  87. package/js/rte/src/selection/map/README.md +53 -0
  88. package/js/rte/src/selection/map/point-map.js +307 -0
  89. package/js/rte/src/selection/ownership/README.md +32 -0
  90. package/js/rte/src/selection/ownership/ownership.js +42 -0
  91. package/js/rte/src/selection/point/README.md +34 -0
  92. package/js/rte/src/selection/point/point.js +83 -0
  93. package/js/rte/src/selection/range/README.md +38 -0
  94. package/js/rte/src/selection/range/edit-range.js +223 -0
  95. package/js/rte/src/selection/snapshot.js +64 -0
  96. package/js/rte/src/source/README.md +64 -0
  97. package/js/rte/src/source/source.js +193 -0
  98. package/js/rte/src/surface/README.md +36 -0
  99. package/js/rte/src/surface/content.js +16 -0
  100. package/js/rte/src/surface/surface.js +87 -0
  101. package/js/rte/src/transaction/README.md +35 -0
  102. package/js/rte/src/transaction/transaction.js +56 -0
  103. package/js/rte/src/ui/README.md +271 -0
  104. package/js/rte/src/ui/caret.js +17 -0
  105. package/js/rte/src/ui/chrome.js +214 -0
  106. package/js/rte/src/ui/handles.js +179 -0
  107. package/js/rte/src/ui/highlight.js +86 -0
  108. package/js/rte/src/ui/place.js +25 -0
  109. package/js/rte/src/ui/toolbar.js +258 -0
  110. package/js/rte/src/unstyle/README.md +103 -0
  111. package/js/rte/src/unstyle/unstyle.js +140 -0
  112. package/js/rte/tables.js +7 -0
  113. package/js/rte/unstyle.js +8 -0
  114. package/js/rte0/README.md +37 -0
  115. package/js/rte0/rte.js +7 -0
  116. package/js/{rte → rte0}/src/behavior.js +0 -1
  117. package/js/{rte → rte0}/src/events.js +0 -1
  118. package/js/{rte → rte0}/src/range.js +0 -2
  119. package/js/{rte → rte0}/src/ui.items.js +0 -2
  120. package/js/{rte → rte0}/src/ui.js +0 -1
  121. package/package.json +1 -1
  122. package/u2/Element.js +2 -2
  123. package/u2/plan-scoped-refactoring.md +133 -303
  124. package/u2/projects.json +18 -2
  125. package/u2/tools/update.repos.json.js +1 -1
  126. package/el/qrcode/package.json +0 -22
  127. /package/js/{rte → rte0}/old/NodeCleaner.js +0 -0
  128. /package/js/{rte → rte0}/old/Rte/Rte.mjs +0 -0
  129. /package/js/{rte → rte0}/old/Rte/Rte.ui.items.mjs +0 -0
  130. /package/js/{rte → rte0}/old/Rte/Rte.ui.mjs +0 -0
  131. /package/js/{rte → rte0}/old/Rte/crossbrowser.mjs +0 -0
  132. /package/js/{rte → rte0}/old/Rte/font/tinymce.svg +0 -0
  133. /package/js/{rte → rte0}/old/Rte/font/tinymce.woff +0 -0
  134. /package/js/{rte → rte0}/old/Rte/helpers.mjs +0 -0
  135. /package/js/{rte → rte0}/old/Rte/htmlparser.mjs +0 -0
  136. /package/js/{rte → rte0}/old/Rte/index.mjs +0 -0
  137. /package/js/{rte → rte0}/old/Rte/main.css +0 -0
  138. /package/js/{rte → rte0}/old/old.rte.js +0 -0
  139. /package/js/{rte → rte0}/old/range-selection.old.js +0 -0
  140. /package/js/{rte → rte0}/src/fixes.js +0 -0
  141. /package/js/{rte → rte0}/src/rte.js +0 -0
  142. /package/js/{rte → rte0}/src/ui/items/ai.js +0 -0
  143. /package/js/{rte → rte0}/src/ui/items/code.js +0 -0
  144. /package/js/{rte → rte0}/src/ui/items/undo.js +0 -0
  145. /package/js/{rte → rte0}/src/util/htmlparser.js +0 -0
  146. /package/js/{rte → rte0}/src/util/indentHtml.js +0 -0
package/el/ico/ico.js CHANGED
@@ -61,14 +61,14 @@ export default class U2Ico extends HTMLElement {
61
61
  const name = this.getAttribute('icon');
62
62
  const path = dirTemplateToUrl(dir, name);
63
63
 
64
- requestAnimationFrame(()=>{
65
- this.setAttribute('state','loading')
66
- });
67
-
68
64
  const pathStr = path.toString();
69
65
  if (this._lastLoading === pathStr) return; // prevent multiple requests
70
66
  this._lastLoading = pathStr;
71
67
 
68
+ requestAnimationFrame(()=>{
69
+ this.setAttribute('state','loading')
70
+ });
71
+
72
72
  loadSvgString(path).then(svg=>{
73
73
  // if (path.origin !== location.origin) {} todo: sanitize svg
74
74
 
@@ -85,7 +85,7 @@ export default class U2Ico extends HTMLElement {
85
85
  const height = parseFloat(svgEl.getAttribute('height')) ?? 24;
86
86
  svgEl.setAttribute('viewBox', '0 0 ' + width + ' ' + height);
87
87
  }
88
- ['xmlns','xmlns:xlink','version','class','width','height'].forEach(attr=>svgEl.removeAttribute(attr)); // remove width, height ok?
88
+ ['xmlns','xmlns:xlink','version','class'].forEach(attr=>svgEl.removeAttribute(attr));
89
89
  svgEl.ariaHidden = true;
90
90
  this.setAttribute('state','loaded');
91
91
  this.dispatchEvent(new CustomEvent('u2-ico-loaded', {bubbles: true, composed: true}));
package/el/rte/README.md CHANGED
@@ -1,21 +1,16 @@
1
1
  # <u2-rte> - element
2
- "Rich" Rich Text Editor (alpha)
2
+ Progressively enhances one native textarea with the RTE rich-text editor
3
+
4
+ The textarea remains the form control, fallback, and canonical serialized value.
3
5
 
4
6
  ## Usage
5
7
 
6
8
  ```html
7
- <u2-rte style="--u2-rte-toolbar:Bold Italic">
8
- <strong>RTE</strong>
9
- <br>
10
- <i>(Rich Text Editor)</i>
11
- </u2-rte>
12
- ```
13
-
14
- ```css
15
- u2-rte {
16
- --u2-rte:true;
17
- --u2-rte-toolbar:Bold Italic;
18
- }
9
+ <form>
10
+ <u2-rte style="--u2-rte-toolbar:bold italic">
11
+ <textarea name=content><p>Some <strong>HTML</strong></p></textarea>
12
+ </u2-rte>
13
+ </form>
19
14
  ```
20
15
 
21
16
  ## Install
@@ -30,6 +25,51 @@ u2-rte {
30
25
  [test.html](http://gcdn.li/u2ui/u2@main/el/rte/tests/test.html)
31
26
  [minimal.html](http://gcdn.li/u2ui/u2@main/el/rte/tests/minimal.html)
32
27
 
28
+ ## Behavior
29
+
30
+ HTML is the default language. Markdown loads its converters only when used:
31
+
32
+ ```html
33
+ <u2-rte language="markdown">
34
+ <textarea name="content">Some **Markdown**</textarea>
35
+ </u2-rte>
36
+ ```
37
+
38
+ Exactly one direct `textarea` is required. Free child content is deliberately
39
+ not supported. Put form attributes such as `name`, `required`, `disabled`,
40
+ `readonly`, `placeholder`, and `form` on the textarea.
41
+
42
+ The `value` property reads and writes the serialized textarea value. Editor
43
+ changes update it before a bubbling `input` event is emitted from the textarea.
44
+ A bubbling `change` event follows when focus leaves an editor changed since its
45
+ last change event. Form reset, disabled, readonly, placeholder, direction,
46
+ language, and spellchecking state are mirrored back to the editor.
47
+
48
+ ## Languages
49
+
50
+ - `html` (default) stores sanitized HTML.
51
+ - `markdown` parses with Marked and serializes with Turndown. The dependencies
52
+ are loaded lazily from jsDelivr.
53
+
54
+ Other formats need a proven round-trip between their source text and the HTML
55
+ DOM edited by RTE. They should not be added as aliases or partial parsers.
56
+
57
+ ## TODO
58
+
59
+ - Add `el/rte` to U2's generated project catalog when the module is ready for
60
+ automatic loading. It currently requires the explicit module import shown
61
+ above because that catalog lives outside this module.
62
+ - RTE needs a shared root-aware convention-client entry point before this
63
+ adapter can support editors placed inside an application ShadowRoot. The
64
+ current public convenience client owns the document root only.
65
+ - RTE should report whether a committed change already has a corresponding
66
+ native `input` event. The adapter currently uses the transaction trigger to
67
+ avoid emitting duplicate input events.
68
+ - Complete label, constraint-validation, selection, and focus tests for the
69
+ visually hidden textarea across current Chromium, Firefox, and WebKit.
70
+ - Allow applications to provide language codecs without turning the initial
71
+ two built-in formats into a general plugin system prematurely.
72
+
33
73
  ## About
34
74
 
35
75
  - MIT License, Copyright (c) 2022 <u2> (like all repositories in this organization) <br>
@@ -6,11 +6,26 @@
6
6
  "path": "rte.js",
7
7
  "declarations": [{
8
8
  "kind": "class",
9
- "description": "\"Rich\" Rich Text Editor (alpha)",
9
+ "description": "Progressively enhances a textarea with the RTE2 rich-text editor",
10
10
  "name": "U2Rte",
11
- "members": [],
12
- "events": [],
13
- "attributes": [],
11
+ "members": [{
12
+ "kind": "field",
13
+ "name": "value",
14
+ "description": "The serialized textarea value"
15
+ }],
16
+ "events": [{
17
+ "name": "input",
18
+ "description": "Fired from the textarea after the editor value changes"
19
+ },{
20
+ "name": "change",
21
+ "description": "Fired from the textarea when focus leaves a changed editor"
22
+ }],
23
+ "attributes": [{
24
+ "name": "language",
25
+ "description": "Serialization language",
26
+ "u2-type": "string",
27
+ "u2Examples": ["html", "markdown"]
28
+ }],
14
29
  "cssProperties": [{
15
30
  "name": "--u2-rte-toolbar",
16
31
  "description": "Space separated list of toolbar commands",
package/el/rte/rte.css CHANGED
@@ -1,9 +1,34 @@
1
1
  u2-rte {
2
- display:block;
3
- border:1px solid var(--color-line, color-mix(in srgb, currentColor, transparent 75%));
4
- }
2
+ display: block;
3
+ position: relative;
4
+ border: 1px solid var(--color-line, color-mix(in srgb, currentColor, transparent 75%));
5
+
6
+ & > textarea {
7
+ display: block;
8
+ inline-size: 100%;
9
+ }
10
+
11
+ &[data-ready] > textarea {
12
+ position: absolute;
13
+ inline-size: 1px;
14
+ block-size: 1px;
15
+ margin: 0 !important;
16
+ padding: 0;
17
+ border: 0;
18
+ overflow: hidden;
19
+ clip-path: inset(50%);
20
+ }
21
+
22
+ & > [data-u2-rte-editor] {
23
+ min-block-size: 6em;
24
+ padding: .5em;
25
+ outline: 0;
26
+ --u2-rte: true;
5
27
 
6
- u2-rte > textarea {
7
- display:block;
8
- width:100%;
28
+ &:empty::before {
29
+ content: attr(data-placeholder);
30
+ color: color-mix(in srgb, currentColor, transparent 45%);
31
+ pointer-events: none;
32
+ }
33
+ }
9
34
  }
package/el/rte/rte.js CHANGED
@@ -1,56 +1,186 @@
1
+ import {Source, editor} from '../../js/rte/rte.js';
1
2
 
2
- import '../../js/rte/rte.js';
3
+ const codecs = {
4
+ html: () => ({
5
+ parse: value => value,
6
+ serialize: value => value,
7
+ }),
8
+ markdown: async () => {
9
+ const [{parse}, {default: TurndownService}] = await Promise.all([
10
+ // deno-lint-ignore no-import-prefix
11
+ import('https://cdn.jsdelivr.net/npm/marked@16.2.1/+esm'),
12
+ // deno-lint-ignore no-import-prefix
13
+ import('https://cdn.jsdelivr.net/npm/turndown@7.2.0/+esm'),
14
+ ]);
15
+ const turndown = new TurndownService({headingStyle: 'atx'});
16
+ return {
17
+ parse: value => parse(value),
18
+ serialize: value => turndown.turndown(value),
19
+ };
20
+ },
21
+ };
3
22
 
4
23
  export default class U2Rte extends HTMLElement {
5
- constructor() {
6
- super();
7
- let shadowRoot = this.attachShadow({mode:'open'});
8
- shadowRoot.innerHTML =
9
- `<style>
10
- :host {
11
- display:block;
12
- --u2-rte:true;
13
- }
14
- #editor {
15
- --u2-rte:true;
16
- padding:1px; /* to avoid margin collapse */
24
+ #codec;
25
+ #source;
26
+ #observer;
27
+ #form;
28
+ #tabindex;
29
+ #dirty = false;
30
+ #writing = false;
31
+ #version = 0;
32
+
33
+ connectedCallback() {
34
+ const textarea = this.querySelector(':scope > textarea:only-child');
35
+ if (!textarea || this.children.length !== 1) {
36
+ console.error('u2-rte requires exactly one direct textarea');
37
+ return;
17
38
  }
18
- </style>
19
- <div id=editor contenteditable></div>
20
- `;
21
- //this.setAttribute('contenteditable', true);
22
- this.editor = this.shadowRoot.getElementById('editor');
23
-
24
- this.editor.addEventListener('input', (event)=>{
25
- const html = this.editor.innerHTML;
26
- const value = html;
27
- if (this.textarea) {
28
- this.textarea.value = value;
29
- } else {
30
- this.innerHTML = value;
31
- }
32
- });
39
+ this.textarea = textarea;
40
+ this.#init(++this.#version);
41
+ }
33
42
 
43
+ disconnectedCallback() {
44
+ this.#version++;
45
+ this.#dispose();
34
46
  }
35
- connectedCallback() {
36
- if (this.children.length === 1 && this.children[0].tagName === 'TEXTAREA') {
37
- this.textarea = this.children[0];
47
+
48
+ get language() { return this.getAttribute('language') || 'html'; }
49
+ get value() { return this.textarea?.value ?? ''; }
50
+ set value(value) {
51
+ if (!this.textarea) return;
52
+ this.textarea.value = String(value);
53
+ if (this.#source) this.#write(this.textarea.value);
54
+ }
55
+
56
+ async #init(version) {
57
+ const load = codecs[this.language];
58
+ if (!load) {
59
+ console.error(`u2-rte: Unsupported language: ${this.language}`);
60
+ return;
61
+ }
62
+ try {
63
+ const codec = await load();
64
+ if (!this.isConnected || version !== this.#version) return;
65
+ this.#codec = codec;
66
+ this.editor = document.createElement('div');
67
+ this.editor.contentEditable = 'true';
68
+ this.editor.dataset.u2RteEditor = '';
69
+ this.editor.role = 'textbox';
70
+ this.editor.ariaMultiLine = 'true';
71
+ this.append(this.editor);
72
+ this.surface = editor.core.add(this.editor);
73
+ this.#source = new Source(this.surface);
74
+ this.#write(this.textarea.value);
75
+ this.editor.addEventListener('input', this.#input);
76
+ this.editor.addEventListener('focusout', this.#focusout);
77
+ this.editor.addEventListener('u2-rte-change', this.#change);
78
+ this.textarea.addEventListener('focus', this.#focus);
79
+ this.textarea.addEventListener('invalid', this.#focus);
80
+ this.#form = this.textarea.form;
81
+ this.#form?.addEventListener('reset', this.#reset);
82
+ this.#tabindex = this.textarea.getAttribute('tabindex');
83
+ this.textarea.tabIndex = -1;
84
+ this.#observer = new MutationObserver(this.#mutate);
85
+ this.#observer.observe(this.editor, {subtree: true, childList: true, characterData: true, attributes: true});
86
+ this.#observer.observe(this.textarea, {attributes: true});
87
+ this.#state();
88
+ this.dataset.ready = '';
89
+ } catch (error) {
90
+ this.#dispose();
91
+ console.error('u2-rte:', error);
38
92
  }
39
- let sourceValue = this.textarea ? this.textarea.value : this.innerHTML;
40
- this.editor.innerHTML = sourceValue;
41
-
42
- this._internals = this.attachInternals();
43
- this._internals.role = 'textbox';
44
93
  }
45
- }
46
94
 
47
- customElements.get('u2-rte') || customElements.define('u2-rte', U2Rte);
95
+ #write(value) {
96
+ this.#writing = true;
97
+ try {
98
+ this.#source.write(this.#codec.parse(value));
99
+ } finally {
100
+ this.#writing = false;
101
+ }
102
+ this.#sync();
103
+ }
104
+
105
+ #sync() {
106
+ const html = this.#source.read().html;
107
+ this.textarea.value = this.#codec.serialize(html);
108
+ }
109
+
110
+ #emitInput(event) {
111
+ this.#sync();
112
+ this.#dirty = true;
113
+ this.textarea.dispatchEvent(new InputEvent('input', {
114
+ bubbles: true,
115
+ composed: true,
116
+ data: event?.data ?? null,
117
+ inputType: event?.inputType || '',
118
+ }));
119
+ }
120
+
121
+ #input = event => {
122
+ event.stopPropagation();
123
+ this.#emitInput(event);
124
+ };
125
+
126
+ #change = event => {
127
+ if (this.#writing) return;
128
+ this.#sync();
129
+ if (event.detail.transaction.options.trigger !== 'input') this.#emitInput();
130
+ };
131
+
132
+ #focusout = () => queueMicrotask(() => {
133
+ if (!this.#dirty || this.editor.matches(':focus-within')) return;
134
+ this.#dirty = false;
135
+ this.textarea.dispatchEvent(new Event('change', {bubbles: true}));
136
+ });
137
+
138
+ #focus = event => {
139
+ event.preventDefault();
140
+ this.editor.focus();
141
+ };
48
142
 
143
+ #reset = () => queueMicrotask(() => this.#write(this.textarea.value));
49
144
 
50
- // import Showdown from 'https://cdn.skypack.dev/showdown';
51
- // const converter = new Showdown.Converter();
52
- // converter.setOption('tables', true);
53
- // this.editor.innerHTML = converter.makeHtml(sourceValue);
145
+ #mutate = records => {
146
+ if (records.some(record => record.target === this.textarea)) this.#state();
147
+ if (records.some(record => record.target !== this.textarea)) this.#sync();
148
+ };
54
149
 
55
- // import TurndownService from 'https://cdn.skypack.dev/turndown@7.1.1';
56
- // value = new TurndownService().turndown(html);
150
+ #state() {
151
+ const inactive = this.textarea.disabled || this.textarea.readOnly;
152
+ this.editor.contentEditable = inactive ? 'false' : 'true';
153
+ this.editor.ariaDisabled = this.textarea.disabled ? 'true' : null;
154
+ this.editor.ariaReadOnly = this.textarea.readOnly ? 'true' : null;
155
+ this.editor.dataset.placeholder = this.textarea.placeholder;
156
+ for (const name of ['dir', 'lang', 'spellcheck']) {
157
+ const value = this.textarea.getAttribute(name);
158
+ if (value === null) this.editor.removeAttribute(name);
159
+ else this.editor.setAttribute(name, value);
160
+ }
161
+ }
162
+
163
+ #dispose() {
164
+ this.#observer?.disconnect();
165
+ this.#form?.removeEventListener('reset', this.#reset);
166
+ this.textarea?.removeEventListener('focus', this.#focus);
167
+ this.textarea?.removeEventListener('invalid', this.#focus);
168
+ this.editor?.removeEventListener('input', this.#input);
169
+ this.editor?.removeEventListener('focusout', this.#focusout);
170
+ this.editor?.removeEventListener('u2-rte-change', this.#change);
171
+ this.surface?.dispose();
172
+ this.editor?.remove();
173
+ if (this.#tabindex === null) this.textarea?.removeAttribute('tabindex');
174
+ else if (this.#tabindex !== undefined) this.textarea?.setAttribute('tabindex', this.#tabindex);
175
+ this.removeAttribute('data-ready');
176
+ this.#observer = null;
177
+ this.#form = null;
178
+ this.#source = null;
179
+ this.#tabindex = undefined;
180
+ this.#dirty = false;
181
+ this.surface = null;
182
+ this.editor = null;
183
+ }
184
+ }
185
+
186
+ customElements.get('u2-rte') || customElements.define('u2-rte', U2Rte);
@@ -0,0 +1,37 @@
1
+ # &lt;u2-rte0&gt; - element
2
+ "Rich" Rich Text Editor (alpha)
3
+
4
+ ## Usage
5
+
6
+ ```html
7
+ <u2-rte0 style="--u2-rte-toolbar:Bold Italic">
8
+ <strong>RTE</strong>
9
+ <br>
10
+ <i>(Rich Text Editor)</i>
11
+ </u2-rte0>
12
+ ```
13
+
14
+ ```css
15
+ u2-rte0 {
16
+ --u2-rte:true;
17
+ --u2-rte-toolbar:Bold Italic;
18
+ }
19
+ ```
20
+
21
+ ## Install
22
+
23
+ ```html
24
+ <link href="https://cdn.jsdelivr.net/gh/u2ui/u2@main/el/rte0/rte0.css" rel=stylesheet>
25
+ <script src="https://cdn.jsdelivr.net/gh/u2ui/u2@main/el/rte0/rte0.js" type=module async></script>
26
+ ```
27
+
28
+ ## Demos
29
+
30
+ [test.html](http://gcdn.li/u2ui/u2@main/el/rte0/tests/test.html)
31
+ [minimal.html](http://gcdn.li/u2ui/u2@main/el/rte0/tests/minimal.html)
32
+
33
+ ## About
34
+
35
+ - MIT License, Copyright (c) 2022 <u2> (like all repositories in this organization) <br>
36
+ - Suggestions, ideas, finding bugs and making pull requests make us very happy. ♥
37
+
@@ -0,0 +1,33 @@
1
+ {
2
+ "schemaVersion": "1.0.0",
3
+ "readme": "README.md",
4
+ "modules": [{
5
+ "kind": "javascript-module",
6
+ "path": "rte0.js",
7
+ "declarations": [{
8
+ "kind": "class",
9
+ "description": "\"Rich\" Rich Text Editor (alpha)",
10
+ "name": "U2Rte0",
11
+ "members": [],
12
+ "events": [],
13
+ "attributes": [],
14
+ "cssProperties": [{
15
+ "name": "--u2-rte-toolbar",
16
+ "description": "Space separated list of toolbar commands",
17
+ "syntax": "*"
18
+ },{
19
+ "name": "--u2-rte",
20
+ "description": "Marks regions as rich-text editable",
21
+ "syntax": "*"
22
+ }],
23
+ "superclass": { "name": "HTMLElement" },
24
+ "tagName": "u2-rte0",
25
+ "customElement": true
26
+ }],
27
+ "exports": [{
28
+ "kind": "custom-element-definition",
29
+ "name": "u2-rte0",
30
+ "declaration": { "name": "U2Rte0", "module": "rte0.js" }
31
+ }]
32
+ }]
33
+ }
@@ -0,0 +1,9 @@
1
+ u2-rte0 {
2
+ display:block;
3
+ border:1px solid var(--color-line, color-mix(in srgb, currentColor, transparent 75%));
4
+ }
5
+
6
+ u2-rte0 > textarea {
7
+ display:block;
8
+ width:100%;
9
+ }
@@ -0,0 +1,56 @@
1
+
2
+ import '../../js/rte0/rte.js';
3
+
4
+ export default class U2Rte0 extends HTMLElement {
5
+ constructor() {
6
+ super();
7
+ let shadowRoot = this.attachShadow({mode:'open'});
8
+ shadowRoot.innerHTML =
9
+ `<style>
10
+ :host {
11
+ display:block;
12
+ --u2-rte:true;
13
+ }
14
+ #editor {
15
+ --u2-rte:true;
16
+ padding:1px; /* to avoid margin collapse */
17
+ }
18
+ </style>
19
+ <div id=editor contenteditable></div>
20
+ `;
21
+ //this.setAttribute('contenteditable', true);
22
+ this.editor = this.shadowRoot.getElementById('editor');
23
+
24
+ this.editor.addEventListener('input', (event)=>{
25
+ const html = this.editor.innerHTML;
26
+ const value = html;
27
+ if (this.textarea) {
28
+ this.textarea.value = value;
29
+ } else {
30
+ this.innerHTML = value;
31
+ }
32
+ });
33
+
34
+ }
35
+ connectedCallback() {
36
+ if (this.children.length === 1 && this.children[0].tagName === 'TEXTAREA') {
37
+ this.textarea = this.children[0];
38
+ }
39
+ let sourceValue = this.textarea ? this.textarea.value : this.innerHTML;
40
+ this.editor.innerHTML = sourceValue;
41
+
42
+ this._internals = this.attachInternals();
43
+ this._internals.role = 'textbox';
44
+ }
45
+ }
46
+
47
+ customElements.get('u2-rte0') || customElements.define('u2-rte0', U2Rte0);
48
+
49
+
50
+ // import Showdown from 'https://cdn.skypack.dev/showdown';
51
+ // const converter = new Showdown.Converter();
52
+ // converter.setOption('tables', true);
53
+ // this.editor.innerHTML = converter.makeHtml(sourceValue);
54
+
55
+ // import TurndownService from 'https://cdn.skypack.dev/turndown@7.1.1';
56
+ // value = new TurndownService().turndown(html);
@@ -597,6 +597,7 @@ function parseCssLength(str) {
597
597
  }
598
598
 
599
599
  function unitToPx(value, unit, el){
600
+ if (!unit) return value;
600
601
  if (unit === 'px') return value;
601
602
  if (unit === 'cm') return value * 96 / 2.54;
602
603
  if (unit === 'mm') return value * 96 / 25.4;
@@ -627,6 +628,7 @@ function unitToPx(value, unit, el){
627
628
  }
628
629
 
629
630
  function pxToUnit(px, unit, el){
631
+ if (!unit) return px;
630
632
  if (unit === 'px') return px;
631
633
  if (unit === 'cm') return px * 2.54 / 96;
632
634
  if (unit === 'mm') return px * 25.4 / 96;
@@ -3,7 +3,7 @@ u2-tooltip {
3
3
  /* relevant */
4
4
  opacity: 0;
5
5
  visibility:hidden;
6
- position: absolute;
6
+ position: absolute; /* keep its document position while scrolling */
7
7
  left: -9999px;
8
8
  top: -9999px;
9
9
  z-index: 9999;
@@ -97,4 +97,4 @@ u2-tooltip {
97
97
  top: var(--halfMinus);
98
98
  }
99
99
  }
100
- /* */
100
+ /* */
@@ -52,7 +52,8 @@ export class Placer {
52
52
 
53
53
  let position = getComputedStyle(this.el).getPropertyValue('position');
54
54
  if (position !== 'fixed') {
55
- let root = offsetParent(this.el);
55
+ // Popovers use the top-layer containing block regardless of their DOM ancestry.
56
+ let root = this.el.popover ? null : offsetParent(this.el);
56
57
  if (root) {
57
58
  // if (!root) root = document.documentElement; // make no sense
58
59
  viewport = root.getBoundingClientRect();