1 | import xs from 'xstream';
|
2 | import {Stream, MemoryStream} from 'xstream';
|
3 | import {DevToolEnabledSource} from '@cycle/run';
|
4 | import {adapt} from '@cycle/run/lib/adapt';
|
5 | import {DOMSource, EventsFnOptions} from './DOMSource';
|
6 | import {DocumentDOMSource} from './DocumentDOMSource';
|
7 | import {BodyDOMSource} from './BodyDOMSource';
|
8 | import {VNode} from 'snabbdom/vnode';
|
9 | import {ElementFinder} from './ElementFinder';
|
10 | import {makeIsolateSink, getScopeObj, Scope, IsolateSink} from './isolate';
|
11 | import {IsolateModule} from './IsolateModule';
|
12 | import {EventDelegator} from './EventDelegator';
|
13 |
|
14 | export interface SpecialSelector {
|
15 | body: BodyDOMSource;
|
16 | document: DocumentDOMSource;
|
17 | }
|
18 |
|
19 | export class MainDOMSource {
|
20 | constructor(
|
21 | private _rootElement$: Stream<Element>,
|
22 | private _sanitation$: Stream<null>,
|
23 | private _namespace: Array<Scope> = [],
|
24 | public _isolateModule: IsolateModule,
|
25 | private _eventDelegator: EventDelegator,
|
26 | private _name: string
|
27 | ) {
|
28 | this.isolateSource = (source, scope) =>
|
29 | new MainDOMSource(
|
30 | source._rootElement$,
|
31 | source._sanitation$,
|
32 | source._namespace.concat(getScopeObj(scope)),
|
33 | source._isolateModule,
|
34 | source._eventDelegator,
|
35 | source._name
|
36 | );
|
37 | this.isolateSink = makeIsolateSink(this._namespace) as any;
|
38 | }
|
39 |
|
40 | private _elements(): Stream<Array<Element>> {
|
41 | if (this._namespace.length === 0) {
|
42 | return this._rootElement$.map(x => [x]);
|
43 | } else {
|
44 | const elementFinder = new ElementFinder(
|
45 | this._namespace,
|
46 | this._isolateModule
|
47 | );
|
48 | return this._rootElement$.map(() => elementFinder.call());
|
49 | }
|
50 | }
|
51 |
|
52 | public elements(): MemoryStream<Array<Element>> {
|
53 | const out: DevToolEnabledSource & MemoryStream<Array<Element>> = adapt(
|
54 | this._elements().remember()
|
55 | );
|
56 | out._isCycleSource = this._name;
|
57 | return out;
|
58 | }
|
59 |
|
60 | public element(): MemoryStream<Element> {
|
61 | const out: DevToolEnabledSource & MemoryStream<Element> = adapt(
|
62 | this._elements()
|
63 | .filter(arr => arr.length > 0)
|
64 | .map(arr => arr[0])
|
65 | .remember()
|
66 | );
|
67 | out._isCycleSource = this._name;
|
68 | return out;
|
69 | }
|
70 |
|
71 | get namespace(): Array<Scope> {
|
72 | return this._namespace;
|
73 | }
|
74 |
|
75 | public select<T extends keyof SpecialSelector>(
|
76 | selector: T
|
77 | ): SpecialSelector[T];
|
78 | public select(selector: string): MainDOMSource;
|
79 | public select(selector: string): DOMSource {
|
80 | if (typeof selector !== 'string') {
|
81 | throw new Error(
|
82 | `DOM driver's select() expects the argument to be a ` +
|
83 | `string as a CSS selector`
|
84 | );
|
85 | }
|
86 | if (selector === 'document') {
|
87 | return new DocumentDOMSource(this._name);
|
88 | }
|
89 | if (selector === 'body') {
|
90 | return new BodyDOMSource(this._name);
|
91 | }
|
92 |
|
93 | const namespace =
|
94 | selector === ':root'
|
95 | ? []
|
96 | : this._namespace.concat({type: 'selector', scope: selector.trim()});
|
97 |
|
98 | return new MainDOMSource(
|
99 | this._rootElement$,
|
100 | this._sanitation$,
|
101 | namespace,
|
102 | this._isolateModule,
|
103 | this._eventDelegator,
|
104 | this._name
|
105 | ) as DOMSource;
|
106 | }
|
107 |
|
108 | public events<K extends keyof HTMLElementEventMap>(
|
109 | eventType: K,
|
110 | options?: EventsFnOptions,
|
111 | bubbles?: boolean
|
112 | ): Stream<HTMLElementEventMap[K]>;
|
113 | public events(
|
114 | eventType: string,
|
115 | options: EventsFnOptions = {},
|
116 | bubbles?: boolean
|
117 | ): Stream<Event> {
|
118 | if (typeof eventType !== `string`) {
|
119 | throw new Error(
|
120 | `DOM driver's events() expects argument to be a ` +
|
121 | `string representing the event type to listen for.`
|
122 | );
|
123 | }
|
124 | const event$: Stream<Event> = this._eventDelegator.addEventListener(
|
125 | eventType,
|
126 | this._namespace,
|
127 | options,
|
128 | bubbles
|
129 | );
|
130 |
|
131 | const out: DevToolEnabledSource & Stream<Event> = adapt(event$);
|
132 | out._isCycleSource = this._name;
|
133 | return out;
|
134 | }
|
135 |
|
136 | public dispose(): void {
|
137 | this._sanitation$.shamefullySendNext(null);
|
138 |
|
139 | }
|
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 | public isolateSource: (source: MainDOMSource, scope: string) => MainDOMSource;
|
147 | public isolateSink: IsolateSink<VNode>;
|
148 | }
|