@lynx-js/web-elements 0.12.9 → 0.12.11
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/CHANGELOG.md +21 -0
- package/README.md +14 -0
- package/dist/elements/ScrollView/ScrollViewMouseDrag.d.ts +1 -0
- package/dist/elements/ScrollView/ScrollViewMouseDrag.js +315 -0
- package/dist/elements/XImage/ImageEvents.d.ts +1 -0
- package/dist/elements/XImage/ImageEvents.js +16 -2
- package/dist/elements/XText/InlineImage.js +2 -1
- package/dist/elements/XText/InlineText.d.ts +1 -1
- package/dist/elements/XText/InlineText.js +3 -2
- package/dist/elements/XText/XText.js +7 -1
- package/dist/elements/XText/XTextSelectionEvents.d.ts +9 -0
- package/dist/elements/XText/XTextSelectionEvents.js +134 -0
- package/dist/elements/XText/index.d.ts +1 -0
- package/dist/elements/XText/index.js +1 -0
- package/package.json +10 -5
- package/src/elements/XText/x-text.css +14 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @lynx-js/web-elements
|
|
2
2
|
|
|
3
|
+
## 0.12.11
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Defer `x-image` and `inline-image` load events received while detached until the host connects to the document, preserving the image dimensions at load time. ([#3906](https://github.com/lynx-family/lynx-stack/pull/3906))
|
|
8
|
+
|
|
9
|
+
- Apply inline image layout styles to `x-svg` inside text and custom truncation content. ([#3899](https://github.com/lynx-family/lynx-stack/pull/3899))
|
|
10
|
+
|
|
11
|
+
## 0.12.10
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Update markdown-it to ^15.0.1. ([#3749](https://github.com/lynx-family/lynx-stack/pull/3749))
|
|
16
|
+
|
|
17
|
+
- Add an optional `scroll-view` mouse-drag plugin for touchscreen-like scrolling ([#3594](https://github.com/lynx-family/lynx-stack/pull/3594))
|
|
18
|
+
on desktop browsers. Enable it by importing
|
|
19
|
+
`@lynx-js/web-core/plugins/scroll-view-mouse-drag` or
|
|
20
|
+
`@lynx-js/web-elements/plugins/scroll-view-mouse-drag` before registering the
|
|
21
|
+
web elements.
|
|
22
|
+
- Support the `bindselectionchange` event on `text` and `inline-text` in Lynx for Web. ([#3741](https://github.com/lynx-family/lynx-stack/pull/3741))
|
|
23
|
+
|
|
3
24
|
## 0.12.9
|
|
4
25
|
|
|
5
26
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -37,6 +37,20 @@ document.body.innerHTML = `
|
|
|
37
37
|
`;
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
+
### Mouse-drag scrolling
|
|
41
|
+
|
|
42
|
+
To make `<scroll-view>` respond to mouse dragging like a touchscreen, load
|
|
43
|
+
the optional plugin before registering the elements:
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
import '@lynx-js/web-elements/plugins/scroll-view-mouse-drag';
|
|
47
|
+
import '@lynx-js/web-elements/all';
|
|
48
|
+
import '@lynx-js/web-elements/index.css';
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The plugin only changes `<scroll-view>` and leaves native touch scrolling
|
|
52
|
+
unchanged.
|
|
53
|
+
|
|
40
54
|
## Document
|
|
41
55
|
|
|
42
56
|
See our website for more information.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
// Copyright 2026 The Lynx Authors. All rights reserved.
|
|
2
|
+
// Licensed under the Apache License Version 2.0 that can be found in the
|
|
3
|
+
// LICENSE file in the root directory of this source tree.
|
|
4
|
+
import { boostedQueueMicrotask } from '../../element-reactive/index.js';
|
|
5
|
+
const dragStartThreshold = 5;
|
|
6
|
+
const userScrollableOverflowValues = new Set(['auto', 'overlay', 'scroll']);
|
|
7
|
+
const blockingOverscrollBehaviorValues = new Set(['contain', 'none']);
|
|
8
|
+
function getScrollableAxes(dom, style) {
|
|
9
|
+
if (dom.getAttribute('enable-scroll') === 'false') {
|
|
10
|
+
return { x: false, y: false };
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
x: userScrollableOverflowValues.has(style.overflowX)
|
|
14
|
+
&& dom.scrollWidth - dom.clientWidth > 1,
|
|
15
|
+
y: userScrollableOverflowValues.has(style.overflowY)
|
|
16
|
+
&& dom.scrollHeight - dom.clientHeight > 1,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function findScrollChain(event) {
|
|
20
|
+
let owner;
|
|
21
|
+
let ownerAxes;
|
|
22
|
+
const x = [];
|
|
23
|
+
const y = [];
|
|
24
|
+
for (const target of event.composedPath()) {
|
|
25
|
+
if (!(target instanceof HTMLElement) || target.localName !== 'scroll-view') {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const style = getComputedStyle(target);
|
|
29
|
+
const axes = getScrollableAxes(target, style);
|
|
30
|
+
if (!axes.x && !axes.y) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (!owner) {
|
|
34
|
+
owner = target;
|
|
35
|
+
ownerAxes = axes;
|
|
36
|
+
}
|
|
37
|
+
if (axes.x) {
|
|
38
|
+
x.push({
|
|
39
|
+
allowsChaining: !blockingOverscrollBehaviorValues.has(style.overscrollBehaviorX),
|
|
40
|
+
dom: target,
|
|
41
|
+
start: target.scrollLeft,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
if (axes.y) {
|
|
45
|
+
y.push({
|
|
46
|
+
allowsChaining: !blockingOverscrollBehaviorValues.has(style.overscrollBehaviorY),
|
|
47
|
+
dom: target,
|
|
48
|
+
start: target.scrollTop,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return owner && ownerAxes ? { owner, ownerAxes, x, y } : undefined;
|
|
53
|
+
}
|
|
54
|
+
function selectDragAxes(chain, deltaX, deltaY) {
|
|
55
|
+
const absoluteX = Math.abs(deltaX);
|
|
56
|
+
const absoluteY = Math.abs(deltaY);
|
|
57
|
+
if (Math.hypot(deltaX, deltaY) <= dragStartThreshold) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
const hasX = chain.x.length > 0;
|
|
61
|
+
const hasY = chain.y.length > 0;
|
|
62
|
+
if (chain.ownerAxes.x && chain.ownerAxes.y) {
|
|
63
|
+
return { x: hasX, y: hasY };
|
|
64
|
+
}
|
|
65
|
+
if (hasX && hasY) {
|
|
66
|
+
if (absoluteX === absoluteY) {
|
|
67
|
+
return chain.ownerAxes.x
|
|
68
|
+
? { x: true, y: false }
|
|
69
|
+
: { x: false, y: true };
|
|
70
|
+
}
|
|
71
|
+
return absoluteX > absoluteY
|
|
72
|
+
? { x: true, y: false }
|
|
73
|
+
: { x: false, y: true };
|
|
74
|
+
}
|
|
75
|
+
if (hasX && absoluteX >= absoluteY) {
|
|
76
|
+
return { x: true, y: false };
|
|
77
|
+
}
|
|
78
|
+
if (hasY && absoluteY >= absoluteX) {
|
|
79
|
+
return { x: false, y: true };
|
|
80
|
+
}
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
function applyScrollDelta(candidates, delta, axis) {
|
|
84
|
+
let remaining = delta;
|
|
85
|
+
for (const candidate of candidates) {
|
|
86
|
+
const next = candidate.start + remaining;
|
|
87
|
+
if (axis === 'x') {
|
|
88
|
+
candidate.dom.scrollTo({
|
|
89
|
+
behavior: 'instant',
|
|
90
|
+
left: next,
|
|
91
|
+
top: candidate.dom.scrollTop,
|
|
92
|
+
});
|
|
93
|
+
remaining -= candidate.dom.scrollLeft - candidate.start;
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
candidate.dom.scrollTo({
|
|
97
|
+
behavior: 'instant',
|
|
98
|
+
left: candidate.dom.scrollLeft,
|
|
99
|
+
top: next,
|
|
100
|
+
});
|
|
101
|
+
remaining -= candidate.dom.scrollTop - candidate.start;
|
|
102
|
+
}
|
|
103
|
+
if (!candidate.allowsChaining) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Makes a scroll-view follow mouse dragging in the same direction as touch
|
|
110
|
+
* scrolling. Pointer events from touchscreens are left to the browser.
|
|
111
|
+
*/
|
|
112
|
+
class ScrollViewMouseDrag {
|
|
113
|
+
static observedAttributes = [];
|
|
114
|
+
#dom;
|
|
115
|
+
#connected = false;
|
|
116
|
+
#dragState;
|
|
117
|
+
#clickSuppression;
|
|
118
|
+
constructor(dom) {
|
|
119
|
+
this.#dom = dom;
|
|
120
|
+
}
|
|
121
|
+
#handlePointerDown = (event) => {
|
|
122
|
+
if (event.pointerType !== 'mouse'
|
|
123
|
+
|| !event.isPrimary
|
|
124
|
+
|| event.button !== 0
|
|
125
|
+
|| this.#dragState) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const chain = findScrollChain(event);
|
|
129
|
+
if (!chain || chain.owner !== this.#dom) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const document = this.#dom.ownerDocument;
|
|
133
|
+
this.#dragState = {
|
|
134
|
+
chain,
|
|
135
|
+
document,
|
|
136
|
+
dragging: false,
|
|
137
|
+
pendingDeltaX: 0,
|
|
138
|
+
pendingDeltaY: 0,
|
|
139
|
+
pointerId: event.pointerId,
|
|
140
|
+
scrollUpdateQueued: false,
|
|
141
|
+
startClientX: event.clientX,
|
|
142
|
+
startClientY: event.clientY,
|
|
143
|
+
};
|
|
144
|
+
this.#trackPotentialClick(document);
|
|
145
|
+
document.addEventListener('pointermove', this.#handlePointerMove, {
|
|
146
|
+
capture: true,
|
|
147
|
+
passive: false,
|
|
148
|
+
});
|
|
149
|
+
document.addEventListener('pointerup', this.#handlePointerUp, true);
|
|
150
|
+
document.addEventListener('pointercancel', this.#handlePointerCancel, true);
|
|
151
|
+
document.defaultView?.addEventListener('blur', this.#handlePointerCancel);
|
|
152
|
+
};
|
|
153
|
+
#handlePointerMove = (event) => {
|
|
154
|
+
const state = this.#dragState;
|
|
155
|
+
if (!state || event.pointerId !== state.pointerId) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if ((event.buttons & 1) === 0) {
|
|
159
|
+
this.#stopDragging(false);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const deltaX = event.clientX - state.startClientX;
|
|
163
|
+
const deltaY = event.clientY - state.startClientY;
|
|
164
|
+
if (!state.dragging) {
|
|
165
|
+
const activeAxes = selectDragAxes(state.chain, deltaX, deltaY);
|
|
166
|
+
if (!activeAxes) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
state.activeAxes = activeAxes;
|
|
170
|
+
state.dragging = true;
|
|
171
|
+
state.previousUserSelect = this.#dom.style.userSelect;
|
|
172
|
+
this.#dom.style.userSelect = 'none';
|
|
173
|
+
state.document.getSelection()?.removeAllRanges();
|
|
174
|
+
this.#dom.setPointerCapture(event.pointerId);
|
|
175
|
+
}
|
|
176
|
+
if (event.cancelable) {
|
|
177
|
+
event.preventDefault();
|
|
178
|
+
}
|
|
179
|
+
state.pendingDeltaX = -deltaX;
|
|
180
|
+
state.pendingDeltaY = -deltaY;
|
|
181
|
+
if (!state.scrollUpdateQueued) {
|
|
182
|
+
state.scrollUpdateQueued = true;
|
|
183
|
+
boostedQueueMicrotask(() => this.#flushScrollUpdate(state));
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
#flushScrollUpdate(state) {
|
|
187
|
+
if (!state.scrollUpdateQueued) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
state.scrollUpdateQueued = false;
|
|
191
|
+
if (state.activeAxes?.x) {
|
|
192
|
+
applyScrollDelta(state.chain.x, state.pendingDeltaX, 'x');
|
|
193
|
+
}
|
|
194
|
+
if (state.activeAxes?.y) {
|
|
195
|
+
applyScrollDelta(state.chain.y, state.pendingDeltaY, 'y');
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
#handlePointerUp = (event) => {
|
|
199
|
+
if (event.pointerId !== this.#dragState?.pointerId) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
this.#stopDragging(this.#dragState.dragging);
|
|
203
|
+
};
|
|
204
|
+
#handlePointerCancel = (event) => {
|
|
205
|
+
if (event instanceof PointerEvent
|
|
206
|
+
&& event.pointerId !== this.#dragState?.pointerId) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
this.#stopDragging(false);
|
|
210
|
+
};
|
|
211
|
+
#handleDragStart = (event) => {
|
|
212
|
+
if (this.#dragState) {
|
|
213
|
+
event.preventDefault();
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
#handleClick = (event) => {
|
|
217
|
+
if (!this.#clickSuppression?.suppress
|
|
218
|
+
|| !event.composedPath().includes(this.#dom)) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
this.#clearClickSuppression();
|
|
222
|
+
event.preventDefault();
|
|
223
|
+
event.stopImmediatePropagation();
|
|
224
|
+
};
|
|
225
|
+
#stopDragging(suppressClick) {
|
|
226
|
+
const state = this.#dragState;
|
|
227
|
+
if (!state) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
const { document } = state;
|
|
231
|
+
this.#flushScrollUpdate(state);
|
|
232
|
+
document.removeEventListener('pointermove', this.#handlePointerMove, true);
|
|
233
|
+
document.removeEventListener('pointerup', this.#handlePointerUp, true);
|
|
234
|
+
document.removeEventListener('pointercancel', this.#handlePointerCancel, true);
|
|
235
|
+
document.defaultView?.removeEventListener('blur', this.#handlePointerCancel);
|
|
236
|
+
if (this.#dom.hasPointerCapture(state.pointerId)) {
|
|
237
|
+
this.#dom.releasePointerCapture(state.pointerId);
|
|
238
|
+
}
|
|
239
|
+
if (state.dragging && this.#dom.style.userSelect === 'none') {
|
|
240
|
+
this.#dom.style.userSelect = state.previousUserSelect ?? '';
|
|
241
|
+
}
|
|
242
|
+
this.#dragState = undefined;
|
|
243
|
+
if (suppressClick) {
|
|
244
|
+
const clickSuppression = this.#clickSuppression;
|
|
245
|
+
if (clickSuppression) {
|
|
246
|
+
clickSuppression.suppress = true;
|
|
247
|
+
clearTimeout(clickSuppression.timer);
|
|
248
|
+
clickSuppression.timer = setTimeout(this.#clearClickSuppression, 0);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
this.#clearClickSuppression();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
#trackPotentialClick(document) {
|
|
256
|
+
this.#clearClickSuppression();
|
|
257
|
+
const target = document.defaultView ?? document;
|
|
258
|
+
this.#clickSuppression = { suppress: false, target };
|
|
259
|
+
target.addEventListener('click', this.#handleClick, true);
|
|
260
|
+
}
|
|
261
|
+
#clearClickSuppression = () => {
|
|
262
|
+
const clickSuppression = this.#clickSuppression;
|
|
263
|
+
if (!clickSuppression) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
clickSuppression.target.removeEventListener('click', this.#handleClick, true);
|
|
267
|
+
clearTimeout(clickSuppression.timer);
|
|
268
|
+
this.#clickSuppression = undefined;
|
|
269
|
+
};
|
|
270
|
+
connectedCallback() {
|
|
271
|
+
if (this.#connected) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
this.#connected = true;
|
|
275
|
+
this.#dom.addEventListener('pointerdown', this.#handlePointerDown, true);
|
|
276
|
+
this.#dom.addEventListener('dragstart', this.#handleDragStart, true);
|
|
277
|
+
}
|
|
278
|
+
dispose() {
|
|
279
|
+
this.#connected = false;
|
|
280
|
+
this.#dom.removeEventListener('pointerdown', this.#handlePointerDown, true);
|
|
281
|
+
this.#dom.removeEventListener('dragstart', this.#handleDragStart, true);
|
|
282
|
+
this.#stopDragging(false);
|
|
283
|
+
this.#clearClickSuppression();
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const scrollViewTagName = 'scroll-view';
|
|
287
|
+
const registeredClasses = new WeakSet();
|
|
288
|
+
function registerMouseDragPlugin(elementClass) {
|
|
289
|
+
if (registeredClasses.has(elementClass)) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const componentClass = elementClass;
|
|
293
|
+
if (!componentClass.registerPlugin) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
componentClass.registerPlugin(ScrollViewMouseDrag);
|
|
297
|
+
registeredClasses.add(elementClass);
|
|
298
|
+
}
|
|
299
|
+
if (typeof customElements !== 'undefined') {
|
|
300
|
+
const registeredScrollView = customElements.get(scrollViewTagName);
|
|
301
|
+
if (registeredScrollView) {
|
|
302
|
+
registerMouseDragPlugin(registeredScrollView);
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
const define = Reflect.get(customElements, 'define');
|
|
306
|
+
customElements.define = function (name, elementClass, options) {
|
|
307
|
+
if (name === scrollViewTagName) {
|
|
308
|
+
registerMouseDragPlugin(elementClass);
|
|
309
|
+
}
|
|
310
|
+
Reflect.apply(define, this, [name, elementClass, options]);
|
|
311
|
+
};
|
|
312
|
+
void customElements.whenDefined(scrollViewTagName).then(registerMouseDragPlugin);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
//# sourceMappingURL=ScrollViewMouseDrag.js.map
|
|
@@ -22,6 +22,7 @@ let ImageEvents = (() => {
|
|
|
22
22
|
}
|
|
23
23
|
static observedAttributes = [];
|
|
24
24
|
#dom = __runInitializers(this, _instanceExtraInitializers);
|
|
25
|
+
#pendingLoadEvents = [];
|
|
25
26
|
#getImg = genDomGetter(() => this.#dom.shadowRoot, '#img');
|
|
26
27
|
_enableLoadEvent(status) {
|
|
27
28
|
if (status) {
|
|
@@ -31,6 +32,7 @@ let ImageEvents = (() => {
|
|
|
31
32
|
}
|
|
32
33
|
else {
|
|
33
34
|
this.#getImg().removeEventListener('load', this.#teleportLoadEvent);
|
|
35
|
+
this.#pendingLoadEvents = [];
|
|
34
36
|
}
|
|
35
37
|
}
|
|
36
38
|
_enableErrorEvent(status) {
|
|
@@ -44,14 +46,26 @@ let ImageEvents = (() => {
|
|
|
44
46
|
}
|
|
45
47
|
}
|
|
46
48
|
#teleportLoadEvent = () => {
|
|
47
|
-
|
|
49
|
+
const event = new CustomEvent('load', {
|
|
48
50
|
...commonComponentEventSetting,
|
|
49
51
|
detail: {
|
|
50
52
|
width: this.#getImg().naturalWidth,
|
|
51
53
|
height: this.#getImg().naturalHeight,
|
|
52
54
|
},
|
|
53
|
-
})
|
|
55
|
+
});
|
|
56
|
+
if (this.#dom.isConnected) {
|
|
57
|
+
this.#dom.dispatchEvent(event);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
// Preserve the dimensions at load time until the host joins the document.
|
|
61
|
+
this.#pendingLoadEvents.push(event);
|
|
62
|
+
}
|
|
54
63
|
};
|
|
64
|
+
connectedCallback() {
|
|
65
|
+
while (this.#dom.isConnected && this.#pendingLoadEvents.length) {
|
|
66
|
+
this.#dom.dispatchEvent(this.#pendingLoadEvents.shift());
|
|
67
|
+
}
|
|
68
|
+
}
|
|
55
69
|
#teleportErrorEvent = () => {
|
|
56
70
|
this.#dom.dispatchEvent(new CustomEvent('error', {
|
|
57
71
|
...commonComponentEventSetting,
|
|
@@ -6,6 +6,7 @@ import { __esDecorate, __runInitializers } from "tslib";
|
|
|
6
6
|
*/
|
|
7
7
|
import { Component, genDomGetter, registerAttributeHandler, } from '../../element-reactive/index.js';
|
|
8
8
|
import { templateInlineImage } from '../htmlTemplates.js';
|
|
9
|
+
import { ImageEvents } from '../XImage/ImageEvents.js';
|
|
9
10
|
let InlineImageAttributes = (() => {
|
|
10
11
|
let _instanceExtraInitializers = [];
|
|
11
12
|
let __handleSrc_decorators;
|
|
@@ -35,7 +36,7 @@ export { InlineImageAttributes };
|
|
|
35
36
|
* @deprecated you can use `x-image` instead in `x-text`.
|
|
36
37
|
*/
|
|
37
38
|
let InlineImage = (() => {
|
|
38
|
-
let _classDecorators = [Component('inline-image', [InlineImageAttributes], templateInlineImage({}))];
|
|
39
|
+
let _classDecorators = [Component('inline-image', [InlineImageAttributes, ImageEvents], templateInlineImage({}))];
|
|
39
40
|
let _classDescriptor;
|
|
40
41
|
let _classExtraInitializers = [];
|
|
41
42
|
let _classThis;
|
|
@@ -5,11 +5,12 @@ import { __esDecorate, __runInitializers } from "tslib";
|
|
|
5
5
|
// LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
import { Component } from '../../element-reactive/index.js';
|
|
8
|
+
import { XTextSelectionEvents } from './XTextSelectionEvents.js';
|
|
8
9
|
/**
|
|
9
|
-
* @deprecated
|
|
10
|
+
* @deprecated Use x-text instead of inline-text.
|
|
10
11
|
*/
|
|
11
12
|
let InlineText = (() => {
|
|
12
|
-
let _classDecorators = [Component('inline-text', [])];
|
|
13
|
+
let _classDecorators = [Component('inline-text', [XTextSelectionEvents])];
|
|
13
14
|
let _classDescriptor;
|
|
14
15
|
let _classExtraInitializers = [];
|
|
15
16
|
let _classThis;
|
|
@@ -10,8 +10,14 @@ import { ScrollIntoView } from '../ScrollView/ScrollIntoView.js';
|
|
|
10
10
|
import { RawTextAttributes } from './RawText.js';
|
|
11
11
|
import { CommonEventsAndMethods, layoutChangeTarget, } from '../common/CommonEventsAndMethods.js';
|
|
12
12
|
import { templateXText } from '../htmlTemplates.js';
|
|
13
|
+
import { XTextSelectionEvents } from './XTextSelectionEvents.js';
|
|
13
14
|
let XText = (() => {
|
|
14
|
-
let _classDecorators = [Component('x-text', [
|
|
15
|
+
let _classDecorators = [Component('x-text', [
|
|
16
|
+
CommonEventsAndMethods,
|
|
17
|
+
XTextSelectionEvents,
|
|
18
|
+
XTextTruncation,
|
|
19
|
+
RawTextAttributes,
|
|
20
|
+
], templateXText)];
|
|
15
21
|
let _classDescriptor;
|
|
16
22
|
let _classExtraInitializers = [];
|
|
17
23
|
let _classThis;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type AttributeReactiveClass } from '../../element-reactive/index.js';
|
|
2
|
+
export declare class XTextSelectionEvents implements InstanceType<AttributeReactiveClass<typeof HTMLElement>> {
|
|
3
|
+
#private;
|
|
4
|
+
static observedAttributes: never[];
|
|
5
|
+
constructor(dom: HTMLElement);
|
|
6
|
+
connectedCallback(): void;
|
|
7
|
+
dispose(): void;
|
|
8
|
+
_handleEnableSelectionChangeEvent(status: boolean): void;
|
|
9
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { __esDecorate, __runInitializers } from "tslib";
|
|
2
|
+
/*
|
|
3
|
+
// Copyright 2026 The Lynx Authors. All rights reserved.
|
|
4
|
+
// Licensed under the Apache License Version 2.0 that can be found in the
|
|
5
|
+
// LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import { registerEventEnableStatusChangeHandler, } from '../../element-reactive/index.js';
|
|
8
|
+
import { commonComponentEventSetting } from '../common/commonEventInitConfiguration.js';
|
|
9
|
+
const emptySelection = () => ({
|
|
10
|
+
start: -1,
|
|
11
|
+
end: -1,
|
|
12
|
+
direction: 'forward',
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* Converts a selection boundary to an offset in the target's text. Using the
|
|
16
|
+
* range text length provides one offset space across nested text nodes.
|
|
17
|
+
*/
|
|
18
|
+
const getTextOffset = (root, node, offset) => {
|
|
19
|
+
if (!node || !root.contains(node))
|
|
20
|
+
return null;
|
|
21
|
+
const range = root.ownerDocument.createRange();
|
|
22
|
+
range.selectNodeContents(root);
|
|
23
|
+
try {
|
|
24
|
+
range.setEnd(node, offset);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
return range.toString().length;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Returns offsets relative to the target text. A -1 offset means the selection
|
|
33
|
+
* is collapsed, unavailable, or outside the target.
|
|
34
|
+
*/
|
|
35
|
+
const getSelectionDetail = (dom) => {
|
|
36
|
+
const selection = dom.ownerDocument.getSelection();
|
|
37
|
+
if (!selection || selection.rangeCount === 0) {
|
|
38
|
+
return emptySelection();
|
|
39
|
+
}
|
|
40
|
+
const anchor = getTextOffset(dom, selection.anchorNode, selection.anchorOffset);
|
|
41
|
+
const focus = getTextOffset(dom, selection.focusNode, selection.focusOffset);
|
|
42
|
+
if (anchor !== null && focus !== null && anchor !== focus) {
|
|
43
|
+
return {
|
|
44
|
+
start: Math.min(anchor, focus),
|
|
45
|
+
end: Math.max(anchor, focus),
|
|
46
|
+
direction: anchor < focus ? 'forward' : 'backward',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// WebKit retargets selection endpoints outside a shadow tree. Ask for the
|
|
50
|
+
// composed range to recover the actual text nodes in that case.
|
|
51
|
+
const shadowRoots = [];
|
|
52
|
+
let root = dom.getRootNode();
|
|
53
|
+
while (root instanceof ShadowRoot) {
|
|
54
|
+
shadowRoots.push(root);
|
|
55
|
+
root = root.host.getRootNode();
|
|
56
|
+
}
|
|
57
|
+
const [range] = selection.getComposedRanges?.({ shadowRoots }) ?? [];
|
|
58
|
+
const start = range
|
|
59
|
+
? getTextOffset(dom, range.startContainer, range.startOffset)
|
|
60
|
+
: null;
|
|
61
|
+
const end = range
|
|
62
|
+
? getTextOffset(dom, range.endContainer, range.endOffset)
|
|
63
|
+
: null;
|
|
64
|
+
if (start === null || end === null || start === end)
|
|
65
|
+
return emptySelection();
|
|
66
|
+
return {
|
|
67
|
+
start,
|
|
68
|
+
end,
|
|
69
|
+
direction: selection.direction === 'backward' ? 'backward' : 'forward',
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
let XTextSelectionEvents = (() => {
|
|
73
|
+
let _instanceExtraInitializers = [];
|
|
74
|
+
let __handleEnableSelectionChangeEvent_decorators;
|
|
75
|
+
return class XTextSelectionEvents {
|
|
76
|
+
static {
|
|
77
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
78
|
+
__handleEnableSelectionChangeEvent_decorators = [registerEventEnableStatusChangeHandler('selectionchange')];
|
|
79
|
+
__esDecorate(this, null, __handleEnableSelectionChangeEvent_decorators, { kind: "method", name: "_handleEnableSelectionChangeEvent", static: false, private: false, access: { has: obj => "_handleEnableSelectionChangeEvent" in obj, get: obj => obj._handleEnableSelectionChangeEvent }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
80
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
81
|
+
}
|
|
82
|
+
static observedAttributes = [];
|
|
83
|
+
#dom = __runInitializers(this, _instanceExtraInitializers);
|
|
84
|
+
#enabled = false;
|
|
85
|
+
#connected = false;
|
|
86
|
+
#attachedDocument;
|
|
87
|
+
#lastSelectionSignature;
|
|
88
|
+
constructor(dom) {
|
|
89
|
+
this.#dom = dom;
|
|
90
|
+
}
|
|
91
|
+
connectedCallback() {
|
|
92
|
+
this.#connected = true;
|
|
93
|
+
this.#updateSelectionChangeListener();
|
|
94
|
+
}
|
|
95
|
+
dispose() {
|
|
96
|
+
this.#connected = false;
|
|
97
|
+
this.#updateSelectionChangeListener();
|
|
98
|
+
}
|
|
99
|
+
_handleEnableSelectionChangeEvent(status) {
|
|
100
|
+
this.#enabled = status;
|
|
101
|
+
if (!status)
|
|
102
|
+
this.#lastSelectionSignature = undefined;
|
|
103
|
+
this.#updateSelectionChangeListener();
|
|
104
|
+
}
|
|
105
|
+
#updateSelectionChangeListener() {
|
|
106
|
+
const document = this.#dom.ownerDocument;
|
|
107
|
+
const shouldAttach = this.#enabled && this.#connected;
|
|
108
|
+
if (shouldAttach && this.#attachedDocument === document)
|
|
109
|
+
return;
|
|
110
|
+
if (!shouldAttach && !this.#attachedDocument)
|
|
111
|
+
return;
|
|
112
|
+
this.#attachedDocument?.removeEventListener('selectionchange', this.#handleSelectionChange);
|
|
113
|
+
if (shouldAttach) {
|
|
114
|
+
document.addEventListener('selectionchange', this.#handleSelectionChange);
|
|
115
|
+
}
|
|
116
|
+
this.#attachedDocument = shouldAttach ? document : undefined;
|
|
117
|
+
}
|
|
118
|
+
#handleSelectionChange = () => {
|
|
119
|
+
const detail = getSelectionDetail(this.#dom);
|
|
120
|
+
const signature = `${detail.start}:${detail.end}:${detail.direction}`;
|
|
121
|
+
if (signature === this.#lastSelectionSignature
|
|
122
|
+
|| (detail.start === -1 && this.#lastSelectionSignature === undefined)) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
this.#lastSelectionSignature = signature;
|
|
126
|
+
this.#dom.dispatchEvent(new CustomEvent('selectionchange', {
|
|
127
|
+
...commonComponentEventSetting,
|
|
128
|
+
detail,
|
|
129
|
+
}));
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
})();
|
|
133
|
+
export { XTextSelectionEvents };
|
|
134
|
+
//# sourceMappingURL=XTextSelectionEvents.js.map
|
|
@@ -14,6 +14,7 @@ export { RawText } from './RawText.js';
|
|
|
14
14
|
*
|
|
15
15
|
* Events:
|
|
16
16
|
* - `layout`: Fired when text layout happens (if enabled). Detail provides line info.
|
|
17
|
+
* - `selectionchange`: Fired when the text selection changes. Detail provides start, end, and direction.
|
|
17
18
|
*
|
|
18
19
|
* CSS Variables:
|
|
19
20
|
* - `--lynx-text-bg-color`: Inherited background color for nested text elements.
|
|
@@ -17,6 +17,7 @@ export { RawText } from './RawText.js';
|
|
|
17
17
|
*
|
|
18
18
|
* Events:
|
|
19
19
|
* - `layout`: Fired when text layout happens (if enabled). Detail provides line info.
|
|
20
|
+
* - `selectionchange`: Fired when the text selection changes. Detail provides start, end, and direction.
|
|
20
21
|
*
|
|
21
22
|
* CSS Variables:
|
|
22
23
|
* - `--lynx-text-bg-color`: Inherited background color for nested text elements.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lynx-js/web-elements",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.11",
|
|
4
4
|
"private": false,
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -34,6 +34,11 @@
|
|
|
34
34
|
"types": "./dist/elements/ScrollView/index.d.ts",
|
|
35
35
|
"default": "./dist/elements/ScrollView/index.js"
|
|
36
36
|
},
|
|
37
|
+
"./plugins/scroll-view-mouse-drag": {
|
|
38
|
+
"@lynx-js/source-field": "./src/elements/ScrollView/ScrollViewMouseDrag.ts",
|
|
39
|
+
"types": "./dist/elements/ScrollView/ScrollViewMouseDrag.d.ts",
|
|
40
|
+
"default": "./dist/elements/ScrollView/ScrollViewMouseDrag.js"
|
|
41
|
+
},
|
|
37
42
|
"./XAudioTT": {
|
|
38
43
|
"@lynx-js/source-field": "./src/elements/XAudioTT/index.ts",
|
|
39
44
|
"types": "./dist/elements/XAudioTT/index.d.ts",
|
|
@@ -135,17 +140,17 @@
|
|
|
135
140
|
],
|
|
136
141
|
"dependencies": {
|
|
137
142
|
"dompurify": "^3.4.13",
|
|
138
|
-
"markdown-it": "^15.0.
|
|
143
|
+
"markdown-it": "^15.0.1"
|
|
139
144
|
},
|
|
140
145
|
"devDependencies": {
|
|
146
|
+
"@lynx-js/playwright-fixtures": "0.0.0",
|
|
141
147
|
"@playwright/test": "^1.61.1",
|
|
142
|
-
"@rsbuild/core": "2.
|
|
148
|
+
"@rsbuild/core": "2.2.4",
|
|
143
149
|
"@rsbuild/plugin-source-build": "1.0.6",
|
|
144
150
|
"@types/markdown-it": "^14.1.2",
|
|
145
151
|
"@types/node": "^24.13.3",
|
|
146
152
|
"nyc": "^18.0.0",
|
|
147
|
-
"tslib": "^2.8.1"
|
|
148
|
-
"@lynx-js/playwright-fixtures": "0.0.0"
|
|
153
|
+
"tslib": "^2.8.1"
|
|
149
154
|
},
|
|
150
155
|
"peerDependencies": {
|
|
151
156
|
"tslib": "^2.5.0"
|
|
@@ -68,16 +68,22 @@ inline-truncation > lynx-wrapper > x-text {
|
|
|
68
68
|
|
|
69
69
|
x-text > inline-image,
|
|
70
70
|
x-text > x-image,
|
|
71
|
+
x-text > x-svg,
|
|
71
72
|
inline-truncation > inline-image,
|
|
72
73
|
inline-truncation > x-image,
|
|
74
|
+
inline-truncation > x-svg,
|
|
73
75
|
inline-text > inline-image,
|
|
74
76
|
inline-text > x-image,
|
|
77
|
+
inline-text > x-svg,
|
|
75
78
|
x-text > lynx-wrapper > inline-image,
|
|
76
79
|
x-text > lynx-wrapper > x-image,
|
|
80
|
+
x-text > lynx-wrapper > x-svg,
|
|
77
81
|
inline-truncation > lynx-wrapper > inline-image,
|
|
78
82
|
inline-truncation > lynx-wrapper > x-image,
|
|
83
|
+
inline-truncation > lynx-wrapper > x-svg,
|
|
79
84
|
inline-text > lynx-wrapper > inline-image,
|
|
80
|
-
inline-text > lynx-wrapper > x-image
|
|
85
|
+
inline-text > lynx-wrapper > x-image,
|
|
86
|
+
inline-text > lynx-wrapper > x-svg {
|
|
81
87
|
display: contents !important;
|
|
82
88
|
}
|
|
83
89
|
|
|
@@ -118,9 +124,13 @@ inline-image::part(img) {
|
|
|
118
124
|
}
|
|
119
125
|
|
|
120
126
|
x-text > x-image::part(img),
|
|
127
|
+
x-text > x-svg::part(img),
|
|
121
128
|
x-text > lynx-wrapper > x-image::part(img),
|
|
129
|
+
x-text > lynx-wrapper > x-svg::part(img),
|
|
122
130
|
inline-truncation > x-image::part(img),
|
|
123
|
-
inline-truncation >
|
|
131
|
+
inline-truncation > x-svg::part(img),
|
|
132
|
+
inline-truncation > lynx-wrapper > x-image::part(img),
|
|
133
|
+
inline-truncation > lynx-wrapper > x-svg::part(img) {
|
|
124
134
|
display: inline-block;
|
|
125
135
|
height: inherit !important;
|
|
126
136
|
width: inherit !important;
|
|
@@ -159,11 +169,13 @@ x-text[text-selection] > inline-text,
|
|
|
159
169
|
x-text[text-selection] > x-text,
|
|
160
170
|
x-text[text-selection] > inline-image,
|
|
161
171
|
x-text[text-selection] > x-image,
|
|
172
|
+
x-text[text-selection] > x-svg,
|
|
162
173
|
x-text[text-selection] > inline-truncation,
|
|
163
174
|
x-text[text-selection] > lynx-wrapper > inline-text,
|
|
164
175
|
x-text[text-selection] > lynx-wrapper > x-text,
|
|
165
176
|
x-text[text-selection] > lynx-wrapper > inline-image,
|
|
166
177
|
x-text[text-selection] > lynx-wrapper > x-image,
|
|
178
|
+
x-text[text-selection] > lynx-wrapper > x-svg,
|
|
167
179
|
x-text[text-selection] > lynx-wrapper > inline-truncation {
|
|
168
180
|
-webkit-user-select: auto;
|
|
169
181
|
user-select: auto;
|