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
@@ -0,0 +1,285 @@
1
+ import {elementAttributes, selectedElement} from '../command/element.js';
2
+ import {inlineUi} from '../config/config.js';
3
+ import {caretAfter} from '../ui/caret.js';
4
+ import {Handles} from '../ui/handles.js';
5
+ import {place} from '../ui/place.js';
6
+
7
+ const STYLE = `
8
+ #image {
9
+ align-items: center;
10
+ display: flex;
11
+ gap: .57em;
12
+ padding: .34em;
13
+
14
+ input {
15
+ background: transparent;
16
+ border: 1px solid color-mix(in srgb, CanvasText 24%, transparent);
17
+ border-radius: .34em;
18
+ min-inline-size: 16em;
19
+ padding: .17em .34em;
20
+ }
21
+ }
22
+ `;
23
+
24
+
25
+ // Only the trailing edges: an image sits in a text flow with its top and its
26
+ // start edge held in place, so dragging the other side is the only direction
27
+ // that grows it where the eye expects. The corner keeps the proportion; the two
28
+ // edges change one measurement alone.
29
+ const HANDLES = Object.freeze([
30
+ Object.freeze({name: 'se', label: 'Resize', x: 1, y: 1, axis: 'both', cursor: 'nwse-resize'}),
31
+ Object.freeze({name: 'e', label: 'Width', x: 1, y: 0.5, axis: 'x', cursor: 'ew-resize'}),
32
+ Object.freeze({name: 's', label: 'Height', x: 0.5, y: 1, axis: 'y', cursor: 'ns-resize'}),
33
+ ]);
34
+
35
+ // Optional image module. A selected image gets a frame with corner handles; the
36
+ // drag resizes the frame only and the size is written once when it is released,
37
+ // so a resize is one undo step rather than a trail of them.
38
+ //
39
+ // It also gets its alt text, in a field below it: what an image says is part of
40
+ // the image, and a form nobody opens is a form everybody fills in.
41
+ //
42
+ // Size is expressed as `width` and `height` attributes because that is what the
43
+ // sanitize policy allows on an image. What is resizable is a selector, so an
44
+ // application can make any atomic element sizeable.
45
+ export function imageTools({selector = 'img', minimum = 16} = {}) {
46
+ const match = element => element.matches(selector);
47
+ const editors = new WeakMap();
48
+ return Object.freeze({
49
+ name: 'images',
50
+ setup({editor, root, chrome}) {
51
+ const state = {
52
+ root,
53
+ chrome,
54
+ document: root.nodeType === Node.DOCUMENT_NODE ? root : root.ownerDocument,
55
+ views: new WeakMap(),
56
+ active: null,
57
+ drag: null,
58
+ minimum,
59
+ handles: null,
60
+ alt: null,
61
+ writing: false,
62
+ controller: null,
63
+ pending: 0,
64
+ };
65
+ editors.set(editor, state);
66
+ let connected = true;
67
+ return {dispose() {
68
+ if (!connected) return;
69
+ release(state);
70
+ editors.delete(editor);
71
+ connected = false;
72
+ }};
73
+ },
74
+ commands({editor, surface, commands}) {
75
+ const state = editors.get(editor);
76
+ if (!state) throw new DOMException('The image extension is not set up', 'InvalidStateError');
77
+ state.views.set(surface, {surface, commands});
78
+ const size = elementAttributes(['width', 'height'], {match});
79
+ return {
80
+ imageSize: size,
81
+ imageAlt: elementAttributes(['alt'], {match}),
82
+ // No value clears both attributes, so the image is its own size.
83
+ imageOriginal: {
84
+ enabled: edit => {
85
+ const element = selectedElement(edit, match);
86
+ return !!element && (element.hasAttribute('width') || element.hasAttribute('height'));
87
+ },
88
+ run: edit => size.run(edit),
89
+ },
90
+ };
91
+ },
92
+ attach({editor, surface}) {
93
+ const state = editors.get(editor);
94
+ const view = state?.views.get(surface);
95
+ if (!view) throw new DOMException('The image surface is not set up', 'InvalidStateError');
96
+ const controller = new state.document.defaultView.AbortController();
97
+ const listen = {signal: controller.signal};
98
+ const sync = () => track(state, view, match);
99
+ surface.addEventListener('u2-rte-selectionchange', sync, listen);
100
+ surface.addEventListener('u2-rte-change', sync, listen);
101
+ // What a session's end takes away, its beginning brings back: coming
102
+ // back to the same selection is no selection change to hear about.
103
+ surface.addEventListener('u2-rte-activate', sync, listen);
104
+ surface.addEventListener('u2-rte-deactivate', () => close(state, surface), listen);
105
+ return {dispose() {
106
+ controller.abort();
107
+ close(state, surface);
108
+ state.views.delete(surface);
109
+ }};
110
+ },
111
+ toolbar: Object.freeze([Object.freeze({
112
+ command: 'imageOriginal', name: 'imageOriginal', label: 'Original size', text: '⤢',
113
+ })]),
114
+ });
115
+ }
116
+
117
+ export const images = imageTools();
118
+
119
+ function track(state, view, match) {
120
+ // Not while the field is naming the image: what it says is being typed, and
121
+ // the attribute is only what has been typed so far.
122
+ if (state.writing) return;
123
+ if (!inlineUi(view.surface.config, 'image')) return close(state, view.surface);
124
+ const element = current(view, match);
125
+ if (!element) return close(state, view.surface);
126
+ build(state);
127
+ state.active = {view, element};
128
+ state.handles.show();
129
+ state.alt.hidden = false;
130
+ state.alt.firstElementChild.value = element.getAttribute('alt') || '';
131
+ schedule(state);
132
+ }
133
+
134
+ function current(view, match) {
135
+ const range = view.surface.selection?.range();
136
+ if (!range || range.collapsed) return null;
137
+ const contents = range.cloneContents();
138
+ if (contents.childNodes.length !== 1) return null;
139
+ const element = range.startContainer.childNodes[range.startOffset];
140
+ return element?.nodeType === Node.ELEMENT_NODE && match(element)
141
+ && view.surface.element.contains(element) ? element : null;
142
+ }
143
+
144
+ function close(state, surface) {
145
+ if (surface && state.active && state.active.view.surface !== surface) return;
146
+ state.active = null;
147
+ state.drag = null;
148
+ state.handles?.show(false);
149
+ if (state.alt) state.alt.hidden = true;
150
+ }
151
+
152
+ // Naming the image moves the selection back onto it, and an engine follows that
153
+ // with focus — so the field takes it back, caret and all, exactly as the link
154
+ // form has to.
155
+ function name(state, field) {
156
+ const active = state.active;
157
+ if (!active) return null;
158
+ const caret = [field.selectionStart, field.selectionEnd];
159
+ state.writing = true;
160
+ try {
161
+ active.view.commands.run('imageAlt', {value: {alt: field.value.trim()}, trigger: 'input'});
162
+ } finally {
163
+ state.writing = false;
164
+ }
165
+ field.focus();
166
+ field.setSelectionRange(...caret);
167
+ return active.element;
168
+ }
169
+
170
+ function leave(state) {
171
+ const active = state.active;
172
+ if (!active) return;
173
+ const {element} = active;
174
+ const surface = active.view.surface;
175
+ close(state);
176
+ if (surface.connected) caretAfter(surface, element);
177
+ }
178
+
179
+ function schedule(state) {
180
+ if (state.pending || !state.handles) return;
181
+ state.pending = state.document.defaultView.requestAnimationFrame(() => {
182
+ state.pending = 0;
183
+ position(state);
184
+ });
185
+ }
186
+
187
+ function position(state, box = null) {
188
+ const element = state.active?.element;
189
+ if (!element?.isConnected) return close(state);
190
+ const rect = box || element.getBoundingClientRect();
191
+ state.handles.frame(rect);
192
+ for (const handle of HANDLES) {
193
+ state.handles.place(handle.name, rect.left + rect.width * handle.x, rect.top + rect.height * handle.y);
194
+ }
195
+ // Below the image, clear of the handles sitting on its lower edge.
196
+ place(state.alt, state.active.view.surface, {align: 'start', prefer: 'below', gap: 16, on: rect});
197
+ }
198
+
199
+ function build(state) {
200
+ if (state.handles) return state.handles;
201
+ const form = state.chrome.part('image', STYLE, 'form');
202
+ form.className = 'panel';
203
+ form.hidden = true;
204
+ form.setAttribute('aria-label', 'Image');
205
+ const field = state.document.createElement('input');
206
+ field.type = 'text';
207
+ field.name = 'alt';
208
+ field.placeholder = 'Alt text';
209
+ field.setAttribute('aria-label', 'Alt text');
210
+ form.append(field);
211
+ // One field, so Enter submits implicitly — and with the name already
212
+ // written, there is nothing left to confirm: it just leaves.
213
+ form.addEventListener('submit', event => {
214
+ event.preventDefault();
215
+ leave(state);
216
+ });
217
+ form.addEventListener('input', () => name(state, field));
218
+ state.alt = form;
219
+ state.handles = new Handles(state.chrome.root, {
220
+ name: 'images',
221
+ handles: HANDLES,
222
+ part: 'corner',
223
+ press: name => start(state, name),
224
+ });
225
+ const controller = new state.document.defaultView.AbortController();
226
+ const listen = {signal: controller.signal};
227
+ const view = state.document.defaultView;
228
+ view.addEventListener('pointermove', event => drag(state, event), listen);
229
+ view.addEventListener('pointerup', event => drop(state, event), listen);
230
+ view.addEventListener('resize', () => state.active && schedule(state), listen);
231
+ state.root.addEventListener('scroll', () => state.active && schedule(state),
232
+ {capture: true, passive: true, signal: controller.signal});
233
+ state.controller = controller;
234
+ return state.handles;
235
+ }
236
+
237
+ function start(state, name) {
238
+ const handle = HANDLES.find(item => item.name === name);
239
+ if (!handle || !state.active) return;
240
+ const rect = state.active.element.getBoundingClientRect();
241
+ // The top and start edges stay where the flow put them.
242
+ state.drag = {rect, axis: handle.axis, ratio: rect.height / rect.width || 1};
243
+ }
244
+
245
+ function drag(state, event) {
246
+ if (!state.drag) return;
247
+ position(state, measure(state.drag, state.minimum, event));
248
+ }
249
+
250
+ function drop(state, event) {
251
+ const drag = state.drag;
252
+ state.drag = null;
253
+ if (!drag || !state.active) return;
254
+ const size = measure(drag, state.minimum, event);
255
+ const {view, element} = state.active;
256
+ if (!view.surface.connected) return;
257
+ if (!view.surface.capture()) view.surface.restore();
258
+ view.commands.run('imageSize', {
259
+ value: {width: Math.round(size.width), height: Math.round(size.height)},
260
+ });
261
+ if (element.isConnected) schedule(state);
262
+ }
263
+
264
+ // The corner keeps the picture's proportion; an edge changes one measurement
265
+ // alone, which is a different intention and says so by where it sits.
266
+ function measure({rect, axis, ratio}, minimum, event) {
267
+ const dragged = {
268
+ width: Math.max(minimum, event.clientX - rect.left),
269
+ height: Math.max(minimum, event.clientY - rect.top),
270
+ };
271
+ const width = axis === 'y' ? rect.width : dragged.width;
272
+ const height = axis === 'x' ? rect.height : axis === 'y' ? dragged.height : width * ratio;
273
+ return {width, height, left: rect.left, top: rect.top};
274
+ }
275
+
276
+ function release(state) {
277
+ state.controller?.abort();
278
+ if (state.pending) state.document.defaultView.cancelAnimationFrame(state.pending);
279
+ state.handles?.dispose();
280
+ state.active = null;
281
+ state.drag = null;
282
+ state.pending = 0;
283
+ state.handles = null;
284
+ state.controller = null;
285
+ }