@lynx-js/web-elements 0.12.0 → 0.12.1
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 +51 -15
- package/dist/elements/XFoldViewNg/XFoldviewSlotNgTouchEventsHandler.js +29 -3
- package/dist/elements/XList/XList.d.ts +2 -0
- package/dist/elements/XList/XList.js +4 -0
- package/dist/elements/XMarkdown/XMarkdown.d.ts +43 -0
- package/dist/elements/XMarkdown/XMarkdown.js +346 -0
- package/dist/elements/XMarkdown/XMarkdownAttributes.d.ts +35 -0
- package/dist/elements/XMarkdown/XMarkdownAttributes.js +1232 -0
- package/dist/elements/XMarkdown/XMarkdownDeps.d.ts +2 -0
- package/dist/elements/XMarkdown/XMarkdownDeps.js +11 -0
- package/dist/elements/XMarkdown/index.d.ts +7 -0
- package/dist/elements/XMarkdown/index.js +11 -0
- package/dist/elements/all.d.ts +1 -0
- package/dist/elements/all.js +1 -0
- package/dist/elements/htmlTemplates.d.ts +2 -1
- package/dist/elements/htmlTemplates.js +87 -0
- package/index.css +1 -0
- package/package.json +15 -3
- package/src/elements/XList/x-list.css +31 -0
- package/src/elements/XMarkdown/x-markdown.css +15 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# @lynx-js/web-elements
|
|
2
2
|
|
|
3
|
+
## 0.12.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- fix: XMarkdown slot created should not have prefix ([#2520](https://github.com/lynx-family/lynx-stack/pull/2520))
|
|
8
|
+
|
|
9
|
+
- feat: add x-markdown support ([#2412](https://github.com/lynx-family/lynx-stack/pull/2412))
|
|
10
|
+
|
|
11
|
+
Add opt-in support for the `x-markdown` element on Lynx Web, including
|
|
12
|
+
Markdown rendering together with its related styling, interaction, animation,
|
|
13
|
+
truncation, range rendering, and effect capabilities exposed through the
|
|
14
|
+
component API.
|
|
15
|
+
|
|
16
|
+
Update the `web-core`, `web-core-wasm`, and `web-mainthread-apis` runtime
|
|
17
|
+
paths to use the shared property-or-attribute setter from `web-constants`, so
|
|
18
|
+
custom elements such as `x-markdown` can receive structured property values
|
|
19
|
+
correctly instead of being forced through string-only attribute updates.
|
|
20
|
+
|
|
21
|
+
```javascript
|
|
22
|
+
import '@lynx-js/web-elements/XMarkdown';
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
- fix: x-markdown inline view injection no longer queries light DOM children when the content attribute changes. Consumers must now pre-set `slot="{id}"` on the child element they want to project into `inlineview://{id}`. ([#2516](https://github.com/lynx-family/lynx-stack/pull/2516))
|
|
26
|
+
|
|
27
|
+
- fix: list cannot drag-scroll inside x-foldview-slot-ng ([#2507](https://github.com/lynx-family/lynx-stack/pull/2507))
|
|
28
|
+
|
|
29
|
+
Cause: `touchstart` used `elementsFromPoint(pageX, pageY)` (expects `clientX/clientY`), so hit-testing can miss the real inner scroller (e.g. `x-list` shadow `#content`) when the document is scrolled.
|
|
30
|
+
|
|
31
|
+
Fix: use `elementsFromPoint(clientX, clientY)` + `event.composedPath()` for Shadow DOM, and keep `previousPageX` updated during `touchmove`.
|
|
32
|
+
|
|
33
|
+
- fix: line-height of markdown-style should be added `px` ([#2509](https://github.com/lynx-family/lynx-stack/pull/2509))
|
|
34
|
+
|
|
35
|
+
- fix: list `bindscrolltolower` may not trigger because the lower threshold ([#2484](https://github.com/lynx-family/lynx-stack/pull/2484))
|
|
36
|
+
sentinel had no effective size or offset, causing the bottom
|
|
37
|
+
`IntersectionObserver` to miss the list boundary
|
|
38
|
+
|
|
3
39
|
## 0.12.0
|
|
4
40
|
|
|
5
41
|
### Minor Changes
|
|
@@ -80,14 +116,14 @@
|
|
|
80
116
|
|
|
81
117
|
- chore: migrate all @lynx-js/web-elements-\* packages into one ([#2057](https://github.com/lynx-family/lynx-stack/pull/2057))
|
|
82
118
|
|
|
83
|
-
|
|
119
|
+
#### Before
|
|
84
120
|
|
|
85
121
|
```js
|
|
86
122
|
import '@lynx-js/web-elements-template';
|
|
87
123
|
import '@lynx-js/web-elements-compat/LinearContainer';
|
|
88
124
|
```
|
|
89
125
|
|
|
90
|
-
|
|
126
|
+
#### After
|
|
91
127
|
|
|
92
128
|
```js
|
|
93
129
|
import '@lynx-js/web-elements/html-templates';
|
|
@@ -710,17 +746,17 @@
|
|
|
710
746
|
- 3547621: feat(web): use `<lynx-wrapper/>` to replace `<div style="display:content"/>`
|
|
711
747
|
- bed4f24: feat(web): implement <x-list> with list-type="single"
|
|
712
748
|
|
|
713
|
-
|
|
749
|
+
#### 1. RFC
|
|
714
750
|
|
|
715
751
|
https://github.com/lynx-wg/lynx-stack/issues/106
|
|
716
752
|
|
|
717
|
-
|
|
753
|
+
#### 2. Implementation differences with RFC
|
|
718
754
|
|
|
719
|
-
|
|
755
|
+
##### paging-enabled
|
|
720
756
|
|
|
721
757
|
deprecated, no need to implement
|
|
722
758
|
|
|
723
|
-
|
|
759
|
+
##### layoutcomplete
|
|
724
760
|
|
|
725
761
|
Triggered only after the first screen because using contentvisibilityautostatechange.
|
|
726
762
|
|
|
@@ -729,28 +765,28 @@
|
|
|
729
765
|
> This is because content is the parent container of list-item, and content is always visible before list-item.
|
|
730
766
|
> We cannot obtain the timing of all the successfully visible list-items on the screen, so 100ms is used to delay this behavior.
|
|
731
767
|
|
|
732
|
-
|
|
768
|
+
##### event-scrolltoedge
|
|
733
769
|
|
|
734
770
|
split bindscrolltoupperedge and bindscrooltolowerdge.
|
|
735
771
|
|
|
736
|
-
|
|
772
|
+
##### event-scrolltoupper/lower
|
|
737
773
|
|
|
738
774
|
Can be used with upper/lower-threshold-item-count.
|
|
739
775
|
|
|
740
776
|
Attention, when the number of x-list children changes, scrolltoupper/lower will be re-triggered (if the new node is on the screen).
|
|
741
777
|
|
|
742
|
-
|
|
778
|
+
##### getVisibleCells, layoutcomplete
|
|
743
779
|
|
|
744
780
|
The returned cells may be an empty array, because there is a high probability that the contentvisibilityautostatechange event of list-item will not be captured when the first screen is displayed.
|
|
745
781
|
|
|
746
|
-
|
|
782
|
+
#### 3. Tests not implemented
|
|
747
783
|
|
|
748
|
-
|
|
784
|
+
##### HTML Tests
|
|
749
785
|
|
|
750
786
|
1. event-layoutcomplete skipped webkit, firefox due to contentvisibilityautostatechange not propagate
|
|
751
787
|
2. get-visible-cells skipped webkit, firefox due to contentvisibilityautostatechange not propagate
|
|
752
788
|
|
|
753
|
-
|
|
789
|
+
##### React Tests
|
|
754
790
|
|
|
755
791
|
1. lynx.createQuery not supported.
|
|
756
792
|
|
|
@@ -815,7 +851,7 @@
|
|
|
815
851
|
- 8c6eeb9: fix(web): rename x-swiper-itrm to swiper-item
|
|
816
852
|
- 1fe49a2: feat(web): add custom element x-audio-tt
|
|
817
853
|
|
|
818
|
-
|
|
854
|
+
#### The behavior is different from the native x-audio-tt:
|
|
819
855
|
|
|
820
856
|
- When src changes, resources will not be loaded immediately. Resources will only be loaded when the play and prepare methods are triggered.
|
|
821
857
|
|
|
@@ -824,13 +860,13 @@
|
|
|
824
860
|
|
|
825
861
|
- The code returned by the binderror event does not include -4
|
|
826
862
|
|
|
827
|
-
|
|
863
|
+
#### Unimplemented properties:
|
|
828
864
|
|
|
829
865
|
- autoplay
|
|
830
866
|
- playertype
|
|
831
867
|
- experimental-ios-async-prepare
|
|
832
868
|
|
|
833
|
-
|
|
869
|
+
#### Unimplemented methods:
|
|
834
870
|
|
|
835
871
|
- requestFocus
|
|
836
872
|
- releaseFocus
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { scrollContainerDom } from '../common/constants.js';
|
|
1
2
|
import { isHeaderShowing } from './XFoldviewNg.js';
|
|
2
3
|
export class XFoldviewSlotNgTouchEventsHandler {
|
|
3
4
|
#parentScrollTop = 0;
|
|
@@ -25,6 +26,17 @@ export class XFoldviewSlotNgTouchEventsHandler {
|
|
|
25
26
|
passive: false,
|
|
26
27
|
});
|
|
27
28
|
}
|
|
29
|
+
#resolveScrollContainer(element) {
|
|
30
|
+
const maybeScrollContainer = element[scrollContainerDom];
|
|
31
|
+
return maybeScrollContainer instanceof Element
|
|
32
|
+
? maybeScrollContainer
|
|
33
|
+
: element;
|
|
34
|
+
}
|
|
35
|
+
#collectCandidateElements(elements) {
|
|
36
|
+
return [
|
|
37
|
+
...new Set(elements.map(element => this.#resolveScrollContainer(element))),
|
|
38
|
+
];
|
|
39
|
+
}
|
|
28
40
|
#isScrollContainer(element) {
|
|
29
41
|
let overflowY;
|
|
30
42
|
if (typeof element.computedStyleMap === 'function') {
|
|
@@ -83,6 +95,7 @@ export class XFoldviewSlotNgTouchEventsHandler {
|
|
|
83
95
|
}
|
|
84
96
|
this.#handleScrollDelta(deltaY, parentElement);
|
|
85
97
|
this.#previousPageY = pageY;
|
|
98
|
+
this.#previousPageX = pageX;
|
|
86
99
|
};
|
|
87
100
|
#handleWheel = (event) => {
|
|
88
101
|
const parentElement = this.#getParentElement();
|
|
@@ -96,7 +109,10 @@ export class XFoldviewSlotNgTouchEventsHandler {
|
|
|
96
109
|
&& element !== this.#dom);
|
|
97
110
|
const { clientX, clientY } = event;
|
|
98
111
|
const pointElements = document.elementsFromPoint(clientX, clientY).filter(e => this.#dom.contains(e));
|
|
99
|
-
this.#elements =
|
|
112
|
+
this.#elements = this.#collectCandidateElements([
|
|
113
|
+
...pathElements,
|
|
114
|
+
...pointElements,
|
|
115
|
+
]);
|
|
100
116
|
this.#parentScrollTop = parentElement.scrollTop;
|
|
101
117
|
if (this.#elements) {
|
|
102
118
|
for (const element of this.#elements) {
|
|
@@ -115,8 +131,18 @@ export class XFoldviewSlotNgTouchEventsHandler {
|
|
|
115
131
|
}
|
|
116
132
|
}
|
|
117
133
|
#touchStart = (event) => {
|
|
118
|
-
const
|
|
119
|
-
|
|
134
|
+
const touch = event.touches.item(0);
|
|
135
|
+
const { pageX, pageY, clientX, clientY } = touch;
|
|
136
|
+
// `elementsFromPoint()` doesn't reliably pierce into Shadow DOM; combine with
|
|
137
|
+
// the composed path so we can pick up internal scroll containers like
|
|
138
|
+
// `x-list`'s `#content` inside the shadow root.
|
|
139
|
+
const pathElements = event.composedPath().filter((element) => element instanceof Element && this.#dom.contains(element)
|
|
140
|
+
&& element !== this.#dom);
|
|
141
|
+
const pointElements = document.elementsFromPoint(clientX, clientY).filter(e => this.#dom.contains(e) && e !== this.#dom);
|
|
142
|
+
this.#elements = this.#collectCandidateElements([
|
|
143
|
+
...pathElements,
|
|
144
|
+
...pointElements,
|
|
145
|
+
]);
|
|
120
146
|
this.#previousPageY = pageY;
|
|
121
147
|
this.#previousPageX = pageX;
|
|
122
148
|
this.#parentScrollTop = this.#getParentElement()?.scrollTop ?? 0;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { scrollContainerDom } from '../common/constants.js';
|
|
1
2
|
export declare class XList extends HTMLElement {
|
|
2
3
|
#private;
|
|
3
4
|
static readonly notToFilterFalseAttributes: Set<string>;
|
|
@@ -7,6 +8,7 @@ export declare class XList extends HTMLElement {
|
|
|
7
8
|
set scrollLeft(val: number);
|
|
8
9
|
get scrollHeight(): number;
|
|
9
10
|
get scrollWidth(): number;
|
|
11
|
+
get [scrollContainerDom](): HTMLElement;
|
|
10
12
|
get __scrollTop(): number;
|
|
11
13
|
get __scrollLeft(): number;
|
|
12
14
|
scrollToPosition(params: {
|
|
@@ -10,6 +10,7 @@ import { XListEvents } from './XListEvents.js';
|
|
|
10
10
|
import { XListWaterfall } from './XListWaterfall.js';
|
|
11
11
|
import { CommonEventsAndMethods } from '../common/CommonEventsAndMethods.js';
|
|
12
12
|
import { commonComponentEventSetting } from '../common/commonEventInitConfiguration.js';
|
|
13
|
+
import { scrollContainerDom } from '../common/constants.js';
|
|
13
14
|
import { templateXList } from '../htmlTemplates.js';
|
|
14
15
|
let XList = (() => {
|
|
15
16
|
let _classDecorators = [Component('x-list', [CommonEventsAndMethods, XListAttributes, XListEvents, XListWaterfall], templateXList)];
|
|
@@ -52,6 +53,9 @@ let XList = (() => {
|
|
|
52
53
|
get scrollWidth() {
|
|
53
54
|
return this.#getListContainer().scrollWidth;
|
|
54
55
|
}
|
|
56
|
+
get [scrollContainerDom]() {
|
|
57
|
+
return this.#getListContainer();
|
|
58
|
+
}
|
|
55
59
|
get __scrollTop() {
|
|
56
60
|
return super.scrollTop;
|
|
57
61
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { MarkdownStyleConfig } from './XMarkdownAttributes.js';
|
|
2
|
+
export declare class XMarkdown extends HTMLElement {
|
|
3
|
+
#private;
|
|
4
|
+
static readonly notToFilterFalseAttributes: Set<string>;
|
|
5
|
+
get markdownStyle(): MarkdownStyleConfig;
|
|
6
|
+
set markdownStyle(value: MarkdownStyleConfig | string | null);
|
|
7
|
+
get ['markdown-style'](): MarkdownStyleConfig;
|
|
8
|
+
set ['markdown-style'](value: MarkdownStyleConfig | string | null);
|
|
9
|
+
/**
|
|
10
|
+
* 获取当前渲染内容中的所有图片 URL。
|
|
11
|
+
*/
|
|
12
|
+
getImages(): string[];
|
|
13
|
+
getContent(params?: {
|
|
14
|
+
start?: number;
|
|
15
|
+
end?: number;
|
|
16
|
+
}): {
|
|
17
|
+
content: string;
|
|
18
|
+
};
|
|
19
|
+
pauseAnimation(): void;
|
|
20
|
+
resumeAnimation(params?: {
|
|
21
|
+
animationStep?: number;
|
|
22
|
+
}): void;
|
|
23
|
+
getSelectedText(): string;
|
|
24
|
+
getTextBoundingRect(params?: {
|
|
25
|
+
start?: number;
|
|
26
|
+
end?: number;
|
|
27
|
+
indexType?: 'char' | 'source';
|
|
28
|
+
}): {
|
|
29
|
+
boundingRect: DOMRect;
|
|
30
|
+
} | null;
|
|
31
|
+
setTextSelection(params: {
|
|
32
|
+
startX: number;
|
|
33
|
+
startY: number;
|
|
34
|
+
endX: number;
|
|
35
|
+
endY: number;
|
|
36
|
+
}): void;
|
|
37
|
+
getParseResult(params: {
|
|
38
|
+
tags: string[];
|
|
39
|
+
}): Record<string, {
|
|
40
|
+
start: number;
|
|
41
|
+
end: number;
|
|
42
|
+
}[]>;
|
|
43
|
+
}
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { __esDecorate, __runInitializers } from "tslib";
|
|
2
|
+
/*
|
|
3
|
+
// Copyright 2024 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 { Component } from '../../element-reactive/index.js';
|
|
8
|
+
import { CommonEventsAndMethods } from '../common/CommonEventsAndMethods.js';
|
|
9
|
+
import { templateXMarkdown } from '../htmlTemplates.js';
|
|
10
|
+
import { parseMarkdownStyle, serializeMarkdownStyle, XMarkdownAttributes, } from './XMarkdownAttributes.js';
|
|
11
|
+
const getComposedRange = (selection, shadowRoot) => {
|
|
12
|
+
const getComposedRanges = selection
|
|
13
|
+
.getComposedRanges;
|
|
14
|
+
if (!shadowRoot || typeof getComposedRanges !== 'function')
|
|
15
|
+
return null;
|
|
16
|
+
try {
|
|
17
|
+
return getComposedRanges.call(selection, {
|
|
18
|
+
shadowRoots: [shadowRoot],
|
|
19
|
+
})[0] ?? null;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
try {
|
|
23
|
+
return getComposedRanges.call(selection, shadowRoot)[0] ?? null;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const createRangeByOffsets = (doc, root, start, end) => {
|
|
31
|
+
const walker = doc.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
32
|
+
let pos = 0;
|
|
33
|
+
let startNode = null;
|
|
34
|
+
let startOffset = 0;
|
|
35
|
+
let endNode = null;
|
|
36
|
+
let endOffset = 0;
|
|
37
|
+
let node = walker.nextNode();
|
|
38
|
+
while (node) {
|
|
39
|
+
const len = node.nodeValue?.length ?? 0;
|
|
40
|
+
if (!startNode && pos + len >= start) {
|
|
41
|
+
startNode = node;
|
|
42
|
+
startOffset = start - pos;
|
|
43
|
+
}
|
|
44
|
+
if (pos + len >= end) {
|
|
45
|
+
endNode = node;
|
|
46
|
+
endOffset = end - pos;
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
pos += len;
|
|
50
|
+
node = walker.nextNode();
|
|
51
|
+
}
|
|
52
|
+
if (!startNode)
|
|
53
|
+
return null;
|
|
54
|
+
if (!endNode) {
|
|
55
|
+
endNode = startNode;
|
|
56
|
+
endOffset = startOffset;
|
|
57
|
+
}
|
|
58
|
+
const range = doc.createRange();
|
|
59
|
+
range.setStart(startNode, Math.max(0, Math.min(startOffset, startNode.length)));
|
|
60
|
+
range.setEnd(endNode, Math.max(0, Math.min(endOffset, endNode.length)));
|
|
61
|
+
return range;
|
|
62
|
+
};
|
|
63
|
+
const getRenderedPrefixLengthForSourceOffset = (source, rendered, sourceOffset) => {
|
|
64
|
+
let renderedIndex = 0;
|
|
65
|
+
const limit = Math.max(0, Math.min(sourceOffset, source.length));
|
|
66
|
+
for (let sourceIndex = 0; sourceIndex < limit; sourceIndex += 1) {
|
|
67
|
+
if (renderedIndex >= rendered.length)
|
|
68
|
+
break;
|
|
69
|
+
if (source[sourceIndex] === rendered[renderedIndex]) {
|
|
70
|
+
renderedIndex += 1;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return renderedIndex;
|
|
74
|
+
};
|
|
75
|
+
const mapSourceRangeToCharRange = (source, rendered, start, end) => {
|
|
76
|
+
const renderedStart = getRenderedPrefixLengthForSourceOffset(source, rendered, start);
|
|
77
|
+
const renderedEnd = getRenderedPrefixLengthForSourceOffset(source, rendered, end);
|
|
78
|
+
return {
|
|
79
|
+
start: renderedStart,
|
|
80
|
+
end: Math.max(renderedStart, renderedEnd),
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
const isSelectionNodeInsideHost = (dom, shadowRoot, node) => !!node
|
|
84
|
+
&& (node === dom
|
|
85
|
+
|| node === shadowRoot
|
|
86
|
+
|| dom.contains(node)
|
|
87
|
+
|| !!shadowRoot?.contains(node));
|
|
88
|
+
const getRangeInRoot = (dom, root, selection) => {
|
|
89
|
+
if (!selection)
|
|
90
|
+
return null;
|
|
91
|
+
const shadowRoot = dom.shadowRoot;
|
|
92
|
+
const sourceRange = getComposedRange(selection, shadowRoot)
|
|
93
|
+
?? (selection.rangeCount > 0 ? selection.getRangeAt(0) : null);
|
|
94
|
+
if (!sourceRange)
|
|
95
|
+
return null;
|
|
96
|
+
if (!root.contains(sourceRange.startContainer)
|
|
97
|
+
|| !root.contains(sourceRange.endContainer)) {
|
|
98
|
+
if (!isSelectionNodeInsideHost(dom, shadowRoot, sourceRange.startContainer)
|
|
99
|
+
|| !isSelectionNodeInsideHost(dom, shadowRoot, sourceRange.endContainer)
|
|
100
|
+
|| !isSelectionNodeInsideHost(dom, shadowRoot, selection.anchorNode)
|
|
101
|
+
|| !isSelectionNodeInsideHost(dom, shadowRoot, selection.focusNode)) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
const selectedText = selection.toString();
|
|
105
|
+
if (!selectedText) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
const start = root.textContent?.indexOf(selectedText) ?? -1;
|
|
109
|
+
if (start < 0)
|
|
110
|
+
return null;
|
|
111
|
+
return createRangeByOffsets(dom.ownerDocument, root, start, start + selectedText.length);
|
|
112
|
+
}
|
|
113
|
+
const range = dom.ownerDocument.createRange();
|
|
114
|
+
range.setStart(sourceRange.startContainer, sourceRange.startOffset);
|
|
115
|
+
range.setEnd(sourceRange.endContainer, sourceRange.endOffset);
|
|
116
|
+
return range;
|
|
117
|
+
};
|
|
118
|
+
const getSelectionCandidates = (dom) => {
|
|
119
|
+
const shadowRoot = dom.shadowRoot;
|
|
120
|
+
const shadowSelection = shadowRoot && typeof shadowRoot.getSelection === 'function'
|
|
121
|
+
? shadowRoot.getSelection()
|
|
122
|
+
: null;
|
|
123
|
+
const documentSelection = dom.ownerDocument.getSelection();
|
|
124
|
+
return [shadowSelection, documentSelection].filter((selection, index, selections) => !!selection && selections.indexOf(selection) === index);
|
|
125
|
+
};
|
|
126
|
+
const getSelectionForRoot = (dom, root) => {
|
|
127
|
+
for (const selection of getSelectionCandidates(dom)) {
|
|
128
|
+
const range = getRangeInRoot(dom, root, selection);
|
|
129
|
+
if (range)
|
|
130
|
+
return { selection, range };
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
};
|
|
134
|
+
const getPreferredSelectionTarget = (dom) => {
|
|
135
|
+
const candidates = getSelectionCandidates(dom);
|
|
136
|
+
return candidates[0] ?? null;
|
|
137
|
+
};
|
|
138
|
+
let XMarkdown = (() => {
|
|
139
|
+
let _classDecorators = [Component('x-markdown', [CommonEventsAndMethods, XMarkdownAttributes], templateXMarkdown)];
|
|
140
|
+
let _classDescriptor;
|
|
141
|
+
let _classExtraInitializers = [];
|
|
142
|
+
let _classThis;
|
|
143
|
+
let _classSuper = HTMLElement;
|
|
144
|
+
var XMarkdown = class extends _classSuper {
|
|
145
|
+
static { _classThis = this; }
|
|
146
|
+
static {
|
|
147
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
148
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
149
|
+
XMarkdown = _classThis = _classDescriptor.value;
|
|
150
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
151
|
+
}
|
|
152
|
+
static notToFilterFalseAttributes = new Set(['content-complete']);
|
|
153
|
+
#getMarkdownStyle() {
|
|
154
|
+
return parseMarkdownStyle(this.getAttribute('markdown-style'));
|
|
155
|
+
}
|
|
156
|
+
#setMarkdownStyle(value) {
|
|
157
|
+
const serialized = serializeMarkdownStyle(value);
|
|
158
|
+
if (serialized === null) {
|
|
159
|
+
this.removeAttribute('markdown-style');
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
this.setAttribute('markdown-style', serialized);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
get markdownStyle() {
|
|
166
|
+
return this.#getMarkdownStyle();
|
|
167
|
+
}
|
|
168
|
+
set markdownStyle(value) {
|
|
169
|
+
this.#setMarkdownStyle(value);
|
|
170
|
+
}
|
|
171
|
+
get ['markdown-style']() {
|
|
172
|
+
return this.#getMarkdownStyle();
|
|
173
|
+
}
|
|
174
|
+
set ['markdown-style'](value) {
|
|
175
|
+
this.#setMarkdownStyle(value);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* 获取当前渲染内容中的所有图片 URL。
|
|
179
|
+
*/
|
|
180
|
+
getImages() {
|
|
181
|
+
const root = this.shadowRoot?.querySelector('#markdown-root');
|
|
182
|
+
if (!root)
|
|
183
|
+
return [];
|
|
184
|
+
return Array.from(root.querySelectorAll('img'))
|
|
185
|
+
.map((img) => img.getAttribute('src') || '')
|
|
186
|
+
.filter((v) => !!v);
|
|
187
|
+
}
|
|
188
|
+
getContent(params) {
|
|
189
|
+
const content = this.getAttribute('content') ?? '';
|
|
190
|
+
const s = Math.max(0, params?.start ?? 0);
|
|
191
|
+
const eInclusive = params?.end ?? (content.length - 1);
|
|
192
|
+
const e = Math.min(content.length, eInclusive + 1);
|
|
193
|
+
const slice = e >= s ? content.slice(s, e) : '';
|
|
194
|
+
return { content: slice };
|
|
195
|
+
}
|
|
196
|
+
pauseAnimation() {
|
|
197
|
+
this.setAttribute('animation-paused', 'true');
|
|
198
|
+
}
|
|
199
|
+
resumeAnimation(params) {
|
|
200
|
+
if (params?.animationStep !== undefined) {
|
|
201
|
+
this.setAttribute('initial-animation-step', String(params.animationStep));
|
|
202
|
+
}
|
|
203
|
+
if (this.getAttribute('animation-type') !== 'typewriter') {
|
|
204
|
+
this.setAttribute('animation-type', 'typewriter');
|
|
205
|
+
}
|
|
206
|
+
const velocity = this.getAttribute('animation-velocity');
|
|
207
|
+
if (!velocity || Number(velocity) <= 0) {
|
|
208
|
+
this.setAttribute('animation-velocity', '40');
|
|
209
|
+
}
|
|
210
|
+
this.removeAttribute('animation-paused');
|
|
211
|
+
}
|
|
212
|
+
getSelectedText() {
|
|
213
|
+
const root = this.shadowRoot?.querySelector('#markdown-root');
|
|
214
|
+
if (!root)
|
|
215
|
+
return '';
|
|
216
|
+
const result = getSelectionForRoot(this, root);
|
|
217
|
+
return result?.range.toString() ?? '';
|
|
218
|
+
}
|
|
219
|
+
getTextBoundingRect(params) {
|
|
220
|
+
const root = this.shadowRoot?.querySelector('#markdown-root');
|
|
221
|
+
if (!root)
|
|
222
|
+
return null;
|
|
223
|
+
const doc = this.ownerDocument;
|
|
224
|
+
const createRangeByChar = (s, e) => {
|
|
225
|
+
const walker = doc.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
226
|
+
let pos = 0;
|
|
227
|
+
let startNode = null;
|
|
228
|
+
let startOffset = 0;
|
|
229
|
+
let endNode = null;
|
|
230
|
+
let endOffset = 0;
|
|
231
|
+
let node = walker.nextNode();
|
|
232
|
+
while (node) {
|
|
233
|
+
const len = node.nodeValue?.length ?? 0;
|
|
234
|
+
if (!startNode && pos + len >= s) {
|
|
235
|
+
startNode = node;
|
|
236
|
+
startOffset = s - pos;
|
|
237
|
+
}
|
|
238
|
+
if (pos + len >= e) {
|
|
239
|
+
endNode = node;
|
|
240
|
+
endOffset = e - pos;
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
pos += len;
|
|
244
|
+
node = walker.nextNode();
|
|
245
|
+
}
|
|
246
|
+
if (!startNode)
|
|
247
|
+
return null;
|
|
248
|
+
if (!endNode) {
|
|
249
|
+
endNode = startNode;
|
|
250
|
+
endOffset = startOffset;
|
|
251
|
+
}
|
|
252
|
+
const r = doc.createRange();
|
|
253
|
+
r.setStart(startNode, Math.max(0, Math.min(startOffset, startNode.length)));
|
|
254
|
+
r.setEnd(endNode, Math.max(0, Math.min(endOffset, endNode.length)));
|
|
255
|
+
return r;
|
|
256
|
+
};
|
|
257
|
+
if (params?.start !== undefined || params?.end !== undefined) {
|
|
258
|
+
const s = Math.max(0, params?.start ?? 0);
|
|
259
|
+
const e = Math.max(s, params?.end ?? s);
|
|
260
|
+
const rangeOffsets = params?.indexType === 'source'
|
|
261
|
+
? mapSourceRangeToCharRange(this.getAttribute('content') ?? '', root.textContent ?? '', s, e)
|
|
262
|
+
: { start: s, end: e };
|
|
263
|
+
if (!rangeOffsets)
|
|
264
|
+
return null;
|
|
265
|
+
const r = createRangeByChar(rangeOffsets.start, rangeOffsets.end);
|
|
266
|
+
if (!r)
|
|
267
|
+
return null;
|
|
268
|
+
return { boundingRect: r.getBoundingClientRect() };
|
|
269
|
+
}
|
|
270
|
+
const result = getSelectionForRoot(this, root);
|
|
271
|
+
if (!result)
|
|
272
|
+
return null;
|
|
273
|
+
return { boundingRect: result.range.getBoundingClientRect() };
|
|
274
|
+
}
|
|
275
|
+
setTextSelection(params) {
|
|
276
|
+
const doc = this.ownerDocument;
|
|
277
|
+
const getRangeAtPoint = (x, y) => {
|
|
278
|
+
if (doc.caretRangeFromPoint)
|
|
279
|
+
return doc.caretRangeFromPoint(x, y);
|
|
280
|
+
if (doc.caretPositionFromPoint) {
|
|
281
|
+
const pos = doc.caretPositionFromPoint(x, y);
|
|
282
|
+
if (!pos)
|
|
283
|
+
return null;
|
|
284
|
+
const r = this.ownerDocument.createRange();
|
|
285
|
+
r.setStart(pos.offsetNode, pos.offset);
|
|
286
|
+
r.collapse(true);
|
|
287
|
+
return r;
|
|
288
|
+
}
|
|
289
|
+
return null;
|
|
290
|
+
};
|
|
291
|
+
const r1 = getRangeAtPoint(params.startX, params.startY);
|
|
292
|
+
const r2 = getRangeAtPoint(params.endX, params.endY);
|
|
293
|
+
if (r1 && r2) {
|
|
294
|
+
const sel = getPreferredSelectionTarget(this);
|
|
295
|
+
if (!sel)
|
|
296
|
+
return;
|
|
297
|
+
sel.removeAllRanges();
|
|
298
|
+
const range = this.ownerDocument.createRange();
|
|
299
|
+
range.setStart(r1.startContainer, r1.startOffset);
|
|
300
|
+
range.setEnd(r2.startContainer, r2.startOffset);
|
|
301
|
+
sel.addRange(range);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
getParseResult(params) {
|
|
305
|
+
const root = this.shadowRoot?.querySelector('#markdown-root');
|
|
306
|
+
if (!root)
|
|
307
|
+
return {};
|
|
308
|
+
const text = root.textContent || '';
|
|
309
|
+
const result = {};
|
|
310
|
+
const doc = this.ownerDocument;
|
|
311
|
+
const calcOffset = (node) => {
|
|
312
|
+
const walker = doc.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
313
|
+
let pos = 0;
|
|
314
|
+
let start = -1;
|
|
315
|
+
let end = -1;
|
|
316
|
+
let current = walker.nextNode();
|
|
317
|
+
while (current) {
|
|
318
|
+
const len = (current.nodeValue || '').length;
|
|
319
|
+
if (current === node || node.contains(current)) {
|
|
320
|
+
if (start < 0) {
|
|
321
|
+
start = pos;
|
|
322
|
+
}
|
|
323
|
+
end = pos + len;
|
|
324
|
+
}
|
|
325
|
+
pos += len;
|
|
326
|
+
current = walker.nextNode();
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
start: Math.max(0, Math.min(start, text.length)),
|
|
330
|
+
end: Math.max(0, Math.min(end, text.length)),
|
|
331
|
+
};
|
|
332
|
+
};
|
|
333
|
+
for (const tag of params.tags) {
|
|
334
|
+
const nodes = Array.from(root.querySelectorAll(tag));
|
|
335
|
+
result[tag] = nodes.map((el) => calcOffset(el));
|
|
336
|
+
}
|
|
337
|
+
return result;
|
|
338
|
+
}
|
|
339
|
+
static {
|
|
340
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
return XMarkdown = _classThis;
|
|
344
|
+
})();
|
|
345
|
+
export { XMarkdown };
|
|
346
|
+
//# sourceMappingURL=XMarkdown.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { XMarkdown } from './XMarkdown.js';
|
|
2
|
+
export type MarkdownStyleMap = Record<string, Record<string, unknown>>;
|
|
3
|
+
export type MarkdownStyleConfig = MarkdownStyleMap & {
|
|
4
|
+
truncation?: {
|
|
5
|
+
content?: string;
|
|
6
|
+
truncationType?: 'text' | 'view';
|
|
7
|
+
};
|
|
8
|
+
typewriterCursor?: {
|
|
9
|
+
customCursor?: string;
|
|
10
|
+
verticalAlign?: string;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
export declare const parseMarkdownStyle: (value: string | null | undefined) => MarkdownStyleConfig;
|
|
14
|
+
export declare const serializeMarkdownStyle: (value: string | MarkdownStyleConfig | null | undefined) => string | null;
|
|
15
|
+
export declare class XMarkdownAttributes {
|
|
16
|
+
#private;
|
|
17
|
+
static observedAttributes: string[];
|
|
18
|
+
constructor(dom: XMarkdown);
|
|
19
|
+
connectedCallback(): void;
|
|
20
|
+
dispose(): void;
|
|
21
|
+
_handleContent(newVal: string | null): void;
|
|
22
|
+
_handleMarkdownEffect(newVal: string | null): void;
|
|
23
|
+
_handleMarkdownStyle(newVal: string | null): void;
|
|
24
|
+
_handleContentId(newVal: string | null): void;
|
|
25
|
+
_handleTextSelection(newVal: string | null): void;
|
|
26
|
+
_handleAnimationType(newVal: string | null): void;
|
|
27
|
+
_handleAnimationVelocity(newVal: string | null): void;
|
|
28
|
+
_handleAnimationPaused(newVal: string | null): void;
|
|
29
|
+
_handleInitialAnimationStep(newVal: string | null): void;
|
|
30
|
+
_handleContentComplete(newVal: string | null): void;
|
|
31
|
+
_handleTypewriterDynamicHeight(newVal: string | null): void;
|
|
32
|
+
_handleTypewriterHeightTransition(newVal: string | null): void;
|
|
33
|
+
_handleTextMaxline(newVal: string | null): void;
|
|
34
|
+
_handleContentRange(newVal: string | null): void;
|
|
35
|
+
}
|