UNPKG

4.38 kBPlain TextView Raw
1import xs from 'xstream';
2import {Stream, MemoryStream} from 'xstream';
3import {DevToolEnabledSource} from '@cycle/run';
4import {adapt} from '@cycle/run/lib/adapt';
5import {DOMSource, EventsFnOptions} from './DOMSource';
6import {DocumentDOMSource} from './DocumentDOMSource';
7import {BodyDOMSource} from './BodyDOMSource';
8import {VNode} from 'snabbdom';
9import {ElementFinder} from './ElementFinder';
10import {makeIsolateSink, getScopeObj, Scope, IsolateSink} from './isolate';
11import {IsolateModule} from './IsolateModule';
12import {EventDelegator} from './EventDelegator';
13
14export interface SpecialSelector {
15 body: BodyDOMSource;
16 document: DocumentDOMSource;
17}
18
19export 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 //this._isolateModule.reset();
139 }
140
141 // The implementation of these are in the constructor so that their `this`
142 // references are automatically bound to the instance, so that library users
143 // can do destructuring `const {isolateSource, isolateSink} = sources.DOM` and
144 // not get bitten by a missing `this` reference.
145
146 public isolateSource: (source: MainDOMSource, scope: string) => MainDOMSource;
147 public isolateSink: IsolateSink<VNode>;
148}