@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 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
@@ -4,5 +4,6 @@ export declare class ImageEvents implements InstanceType<AttributeReactiveClass<
4
4
  static observedAttributes: never[];
5
5
  _enableLoadEvent(status: boolean): void;
6
6
  _enableErrorEvent(status: boolean): void;
7
+ connectedCallback(): void;
7
8
  constructor(dom: HTMLElement);
8
9
  }
@@ -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
- this.#dom.dispatchEvent(new CustomEvent('load', {
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;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @deprecated you can use x-text instead in x-text.
2
+ * @deprecated Use x-text instead of inline-text.
3
3
  */
4
4
  export declare class InlineText extends HTMLElement {
5
5
  }
@@ -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 you can use x-text instead in x-text.
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', [CommonEventsAndMethods, XTextTruncation, RawTextAttributes], templateXText)];
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.9",
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.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.1.10",
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 > lynx-wrapper > x-image::part(img) {
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;