u2ui 1.5.2 → 1.5.4

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