@douyinfe/semi-foundation 2.70.1 → 2.71.0-alpha.3
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/cascader/constants.ts +2 -0
- package/cascader/foundation.ts +49 -2
- package/dragMove/foundation.ts +194 -0
- package/highlight/foundation.ts +211 -0
- package/jsonViewer/constants.ts +7 -0
- package/jsonViewer/foundation.ts +72 -0
- package/jsonViewer/jsonViewer.scss +200 -0
- package/jsonViewer/script/build.js +51 -0
- package/jsonViewer/variables.scss +15 -0
- package/lib/cjs/cascader/constants.d.ts +2 -0
- package/lib/cjs/cascader/constants.js +3 -1
- package/lib/cjs/cascader/foundation.d.ts +3 -0
- package/lib/cjs/cascader/foundation.js +55 -3
- package/lib/cjs/dragMove/foundation.d.ts +45 -0
- package/lib/cjs/dragMove/foundation.js +162 -0
- package/lib/cjs/highlight/foundation.d.ts +84 -0
- package/lib/cjs/highlight/foundation.js +184 -0
- package/lib/cjs/jsonViewer/constants.d.ts +4 -0
- package/lib/cjs/jsonViewer/constants.js +10 -0
- package/lib/cjs/jsonViewer/foundation.d.ts +24 -0
- package/lib/cjs/jsonViewer/foundation.js +69 -0
- package/lib/cjs/jsonViewer/jsonViewer.css +168 -0
- package/lib/cjs/jsonViewer/jsonViewer.scss +200 -0
- package/lib/cjs/jsonViewer/variables.scss +15 -0
- package/lib/cjs/lottie/foundation.d.ts +1 -1
- package/lib/cjs/resizable/foundation.d.ts +6 -2
- package/lib/cjs/tree/tree.css +1 -0
- package/lib/cjs/tree/tree.scss +2 -0
- package/lib/es/cascader/constants.d.ts +2 -0
- package/lib/es/cascader/constants.js +3 -1
- package/lib/es/cascader/foundation.d.ts +3 -0
- package/lib/es/cascader/foundation.js +55 -3
- package/lib/es/dragMove/foundation.d.ts +45 -0
- package/lib/es/dragMove/foundation.js +153 -0
- package/lib/es/highlight/foundation.d.ts +84 -0
- package/lib/es/highlight/foundation.js +174 -0
- package/lib/es/jsonViewer/constants.d.ts +4 -0
- package/lib/es/jsonViewer/constants.js +5 -0
- package/lib/es/jsonViewer/foundation.d.ts +24 -0
- package/lib/es/jsonViewer/foundation.js +62 -0
- package/lib/es/jsonViewer/jsonViewer.css +168 -0
- package/lib/es/jsonViewer/jsonViewer.scss +200 -0
- package/lib/es/jsonViewer/variables.scss +15 -0
- package/lib/es/lottie/foundation.d.ts +1 -1
- package/lib/es/resizable/foundation.d.ts +6 -2
- package/lib/es/resizable/foundation.js +3 -2
- package/lib/es/tree/tree.css +1 -0
- package/lib/es/tree/tree.scss +2 -0
- package/lottie/foundation.ts +2 -2
- package/package.json +5 -3
- package/resizable/foundation.ts +19 -5
- package/tree/tree.scss +2 -0
- package/tsconfig.json +1 -1
- package/lib/cjs/utils/getHighlight.d.ts +0 -45
- package/lib/cjs/utils/getHighlight.js +0 -175
- package/lib/es/utils/getHighlight.d.ts +0 -45
- package/lib/es/utils/getHighlight.js +0 -166
- package/utils/getHighlight.ts +0 -178
package/cascader/constants.ts
CHANGED
package/cascader/foundation.ts
CHANGED
|
@@ -168,6 +168,7 @@ export interface BasicCascaderProps {
|
|
|
168
168
|
enableLeafClick?: boolean;
|
|
169
169
|
preventScroll?: boolean;
|
|
170
170
|
virtualizeInSearch?: Virtualize;
|
|
171
|
+
checkRelation?: string;
|
|
171
172
|
onClear?: () => void;
|
|
172
173
|
triggerRender?: (props: BasicTriggerRenderProps) => any;
|
|
173
174
|
onListScroll?: (e: any, panel: BasicScrollPanelProps) => void;
|
|
@@ -591,7 +592,7 @@ export default class CascaderFoundation extends BaseFoundation<CascaderAdapter,
|
|
|
591
592
|
}
|
|
592
593
|
}
|
|
593
594
|
|
|
594
|
-
updateSearching = (isSearching: boolean)=>{
|
|
595
|
+
updateSearching = (isSearching: boolean) => {
|
|
595
596
|
this._adapter.updateStates({ isSearching: false });
|
|
596
597
|
}
|
|
597
598
|
|
|
@@ -772,6 +773,16 @@ export default class CascaderFoundation extends BaseFoundation<CascaderAdapter,
|
|
|
772
773
|
}
|
|
773
774
|
|
|
774
775
|
_handleMultipleSelect(item: BasicEntity | BasicData) {
|
|
776
|
+
const { checkRelation } = this.getProps();
|
|
777
|
+
if (checkRelation === strings.RELATED) {
|
|
778
|
+
this._handleRelatedMultipleSelect(item);
|
|
779
|
+
} else if (checkRelation === 'unRelated') {
|
|
780
|
+
this._handleUnRelatedMultipleSelect(item);
|
|
781
|
+
}
|
|
782
|
+
this._adapter.updateStates({ inputValue: '' });
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
_handleRelatedMultipleSelect(item: BasicEntity | BasicData) {
|
|
775
786
|
const { key } = item;
|
|
776
787
|
const { checkedKeys, keyEntities, resolvedCheckedKeys } = this.getStates();
|
|
777
788
|
const { autoMergeValue, max, disableStrictly, leafOnly } = this.getProps();
|
|
@@ -837,8 +848,44 @@ export default class CascaderFoundation extends BaseFoundation<CascaderAdapter,
|
|
|
837
848
|
if (curCheckedStatus) {
|
|
838
849
|
this._notifySelect(curRealCheckedKeys);
|
|
839
850
|
}
|
|
851
|
+
}
|
|
840
852
|
|
|
841
|
-
|
|
853
|
+
_handleUnRelatedMultipleSelect(item: BasicEntity | BasicData) {
|
|
854
|
+
const { key } = item;
|
|
855
|
+
const { checkedKeys, keyEntities } = this.getStates();
|
|
856
|
+
const { max } = this.getProps();
|
|
857
|
+
const newCheckedKeys: Set<string> = new Set(checkedKeys);
|
|
858
|
+
let targetStatus: boolean;
|
|
859
|
+
const prevCheckedStatus = checkedKeys.has(key);
|
|
860
|
+
if (prevCheckedStatus) {
|
|
861
|
+
newCheckedKeys.delete(key);
|
|
862
|
+
targetStatus = false;
|
|
863
|
+
} else {
|
|
864
|
+
// 查看是否超出 max
|
|
865
|
+
if (isNumber(max)) {
|
|
866
|
+
if (checkedKeys.size >= max) {
|
|
867
|
+
const checkedEntities: BasicEntity[] = [];
|
|
868
|
+
checkedKeys.forEach(itemKey => {
|
|
869
|
+
checkedEntities.push(keyEntities[itemKey]);
|
|
870
|
+
});
|
|
871
|
+
this._adapter.notifyOnExceed(checkedEntities);
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
newCheckedKeys.add(key);
|
|
876
|
+
targetStatus = true;
|
|
877
|
+
}
|
|
878
|
+
if (!this._isControlledComponent()) {
|
|
879
|
+
this._adapter.updateStates({
|
|
880
|
+
checkedKeys: newCheckedKeys,
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
this._notifyChange(newCheckedKeys);
|
|
885
|
+
|
|
886
|
+
if (targetStatus) {
|
|
887
|
+
this._notifySelect(newCheckedKeys);
|
|
888
|
+
}
|
|
842
889
|
}
|
|
843
890
|
|
|
844
891
|
calcNonDisabledCheckedKeys(eventKey: string, targetStatus: boolean) {
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import BaseFoundation, { DefaultAdapter } from '../base/foundation';
|
|
2
|
+
|
|
3
|
+
export function clampValueInRange(value: number, min: number, max: number) {
|
|
4
|
+
return Math.min(Math.max(value, min), max);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface DragMoveAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
|
|
8
|
+
getDragElement: () => HTMLElement;
|
|
9
|
+
getConstrainer: () => HTMLElement | null;
|
|
10
|
+
getHandler: () => HTMLElement;
|
|
11
|
+
notifyMouseDown?: (e: MouseEvent) => void;
|
|
12
|
+
notifyMouseMove?: (e: MouseEvent) => void;
|
|
13
|
+
notifyMouseUp?: (e: MouseEvent) => void;
|
|
14
|
+
notifyTouchStart?: (e: TouchEvent) => void;
|
|
15
|
+
notifyTouchMove?: (e: TouchEvent) => void;
|
|
16
|
+
notifyTouchEnd?: (e: TouchEvent) => void;
|
|
17
|
+
notifyTouchCancel?: (e: TouchEvent) => void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default class DragMoveFoundation<P = Record<string, any>, S = Record<string, any>> extends BaseFoundation<DragMoveAdapter<P, S>, P, S> {
|
|
21
|
+
element: HTMLElement;
|
|
22
|
+
xMax: number;
|
|
23
|
+
xMin: number;
|
|
24
|
+
yMax: number;
|
|
25
|
+
yMin: number;
|
|
26
|
+
startOffsetX: number;
|
|
27
|
+
startOffsetY: number;
|
|
28
|
+
|
|
29
|
+
get constrainer() {
|
|
30
|
+
return this._adapter.getConstrainer();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get handler() {
|
|
34
|
+
return this._adapter.getHandler();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
constructor(adapter: DragMoveAdapter<P, S>) {
|
|
38
|
+
super({ ...adapter });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
init() {
|
|
42
|
+
const element = this._adapter.getDragElement();
|
|
43
|
+
if (!element) {
|
|
44
|
+
throw new Error('drag element must be a valid element');
|
|
45
|
+
}
|
|
46
|
+
this.element = element;
|
|
47
|
+
this.element.style.position = 'absolute';
|
|
48
|
+
this.handler.style.cursor = 'move';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
destroy() {
|
|
52
|
+
this._unRegisterEvent();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
_registerDocMouseEvent = () => {
|
|
56
|
+
document.addEventListener('mousemove', this._onMouseMove);
|
|
57
|
+
document.addEventListener('mouseup', this._onMouseUp);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
_unRegisterDocMouseEvent = () => {
|
|
61
|
+
document.removeEventListener('mousemove', this._onMouseMove);
|
|
62
|
+
document.removeEventListener('mouseup', this._onMouseUp);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
_registerDocTouchEvent = () => {
|
|
66
|
+
document.addEventListener('touchend', this._onTouchEnd);
|
|
67
|
+
document.addEventListener('touchmove', this._onTouchMove);
|
|
68
|
+
document.addEventListener('touchcancel', this._onTouchCancel);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
_unRegisterDocTouchEvent = () => {
|
|
72
|
+
document.removeEventListener('touchend', this._onTouchEnd);
|
|
73
|
+
document.removeEventListener('touchmove', this._onTouchMove);
|
|
74
|
+
document.removeEventListener('touchcancel', this._onTouchCancel);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
_unRegisterEvent() {
|
|
78
|
+
this._unRegisterDocMouseEvent();
|
|
79
|
+
|
|
80
|
+
this._unRegisterDocTouchEvent();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
_calcMoveRange() {
|
|
84
|
+
// Calculate the range within which an element can move
|
|
85
|
+
if (this.constrainer) {
|
|
86
|
+
let node = this.element.offsetParent as HTMLElement;
|
|
87
|
+
let startX = 0;
|
|
88
|
+
let startY = 0;
|
|
89
|
+
while (node !== this.constrainer && node !== null) {
|
|
90
|
+
startX -= node.offsetLeft;
|
|
91
|
+
startY -= node.offsetTop;
|
|
92
|
+
node = node.offsetParent as any;
|
|
93
|
+
}
|
|
94
|
+
this.xMin = startX;
|
|
95
|
+
this.xMax = startX + this.constrainer.offsetWidth - this.element.offsetWidth;
|
|
96
|
+
this.yMin = startY;
|
|
97
|
+
this.yMax = startY + this.constrainer.offsetHeight - this.element.offsetHeight;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
_allowMove(e: MouseEvent | TouchEvent) {
|
|
102
|
+
const { allowMove, allowInputDrag } = this.getProps();
|
|
103
|
+
// When the clicked object is an input or textarea, clicking should be allowed but dragging should not be allowed.
|
|
104
|
+
if (!allowInputDrag) {
|
|
105
|
+
let target = (e.target as HTMLElement).tagName.toLowerCase();
|
|
106
|
+
if (target === 'input' || target === 'textarea') {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (allowMove) {
|
|
111
|
+
return allowMove(e, this.element);
|
|
112
|
+
}
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
_calcOffset = (e: Touch | MouseEvent) => {
|
|
117
|
+
this.startOffsetX = e.clientX - this.element.offsetLeft;
|
|
118
|
+
this.startOffsetY = e.clientY - this.element.offsetTop;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
_preventDefault = (e: MouseEvent | TouchEvent) => {
|
|
122
|
+
// prevent default behavior, avoid other element(like img, text) be selected
|
|
123
|
+
e.preventDefault();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
onMouseDown = (e: MouseEvent) => {
|
|
127
|
+
this._calcMoveRange();
|
|
128
|
+
this._adapter.notifyMouseDown(e);
|
|
129
|
+
if (!this._allowMove(e)) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
this._registerDocMouseEvent();
|
|
133
|
+
// store origin offset
|
|
134
|
+
this._calcOffset(e);
|
|
135
|
+
this._preventDefault(e);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
onTouchStart = (e: TouchEvent) => {
|
|
139
|
+
this._calcMoveRange();
|
|
140
|
+
this._adapter.notifyTouchStart(e);
|
|
141
|
+
if (!this._allowMove(e)) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
this._registerDocTouchEvent();
|
|
145
|
+
const touch = e.targetTouches[0];
|
|
146
|
+
this._calcOffset(touch);
|
|
147
|
+
this._preventDefault(e);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
_changePos = (e: Touch | MouseEvent) => {
|
|
151
|
+
const { customMove } = this.getProps();
|
|
152
|
+
let newLeft = e.clientX - this.startOffsetX;
|
|
153
|
+
let newTop = e.clientY - this.startOffsetY;
|
|
154
|
+
if (this.constrainer) {
|
|
155
|
+
newLeft = clampValueInRange(newLeft, this.xMin, this.xMax);
|
|
156
|
+
newTop = clampValueInRange(newTop, this.yMin, this.yMax);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
requestAnimationFrame(() => {
|
|
160
|
+
if (customMove) {
|
|
161
|
+
customMove(this.element, newTop, newLeft);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
this.element.style.top = newTop + 'px';
|
|
165
|
+
this.element.style.left = newLeft + 'px';
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
_onMouseMove = (e: MouseEvent) => {
|
|
170
|
+
this._adapter.notifyMouseMove(e);
|
|
171
|
+
this._changePos(e);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
_onTouchMove = (e: TouchEvent) => {
|
|
175
|
+
this._adapter.notifyTouchMove(e);
|
|
176
|
+
const touch = e.targetTouches[0];
|
|
177
|
+
this._changePos(touch);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
_onMouseUp = (e: MouseEvent) => {
|
|
181
|
+
this._adapter.notifyMouseUp(e);
|
|
182
|
+
this._unRegisterDocMouseEvent();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
_onTouchEnd = (e: TouchEvent) => {
|
|
186
|
+
this._adapter.notifyTouchEnd(e);
|
|
187
|
+
this._unRegisterDocTouchEvent();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
_onTouchCancel = (e: TouchEvent) => {
|
|
191
|
+
this._adapter.notifyTouchCancel(e);
|
|
192
|
+
this._unRegisterDocTouchEvent();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
// Modified version based on 'highlight-words-core'
|
|
2
|
+
import { isString } from 'lodash';
|
|
3
|
+
import BaseFoundation, { DefaultAdapter } from '../base/foundation';
|
|
4
|
+
|
|
5
|
+
interface HighlightAdapter extends Partial<DefaultAdapter> {}
|
|
6
|
+
|
|
7
|
+
interface ChunkQuery {
|
|
8
|
+
autoEscape?: boolean;
|
|
9
|
+
caseSensitive?: boolean;
|
|
10
|
+
searchWords: SearchWords;
|
|
11
|
+
sourceString: string
|
|
12
|
+
}
|
|
13
|
+
export interface Chunk {
|
|
14
|
+
start: number;
|
|
15
|
+
end: number;
|
|
16
|
+
highlight: boolean;
|
|
17
|
+
className: string;
|
|
18
|
+
style: Record<string, string>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ComplexSearchWord {
|
|
22
|
+
text: string;
|
|
23
|
+
className?: string;
|
|
24
|
+
style?: Record<string, string>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type SearchWord = string | ComplexSearchWord | undefined;
|
|
28
|
+
export type SearchWords = SearchWord[];
|
|
29
|
+
|
|
30
|
+
const escapeRegExpFn = (string: string) => string.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
|
|
31
|
+
|
|
32
|
+
export default class HighlightFoundation extends BaseFoundation<HighlightAdapter> {
|
|
33
|
+
|
|
34
|
+
constructor(adapter?: HighlightAdapter) {
|
|
35
|
+
super({
|
|
36
|
+
...adapter,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Creates an array of chunk objects representing both higlightable and non highlightable pieces of text that match each search word.
|
|
42
|
+
*
|
|
43
|
+
findAll ['z'], 'aaazaaazaaa'
|
|
44
|
+
result #=> [
|
|
45
|
+
{ start: 0, end: 3, highlight: false }
|
|
46
|
+
{ start: 3, end: 4, highlight: true }
|
|
47
|
+
{ start: 4, end: 7, highlight: false }
|
|
48
|
+
{ start: 7, end: 8, highlight: true }
|
|
49
|
+
{ start: 8, end: 11, highlight: false }
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
findAll ['do', 'dollar'], 'aaa do dollar aaa'
|
|
53
|
+
#=> chunks: [
|
|
54
|
+
{ start: 4, end: 6 },
|
|
55
|
+
{ start: 7, end: 9 },
|
|
56
|
+
{ start: 7, end: 13 },
|
|
57
|
+
]
|
|
58
|
+
#=> chunksToHight: [
|
|
59
|
+
{ start: 4, end: 6 },
|
|
60
|
+
{ start: 7, end: 13 },
|
|
61
|
+
]
|
|
62
|
+
#=> result: [
|
|
63
|
+
{ start: 0, end: 4, highlight: false },
|
|
64
|
+
{ start: 4, end: 6, highlight: true },
|
|
65
|
+
{ start: 6, end: 7, highlight: false },
|
|
66
|
+
{ start: 7, end: 13, highlight: true },
|
|
67
|
+
{ start: 13, end: 17, highlight: false },
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
* @return Array of "chunks" (where a Chunk is { start:number, end:number, highlight:boolean })
|
|
71
|
+
*/
|
|
72
|
+
findAll = ({
|
|
73
|
+
autoEscape = true,
|
|
74
|
+
caseSensitive = false,
|
|
75
|
+
searchWords,
|
|
76
|
+
sourceString
|
|
77
|
+
}: ChunkQuery) => {
|
|
78
|
+
if (isString(searchWords)) {
|
|
79
|
+
searchWords = [searchWords];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const chunks = this.findChunks({
|
|
83
|
+
autoEscape,
|
|
84
|
+
caseSensitive,
|
|
85
|
+
searchWords,
|
|
86
|
+
sourceString
|
|
87
|
+
});
|
|
88
|
+
const chunksToHighlight = this.combineChunks({ chunks });
|
|
89
|
+
const result = this.fillInChunks({
|
|
90
|
+
chunksToHighlight,
|
|
91
|
+
totalLength: sourceString ? sourceString.length : 0
|
|
92
|
+
});
|
|
93
|
+
return result;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Examine text for any matches.
|
|
98
|
+
* If we find matches, add them to the returned array as a "chunk" object ({start:number, end:number}).
|
|
99
|
+
* @return { start:number, end:number }[]
|
|
100
|
+
*/
|
|
101
|
+
findChunks = ({
|
|
102
|
+
autoEscape,
|
|
103
|
+
caseSensitive,
|
|
104
|
+
searchWords,
|
|
105
|
+
sourceString
|
|
106
|
+
}: ChunkQuery): Chunk[] => (
|
|
107
|
+
searchWords
|
|
108
|
+
.map(searchWord => typeof searchWord === 'string' ? { text: searchWord } : searchWord)
|
|
109
|
+
.filter(searchWord => searchWord.text) // Remove empty words
|
|
110
|
+
.reduce((chunks, searchWord) => {
|
|
111
|
+
let searchText = searchWord.text;
|
|
112
|
+
if (autoEscape) {
|
|
113
|
+
searchText = escapeRegExpFn(searchText);
|
|
114
|
+
}
|
|
115
|
+
const regex = new RegExp(searchText, caseSensitive ? 'g' : 'gi');
|
|
116
|
+
|
|
117
|
+
let match;
|
|
118
|
+
while ((match = regex.exec(sourceString))) {
|
|
119
|
+
const start = match.index;
|
|
120
|
+
const end = regex.lastIndex;
|
|
121
|
+
if (end > start) {
|
|
122
|
+
chunks.push({
|
|
123
|
+
highlight: true,
|
|
124
|
+
start,
|
|
125
|
+
end,
|
|
126
|
+
className: searchWord.className,
|
|
127
|
+
style: searchWord.style
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
if (match.index === regex.lastIndex) {
|
|
131
|
+
regex.lastIndex++;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return chunks;
|
|
135
|
+
}, [])
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Takes an array of {start:number, end:number} objects and combines chunks that overlap into single chunks.
|
|
140
|
+
* @return {start:number, end:number}[]
|
|
141
|
+
*/
|
|
142
|
+
combineChunks = ({ chunks }: { chunks: Chunk[] }): Chunk[] => {
|
|
143
|
+
return chunks
|
|
144
|
+
.sort((first, second) => first.start - second.start)
|
|
145
|
+
.reduce((processedChunks, nextChunk) => {
|
|
146
|
+
// First chunk just goes straight in the array...
|
|
147
|
+
if (processedChunks.length === 0) {
|
|
148
|
+
return [nextChunk];
|
|
149
|
+
} else {
|
|
150
|
+
// ... subsequent chunks get checked to see if they overlap...
|
|
151
|
+
const prevChunk = processedChunks.pop();
|
|
152
|
+
if (nextChunk.start <= prevChunk.end) {
|
|
153
|
+
// It may be the case that prevChunk completely surrounds nextChunk, so take the
|
|
154
|
+
// largest of the end indeces.
|
|
155
|
+
const endIndex = Math.max(prevChunk.end, nextChunk.end);
|
|
156
|
+
processedChunks.push({
|
|
157
|
+
highlight: true,
|
|
158
|
+
start: prevChunk.start,
|
|
159
|
+
end: endIndex,
|
|
160
|
+
className: prevChunk.className || nextChunk.className,
|
|
161
|
+
style: { ...prevChunk.style, ...nextChunk.style }
|
|
162
|
+
});
|
|
163
|
+
} else {
|
|
164
|
+
processedChunks.push(prevChunk, nextChunk);
|
|
165
|
+
}
|
|
166
|
+
return processedChunks;
|
|
167
|
+
}
|
|
168
|
+
}, []);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Given a set of chunks to highlight, create an additional set of chunks
|
|
173
|
+
* to represent the bits of text between the highlighted text.
|
|
174
|
+
* @param chunksToHighlight {start:number, end:number}[]
|
|
175
|
+
* @param totalLength number
|
|
176
|
+
* @return {start:number, end:number, highlight:boolean}[]
|
|
177
|
+
*/
|
|
178
|
+
fillInChunks = ({ chunksToHighlight, totalLength }: { chunksToHighlight: Chunk[]; totalLength: number }): Chunk[] => {
|
|
179
|
+
const allChunks: Chunk[] = [];
|
|
180
|
+
const append = (start: number, end: number, highlight: boolean, className?: string, style?: Record<string, string>) => {
|
|
181
|
+
if (end - start > 0) {
|
|
182
|
+
allChunks.push({
|
|
183
|
+
start,
|
|
184
|
+
end,
|
|
185
|
+
highlight,
|
|
186
|
+
className,
|
|
187
|
+
style
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
if (chunksToHighlight.length === 0) {
|
|
193
|
+
append(0, totalLength, false);
|
|
194
|
+
} else {
|
|
195
|
+
let lastIndex = 0;
|
|
196
|
+
chunksToHighlight.forEach(chunk => {
|
|
197
|
+
append(lastIndex, chunk.start, false);
|
|
198
|
+
append(chunk.start, chunk.end, true, chunk.className, chunk.style);
|
|
199
|
+
lastIndex = chunk.end;
|
|
200
|
+
});
|
|
201
|
+
append(lastIndex, totalLength, false);
|
|
202
|
+
}
|
|
203
|
+
return allChunks;
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
|
|
2
|
+
import { JsonViewer, JsonViewerOptions } from '@douyinfe/semi-json-viewer-core';
|
|
3
|
+
import BaseFoundation, { DefaultAdapter, noopFunction } from '../base/foundation';
|
|
4
|
+
|
|
5
|
+
export type { JsonViewerOptions };
|
|
6
|
+
export interface JsonViewerAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
|
|
7
|
+
getEditorRef: () => HTMLElement;
|
|
8
|
+
getSearchRef: () => HTMLInputElement;
|
|
9
|
+
notifyChange: (value: string) => void;
|
|
10
|
+
notifyHover: (value: string, el: HTMLElement) => HTMLElement | undefined;
|
|
11
|
+
setSearchOptions: (key: string) => void;
|
|
12
|
+
showSearchBar: () => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class JsonViewerFoundation extends BaseFoundation<JsonViewerAdapter> {
|
|
16
|
+
constructor(adapter: JsonViewerAdapter) {
|
|
17
|
+
super({ ...JsonViewerFoundation, ...adapter });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
jsonViewer: JsonViewer | null = null;
|
|
21
|
+
|
|
22
|
+
init() {
|
|
23
|
+
const props = this.getProps();
|
|
24
|
+
const editorRef = this._adapter.getEditorRef();
|
|
25
|
+
this.jsonViewer = new JsonViewer(editorRef, props.value, props.options);
|
|
26
|
+
this.jsonViewer.layout();
|
|
27
|
+
this.jsonViewer.emitter.on('contentChanged', (e) => {
|
|
28
|
+
this._adapter.notifyChange(this.jsonViewer?.getModel().getValue());
|
|
29
|
+
if (this.getState('showSearchBar')) {
|
|
30
|
+
this.search(this._adapter.getSearchRef().value);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
this.jsonViewer.emitter.on('hoverNode', (e) => {
|
|
34
|
+
const el = this._adapter.notifyHover(e.value, e.target);
|
|
35
|
+
if (el) {
|
|
36
|
+
this.jsonViewer.emitter.emit('renderHoverNode', { el });
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
search(searchText: string) {
|
|
42
|
+
const state = this.getState('searchOptions');
|
|
43
|
+
const { caseSensitive, wholeWord, regex } = state;
|
|
44
|
+
this.jsonViewer?.getSearchWidget().search(searchText, caseSensitive, wholeWord, regex);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
prevSearch() {
|
|
48
|
+
this.jsonViewer?.getSearchWidget().navigateResults(-1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
nextSearch() {
|
|
52
|
+
this.jsonViewer?.getSearchWidget().navigateResults(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
replace(replaceText: string) {
|
|
56
|
+
this.jsonViewer?.getSearchWidget().replace(replaceText);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
replaceAll(replaceText: string) {
|
|
60
|
+
this.jsonViewer?.getSearchWidget().replaceAll(replaceText);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
setSearchOptions(key: string) {
|
|
64
|
+
this._adapter.setSearchOptions(key);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
showSearchBar() {
|
|
68
|
+
this._adapter.showSearchBar();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export default JsonViewerFoundation;
|