This repository has been archived by the owner on Sep 16, 2023. It is now read-only.
/
charger.target.ts
132 lines (117 loc) · 3.85 KB
/
charger.target.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import { DrekContentStatus } from '../content-status';
import { DrekPlacement } from '../placement';
import { DrekTarget } from './target';
/**
* Creates a rendering target that charges rendered content prior to placing it to another target.
*
* @typeParam TStatus - A tuple type reflecting a content {@link DrekContentStatus placement status}.
* @param target - Rendering target of charged content.
* @param spec - Content charging options.
*
* @returns Rendering target.
*/
export function drekCharger<TStatus extends [DrekContentStatus] = [DrekContentStatus]>(
target: DrekTarget<TStatus>,
spec?: DrekCharger.Spec<TStatus>,
): DrekTarget {
const charger = DrekCharger$custom(target, spec);
return {
context: target.context,
host: target.host,
placeContent(content: Node): DrekPlacement {
return charger.charge(content, target);
},
};
}
export namespace DrekCharger {
/**
* Rendered content charger specifier.
*
* Can be one of:
*
* - An arbitrary string containing a text for enclosing comments.
* - A {@link Custom custom charger}.
* - A {@link Factory charger factory} function.
* - `null`/`undefined` to enclose the rendered contents in comments with random text.
*
* @typeParam TStatus - A tuple type reflecting a content {@link DrekContentStatus placement status}.
*/
export type Spec<TStatus extends [DrekContentStatus] = [DrekContentStatus]> =
| string
| Custom
| Factory<TStatus>
| null
| undefined;
/**
* Custom rendered content charger.
*/
export interface Custom {
/**
* Charges rendered content by representing it as another DOM node.
*
* @typeParam TStatus - A tuple type reflecting a content {@link DrekContentStatus placement status}.
* @param content - Rendered content to charge.
* @param target - Rendering target to place the charged content to.
*
* @returns Charged content placement status.
*/
charge<TStatus extends [DrekContentStatus]>(
content: Node,
target: DrekTarget<TStatus>,
): DrekPlacement<TStatus>;
}
/**
* Rendered content charger factory signature.
*
* @typeParam TStatus - A tuple type reflecting a content {@link DrekContentStatus placement status}.
*/
export type Factory<TStatus extends [DrekContentStatus] = [DrekContentStatus]> =
/**
* @param target - A target to render the charged content to.
*
* @returns Rendered content charger specifier.
*/
(this: void, target: DrekTarget<TStatus>) => Spec;
}
function DrekCharger$custom<TStatus extends [DrekContentStatus]>(
target: DrekTarget<TStatus>,
spec: DrekCharger.Spec<TStatus>,
): DrekCharger.Custom {
if (typeof spec === 'function') {
return DrekCharger$custom(target, spec(target));
}
if (typeof spec === 'string') {
return DrekCharger$commentWrapper(target, spec);
}
if (spec) {
return spec;
}
return DrekCharger$commentWrapper(target, Math.random().toString(32).substr(2));
}
function DrekCharger$commentWrapper(
{ context: { document } }: DrekTarget,
rem: string,
): DrekCharger.Custom {
let wrapContent = <TStatus extends [DrekContentStatus]>(
content: Node,
target: DrekTarget<TStatus>,
): DrekPlacement<TStatus> => {
const start = document.createComment(` [[ ${rem} [[ `);
const end = document.createComment(` ]] ${rem} ]] `);
let placement: DrekPlacement<TStatus>;
wrapContent = (content, _target) => {
const range = document.createRange();
range.setStartAfter(start);
range.setEndBefore(end);
range.deleteContents();
range.insertNode(content);
return placement;
};
const fragment = document.createDocumentFragment();
fragment.append(start, content, end);
return (placement = target.placeContent(fragment));
};
return {
charge: (content, target) => wrapContent(content, target),
};
}