sygnal 2.4.0 → 2.5.0
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/dist/index.cjs.js +1351 -0
- package/dist/index.esm.js +1300 -0
- package/dist/jsx.cjs.js +187 -0
- package/dist/jsx.esm.js +175 -0
- package/dist/sygnal.min.js +1 -0
- package/package.json +22 -12
- package/.babelrc +0 -6
- package/dist/collection.js +0 -104
- package/dist/component.js +0 -1324
- package/dist/extra/classes.js +0 -118
- package/dist/extra/eventDriver.js +0 -38
- package/dist/extra/logDriver.js +0 -9
- package/dist/extra/processForm.js +0 -71
- package/dist/extra/run.js +0 -35
- package/dist/index.js +0 -17
- package/dist/jsx.js +0 -2
- package/dist/pragma/fn.js +0 -66
- package/dist/pragma/index.js +0 -138
- package/dist/pragma/is.js +0 -44
- package/dist/switchable.js +0 -148
|
@@ -0,0 +1,1351 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var isolate = require('@cycle/isolate');
|
|
6
|
+
var state = require('@cycle/state');
|
|
7
|
+
var xs = require('xstream');
|
|
8
|
+
var dropRepeats = require('xstream/extra/dropRepeats');
|
|
9
|
+
var delay$1 = require('xstream/extra/delay.js');
|
|
10
|
+
var concat = require('xstream/extra/concat.js');
|
|
11
|
+
var debounce$1 = require('xstream/extra/debounce.js');
|
|
12
|
+
var dropRepeats$1 = require('xstream/extra/dropRepeats.js');
|
|
13
|
+
var run$1 = require('@cycle/run');
|
|
14
|
+
var dom = require('@cycle/dom');
|
|
15
|
+
var adapt = require('@cycle/run/lib/adapt');
|
|
16
|
+
var debounce = require('xstream/extra/debounce');
|
|
17
|
+
var throttle = require('xstream/extra/throttle');
|
|
18
|
+
var delay = require('xstream/extra/delay');
|
|
19
|
+
var sampleCombine = require('xstream/extra/sampleCombine');
|
|
20
|
+
|
|
21
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
22
|
+
|
|
23
|
+
var isolate__default = /*#__PURE__*/_interopDefaultLegacy(isolate);
|
|
24
|
+
var xs__default = /*#__PURE__*/_interopDefaultLegacy(xs);
|
|
25
|
+
var dropRepeats__default = /*#__PURE__*/_interopDefaultLegacy(dropRepeats);
|
|
26
|
+
var delay__default$1 = /*#__PURE__*/_interopDefaultLegacy(delay$1);
|
|
27
|
+
var concat__default = /*#__PURE__*/_interopDefaultLegacy(concat);
|
|
28
|
+
var debounce__default$1 = /*#__PURE__*/_interopDefaultLegacy(debounce$1);
|
|
29
|
+
var dropRepeats__default$1 = /*#__PURE__*/_interopDefaultLegacy(dropRepeats$1);
|
|
30
|
+
var debounce__default = /*#__PURE__*/_interopDefaultLegacy(debounce);
|
|
31
|
+
var throttle__default = /*#__PURE__*/_interopDefaultLegacy(throttle);
|
|
32
|
+
var delay__default = /*#__PURE__*/_interopDefaultLegacy(delay);
|
|
33
|
+
var sampleCombine__default = /*#__PURE__*/_interopDefaultLegacy(sampleCombine);
|
|
34
|
+
|
|
35
|
+
function collection(component, stateLense, opts={}) {
|
|
36
|
+
const {
|
|
37
|
+
combineList = ['DOM'],
|
|
38
|
+
globalList = ['EVENTS'],
|
|
39
|
+
stateSourceName = 'STATE',
|
|
40
|
+
domSourceName = 'DOM',
|
|
41
|
+
container = 'div',
|
|
42
|
+
containerClass
|
|
43
|
+
} = opts;
|
|
44
|
+
|
|
45
|
+
return (sources) => {
|
|
46
|
+
const key = Date.now();
|
|
47
|
+
const collectionOpts = {
|
|
48
|
+
item: component,
|
|
49
|
+
itemKey: (state, ind) => typeof state.id !== 'undefined' ? state.id : ind,
|
|
50
|
+
itemScope: key => key,
|
|
51
|
+
channel: stateSourceName,
|
|
52
|
+
collectSinks: instances => {
|
|
53
|
+
return Object.entries(sources).reduce((acc, [name, stream]) => {
|
|
54
|
+
if (combineList.includes(name)) {
|
|
55
|
+
const combined = instances.pickCombine(name);
|
|
56
|
+
if (name === domSourceName && container) {
|
|
57
|
+
acc.DOM = combined.map(children => {
|
|
58
|
+
const data = (containerClass) ? { props: { className: containerClass } } : {};
|
|
59
|
+
return { sel: container, data, children, key, text: undefined, elm: undefined}
|
|
60
|
+
});
|
|
61
|
+
} else {
|
|
62
|
+
console.warn('Collections without wrapping containers will fail in unpredictable ways when used inside JSX fragments');
|
|
63
|
+
acc[name] = combined;
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
acc[name] = instances.pickMerge(name);
|
|
67
|
+
}
|
|
68
|
+
return acc
|
|
69
|
+
}, {})
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const isolateOpts = {[stateSourceName]: stateLense};
|
|
74
|
+
|
|
75
|
+
globalList.forEach(global => isolateOpts[global] = null);
|
|
76
|
+
combineList.forEach(combine => isolateOpts[combine] = null);
|
|
77
|
+
|
|
78
|
+
return makeIsolatedCollection(collectionOpts, isolateOpts, sources)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* instantiate a cycle collection and isolate
|
|
84
|
+
* (makes the code for doing isolated collections more readable)
|
|
85
|
+
*
|
|
86
|
+
* @param {Object} collectionOpts options for the makeCollection function (see cycle/state documentation)
|
|
87
|
+
* @param {String|Object} isolateOpts options for the isolate function (see cycle/isolate documentation)
|
|
88
|
+
* @param {Object} sources object of cycle style sources to use for the created collection
|
|
89
|
+
* @return {Object} collection of component sinks
|
|
90
|
+
*/
|
|
91
|
+
function makeIsolatedCollection (collectionOpts, isolateOpts, sources) {
|
|
92
|
+
return isolate__default["default"](state.makeCollection(collectionOpts), isolateOpts)(sources)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function switchable(factories, name$, initial, opts={}) {
|
|
96
|
+
const {
|
|
97
|
+
switched=['DOM'],
|
|
98
|
+
stateSourceName='STATE'
|
|
99
|
+
} = opts;
|
|
100
|
+
const nameType = typeof name$;
|
|
101
|
+
|
|
102
|
+
if (!name$) throw new Error(`Missing 'name$' parameter for switchable()`)
|
|
103
|
+
if (!(nameType === 'string' || nameType === 'function' || name$ instanceof xs.Stream)) {
|
|
104
|
+
throw new Error(`Invalid 'name$' parameter for switchable(): expects Stream, String, or Function`)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (name$ instanceof xs.Stream) {
|
|
108
|
+
const withInitial$ = name$
|
|
109
|
+
.compose(dropRepeats__default["default"]())
|
|
110
|
+
.startWith(initial)
|
|
111
|
+
.remember();
|
|
112
|
+
return sources => _switchable(factories, sources, withInitial$, switched)
|
|
113
|
+
} else {
|
|
114
|
+
const mapFunction = (nameType === 'function' && name$) || (state => state[name$]);
|
|
115
|
+
return sources => {
|
|
116
|
+
const state$ = sources && ((typeof stateSourceName === 'string' && sources[stateSourceName]) || sources.STATE || sources.state).stream;
|
|
117
|
+
if (!state$ instanceof xs.Stream) throw new Error(`Could not find the state source: ${ stateSourceName }`)
|
|
118
|
+
const _name$ = state$
|
|
119
|
+
.map(mapFunction)
|
|
120
|
+
.filter(name => typeof name === 'string')
|
|
121
|
+
.compose(dropRepeats__default["default"]())
|
|
122
|
+
.startWith(initial)
|
|
123
|
+
.remember();
|
|
124
|
+
return _switchable(factories, sources, _name$, switched, stateSourceName)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* create a group of components which can be switched between based on a stream of component names
|
|
133
|
+
*
|
|
134
|
+
* @param {Object} factories maps names to component creation functions
|
|
135
|
+
* @param {Object} sources standard cycle sources object provided to each component
|
|
136
|
+
* @param {Observable} name$ stream of names corresponding to the component names
|
|
137
|
+
* @param {Array} switched which cycle sinks from the components should be `switched` when a new `name$` is emitted
|
|
138
|
+
* @return {Object} cycle sinks object where the selected sinks are switched to the last component name emitted to `name$`
|
|
139
|
+
*
|
|
140
|
+
* any component sinks not dsignated in `switched` will be merged across all components
|
|
141
|
+
*/
|
|
142
|
+
function _switchable (factories, sources, name$, switched=['DOM'], stateSourceName='STATE') {
|
|
143
|
+
if (typeof switched === 'string') switched = [switched];
|
|
144
|
+
|
|
145
|
+
const sinks = Object.entries(factories)
|
|
146
|
+
.map(([name, factory]) => {
|
|
147
|
+
if (sources[stateSourceName]) {
|
|
148
|
+
const state$ = sources[stateSourceName].stream;
|
|
149
|
+
const switched = xs__default["default"].combine(name$, state$)
|
|
150
|
+
.filter(([newComponentName, _]) => newComponentName == name)
|
|
151
|
+
.map(([_, state]) => state)
|
|
152
|
+
.remember();
|
|
153
|
+
|
|
154
|
+
const state = new sources[stateSourceName].constructor(switched, sources[stateSourceName]._name);
|
|
155
|
+
return [name, factory({ ...sources, state })]
|
|
156
|
+
}
|
|
157
|
+
return [name, factory(sources)]
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const switchedSinks = Object.keys(sources)
|
|
161
|
+
.reduce((obj, sinkName) => {
|
|
162
|
+
if (switched.includes(sinkName)) {
|
|
163
|
+
obj[sinkName] = name$
|
|
164
|
+
.map( newComponentName => {
|
|
165
|
+
const sink = sinks.find(([componentName, _]) => componentName === newComponentName);
|
|
166
|
+
return (sink && sink[1][sinkName]) || xs__default["default"].never()
|
|
167
|
+
})
|
|
168
|
+
.flatten()
|
|
169
|
+
.remember()
|
|
170
|
+
.startWith(undefined);
|
|
171
|
+
} else {
|
|
172
|
+
const definedSinks = sinks.filter(([_,sink]) => sink[sinkName] !== undefined)
|
|
173
|
+
.map(([_,sink]) => sink[sinkName]);
|
|
174
|
+
obj[sinkName] = xs__default["default"].merge(...definedSinks);
|
|
175
|
+
}
|
|
176
|
+
return obj
|
|
177
|
+
}, {});
|
|
178
|
+
|
|
179
|
+
return switchedSinks
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// import syntax has bugs for xstream in Node context
|
|
183
|
+
// this attempts to normalize to work in both Node and browser
|
|
184
|
+
// if (!xs.never && xs.default && xs.default.never) {
|
|
185
|
+
// xs.never = xs.default.never
|
|
186
|
+
// xs.merge = xs.default.merge
|
|
187
|
+
// xs.of = xs.default.of
|
|
188
|
+
// }
|
|
189
|
+
// const concat = (Concat && Concat.default) ? Concat.default : Concat
|
|
190
|
+
// const delay = (Delay && Delay.default) ? Delay.default : Delay
|
|
191
|
+
// const dropRepeats = (DropRepeats && DropRepeats.default) ? DropRepeats.default : DropRepeats
|
|
192
|
+
|
|
193
|
+
const ENVIRONMENT = ((typeof window != 'undefined' && window) || (process && process.env)) || {};
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
const REQUEST_SELECTOR_METHOD = 'request';
|
|
197
|
+
const BOOTSTRAP_ACTION = 'BOOTSTRAP';
|
|
198
|
+
const INITIALIZE_ACTION = 'INITIALIZE';
|
|
199
|
+
const HYDRATE_ACTION = 'HYDRATE';
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
let IS_ROOT_COMPONENT = true;
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
const ABORT = '~#~#~ABORT~#~#~';
|
|
206
|
+
|
|
207
|
+
function component (opts) {
|
|
208
|
+
const { name, sources, isolateOpts, stateSourceName='STATE' } = opts;
|
|
209
|
+
|
|
210
|
+
if (sources && typeof sources !== 'object') {
|
|
211
|
+
throw new Error('Sources must be a Cycle.js sources object:', name)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
let fixedIsolateOpts;
|
|
215
|
+
if (typeof isolateOpts == 'string') {
|
|
216
|
+
fixedIsolateOpts = { [stateSourceName]: isolateOpts };
|
|
217
|
+
} else {
|
|
218
|
+
if (isolateOpts === true) {
|
|
219
|
+
fixedIsolateOpts = {};
|
|
220
|
+
} else {
|
|
221
|
+
fixedIsolateOpts = isolateOpts;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const currySources = typeof sources === 'undefined';
|
|
226
|
+
|
|
227
|
+
if (typeof fixedIsolateOpts == 'object') {
|
|
228
|
+
const wrapped = (sources) => {
|
|
229
|
+
const fixedOpts = { ...opts, sources };
|
|
230
|
+
return (new Component(fixedOpts)).sinks
|
|
231
|
+
};
|
|
232
|
+
return currySources ? isolate__default["default"](wrapped, fixedIsolateOpts) : isolate__default["default"](wrapped, fixedIsolateOpts)(sources)
|
|
233
|
+
} else {
|
|
234
|
+
return currySources ? (sources) => (new Component({ ...opts, sources })).sinks : (new Component(opts)).sinks
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class Component {
|
|
243
|
+
// [ PASSED PARAMETERS ]
|
|
244
|
+
// name
|
|
245
|
+
// sources
|
|
246
|
+
// intent
|
|
247
|
+
// request
|
|
248
|
+
// model
|
|
249
|
+
// response
|
|
250
|
+
// view
|
|
251
|
+
// children
|
|
252
|
+
// initialState
|
|
253
|
+
// calculated
|
|
254
|
+
// storeCalculatedInState
|
|
255
|
+
// DOMSourceName
|
|
256
|
+
// stateSourceName
|
|
257
|
+
// requestSourceName
|
|
258
|
+
|
|
259
|
+
// [ PRIVATE / CALCULATED VALUES ]
|
|
260
|
+
// sourceNames
|
|
261
|
+
// intent$
|
|
262
|
+
// action$
|
|
263
|
+
// model$
|
|
264
|
+
// response$
|
|
265
|
+
// sendResponse$
|
|
266
|
+
// children$
|
|
267
|
+
// vdom$
|
|
268
|
+
// subComponentSink$
|
|
269
|
+
|
|
270
|
+
// [ INSTANTIATED STREAM OPERATOR ]
|
|
271
|
+
// log
|
|
272
|
+
|
|
273
|
+
// [ OUTPUT ]
|
|
274
|
+
// sinks
|
|
275
|
+
|
|
276
|
+
constructor({ name='NO NAME', sources, intent, request, model, response, view, children={}, components={}, initialState, calculated, storeCalculatedInState=true, DOMSourceName='DOM', stateSourceName='STATE', requestSourceName='HTTP' }) {
|
|
277
|
+
if (!sources || typeof sources != 'object') throw new Error('Missing or invalid sources')
|
|
278
|
+
|
|
279
|
+
this.name = name;
|
|
280
|
+
this.sources = sources;
|
|
281
|
+
this.intent = intent;
|
|
282
|
+
this.request = request;
|
|
283
|
+
this.model = model;
|
|
284
|
+
this.response = response;
|
|
285
|
+
this.view = view;
|
|
286
|
+
this.children = children;
|
|
287
|
+
this.components = components;
|
|
288
|
+
this.initialState = initialState;
|
|
289
|
+
this.calculated = calculated;
|
|
290
|
+
this.storeCalculatedInState = storeCalculatedInState;
|
|
291
|
+
this.DOMSourceName = DOMSourceName;
|
|
292
|
+
this.stateSourceName = stateSourceName;
|
|
293
|
+
this.requestSourceName = requestSourceName;
|
|
294
|
+
this.sourceNames = Object.keys(sources);
|
|
295
|
+
|
|
296
|
+
this.isSubComponent = this.sourceNames.includes('props$');
|
|
297
|
+
|
|
298
|
+
const state$ = sources[stateSourceName] && sources[stateSourceName].stream;
|
|
299
|
+
|
|
300
|
+
if (state$) {
|
|
301
|
+
this.currentState = initialState || {};
|
|
302
|
+
this.sources[this.stateSourceName] = new state.StateSource(state$.map(val => {
|
|
303
|
+
this.currentState = val;
|
|
304
|
+
return val
|
|
305
|
+
}));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (IS_ROOT_COMPONENT && typeof this.intent === 'undefined' && typeof this.model === 'undefined') {
|
|
309
|
+
this.initialState = initialState || true;
|
|
310
|
+
this.intent = _ => ({__NOOP_ACTION__:xs__default["default"].never()});
|
|
311
|
+
this.model = {
|
|
312
|
+
__NOOP_ACTION__: state => state
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
IS_ROOT_COMPONENT = false;
|
|
316
|
+
|
|
317
|
+
this.log = makeLog(name);
|
|
318
|
+
|
|
319
|
+
this.initIntent$();
|
|
320
|
+
this.initAction$();
|
|
321
|
+
this.initResponse$();
|
|
322
|
+
this.initState();
|
|
323
|
+
this.initModel$();
|
|
324
|
+
this.initSendResponse$();
|
|
325
|
+
this.initChildren$();
|
|
326
|
+
this.initSubComponentSink$();
|
|
327
|
+
this.initVdom$();
|
|
328
|
+
this.initSinks();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
initIntent$() {
|
|
332
|
+
if (!this.intent) {
|
|
333
|
+
return
|
|
334
|
+
}
|
|
335
|
+
if (typeof this.intent != 'function') {
|
|
336
|
+
throw new Error('Intent must be a function')
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
this.intent$ = this.intent(this.sources);
|
|
340
|
+
|
|
341
|
+
if (!(this.intent$ instanceof xs.Stream) && (typeof this.intent$ != 'object')) {
|
|
342
|
+
throw new Error('Intent must return either an action$ stream or map of event streams')
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
initAction$() {
|
|
347
|
+
const requestSource = (this.sources && this.sources[this.requestSourceName]) || null;
|
|
348
|
+
|
|
349
|
+
if (!this.intent$) {
|
|
350
|
+
this.action$ = xs__default["default"].never();
|
|
351
|
+
return
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
let runner;
|
|
355
|
+
if (this.intent$ instanceof xs.Stream) {
|
|
356
|
+
runner = this.intent$;
|
|
357
|
+
} else {
|
|
358
|
+
const mapped = Object.entries(this.intent$)
|
|
359
|
+
.map(([type, data$]) => data$.map(data => ({type, data})));
|
|
360
|
+
runner = xs__default["default"].merge(xs__default["default"].never(), ...mapped);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const action$ = ((runner instanceof xs.Stream) ? runner : (runner.apply && runner(this.sources) || xs__default["default"].never()));
|
|
364
|
+
const wrapped$ = concat__default["default"](xs__default["default"].of({ type: BOOTSTRAP_ACTION }), action$)
|
|
365
|
+
.compose(delay__default$1["default"](10));
|
|
366
|
+
|
|
367
|
+
let initialApiData;
|
|
368
|
+
if (requestSource && typeof requestSource.select == 'function') {
|
|
369
|
+
initialApiData = requestSource.select('initial')
|
|
370
|
+
.flatten();
|
|
371
|
+
} else {
|
|
372
|
+
initialApiData = xs__default["default"].never();
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const hydrate$ = initialApiData.map(data => ({ type: HYDRATE_ACTION, data }));
|
|
376
|
+
|
|
377
|
+
this.action$ = xs__default["default"].merge(wrapped$, hydrate$)
|
|
378
|
+
.compose(this.log(({ type }) => `Action triggered: <${ type }>`));
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
initResponse$() {
|
|
382
|
+
if (typeof this.request == 'undefined') {
|
|
383
|
+
return
|
|
384
|
+
} else if (typeof this.request != 'object') {
|
|
385
|
+
throw new Error('The request parameter must be an object')
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const router$ = this.sources[this.requestSourceName];
|
|
389
|
+
const methods = Object.entries(this.request);
|
|
390
|
+
|
|
391
|
+
const wrapped = methods.reduce((acc, [method, routes]) => {
|
|
392
|
+
const _method = method.toLowerCase();
|
|
393
|
+
if (typeof router$[_method] != 'function') {
|
|
394
|
+
throw new Error('Invalid method in request object:', method)
|
|
395
|
+
}
|
|
396
|
+
const entries = Object.entries(routes);
|
|
397
|
+
const mapped = entries.reduce((acc, [route, action]) => {
|
|
398
|
+
const routeString = `[${_method.toUpperCase()}]:${route || 'none'}`;
|
|
399
|
+
const actionType = typeof action;
|
|
400
|
+
if (actionType === 'undefined') {
|
|
401
|
+
throw new Error(`Action for '${ route }' route in request object not specified`)
|
|
402
|
+
} else if (actionType !== 'string' && actionType !== 'function') {
|
|
403
|
+
throw new Error(`Invalid action for '${ route }' route: expecting string or function`)
|
|
404
|
+
}
|
|
405
|
+
const actionString = (actionType === 'function') ? '[ FUNCTION ]' : `< ${ action } >`;
|
|
406
|
+
console.log(`[${ this.name }] Adding ${ this.requestSourceName } route:`, _method.toUpperCase(), `'${ route }' <${ actionString }>`);
|
|
407
|
+
const route$ = router$[_method](route)
|
|
408
|
+
.compose(dropRepeats__default$1["default"]((a, b) => a.id == b.id))
|
|
409
|
+
.map(req => {
|
|
410
|
+
if (!req || !req.id) {
|
|
411
|
+
throw new Error(`No id found in request: ${ routeString }`)
|
|
412
|
+
}
|
|
413
|
+
try {
|
|
414
|
+
const _reqId = req.id;
|
|
415
|
+
const params = req.params;
|
|
416
|
+
const body = req.body;
|
|
417
|
+
const cookies = req.cookies;
|
|
418
|
+
const type = (actionType === 'function') ? 'FUNCTION' : action;
|
|
419
|
+
const data = { params, body, cookies, req };
|
|
420
|
+
const obj = { type, data: body, req, _reqId, _action: type };
|
|
421
|
+
|
|
422
|
+
const timestamp = (new Date()).toISOString();
|
|
423
|
+
const ip = req.get ? req.get('host') : '0.0.0.0';
|
|
424
|
+
|
|
425
|
+
console.log(`${ timestamp } ${ ip } ${ req.method } ${ req.url }`);
|
|
426
|
+
|
|
427
|
+
if (ENVIRONMENT.DEBUG) {
|
|
428
|
+
this.action$.setDebugListener({next: ({ type }) => console.log(`[${ this.name }] Action from ${ this.requestSourceName } request: <${ type }>`)});
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (actionType === 'function') {
|
|
432
|
+
const enhancedState = this.addCalculated(this.currentState);
|
|
433
|
+
const result = action(enhancedState, req);
|
|
434
|
+
return xs__default["default"].of({ ...obj, data: result })
|
|
435
|
+
} else {
|
|
436
|
+
this.action$.shamefullySendNext(obj);
|
|
437
|
+
|
|
438
|
+
const sourceEntries = Object.entries(this.sources);
|
|
439
|
+
const responses = sourceEntries.reduce((acc, [name, source]) => {
|
|
440
|
+
if (!source || typeof source[REQUEST_SELECTOR_METHOD] != 'function') return acc
|
|
441
|
+
const selected$ = source[REQUEST_SELECTOR_METHOD](_reqId);
|
|
442
|
+
return [ ...acc, selected$ ]
|
|
443
|
+
}, []);
|
|
444
|
+
return xs__default["default"].merge(...responses)
|
|
445
|
+
}
|
|
446
|
+
} catch(err) {
|
|
447
|
+
console.error(err);
|
|
448
|
+
}
|
|
449
|
+
}).flatten();
|
|
450
|
+
return [ ...acc, route$ ]
|
|
451
|
+
}, []);
|
|
452
|
+
const mapped$ = xs__default["default"].merge(...mapped);
|
|
453
|
+
return [ ...acc, mapped$ ]
|
|
454
|
+
}, []);
|
|
455
|
+
|
|
456
|
+
this.response$ = xs__default["default"].merge(...wrapped)
|
|
457
|
+
.compose(this.log(res => {
|
|
458
|
+
if (res._action) return `[${ this.requestSourceName }] response data received for Action: <${ res._action }>`
|
|
459
|
+
return `[${ this.requestSourceName }] response data received from FUNCTION`
|
|
460
|
+
}));
|
|
461
|
+
|
|
462
|
+
if (typeof this.response != 'undefined' && typeof this.response$ == 'undefined') {
|
|
463
|
+
throw new Error('Cannot have a response parameter without a request parameter')
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
initState() {
|
|
468
|
+
if (this.model != undefined) {
|
|
469
|
+
if (this.model[INITIALIZE_ACTION] === undefined) {
|
|
470
|
+
this.model[INITIALIZE_ACTION] = {
|
|
471
|
+
[this.stateSourceName]: (_, data) => ({ ...this.addCalculated(data) })
|
|
472
|
+
};
|
|
473
|
+
} else {
|
|
474
|
+
Object.keys(this.model[INITIALIZE_ACTION]).forEach(name => {
|
|
475
|
+
if (name !== this.stateSourceName) {
|
|
476
|
+
console.warn(`${ INITIALIZE_ACTION } can only be used with the ${ this.stateSourceName } source... disregarding ${ name }`);
|
|
477
|
+
delete this.model[INITIALIZE_ACTION][name];
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
initModel$() {
|
|
485
|
+
if (typeof this.model == 'undefined') {
|
|
486
|
+
this.model$ = this.sourceNames.reduce((a,s) => {
|
|
487
|
+
a[s] = xs__default["default"].never();
|
|
488
|
+
return a
|
|
489
|
+
}, {});
|
|
490
|
+
return
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const initial = { type: INITIALIZE_ACTION, data: this.initialState };
|
|
494
|
+
const shimmed$ = this.initialState ? concat__default["default"](xs__default["default"].of(initial), this.action$).compose(delay__default$1["default"](0)) : this.action$;
|
|
495
|
+
const onState = this.makeOnAction(shimmed$, true, this.action$);
|
|
496
|
+
const onNormal = this.makeOnAction(this.action$, false, this.action$);
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
const modelEntries = Object.entries(this.model);
|
|
500
|
+
|
|
501
|
+
const reducers = {};
|
|
502
|
+
|
|
503
|
+
modelEntries.forEach((entry) => {
|
|
504
|
+
let [action, sinks] = entry;
|
|
505
|
+
|
|
506
|
+
if (typeof sinks === 'function') {
|
|
507
|
+
sinks = { [this.stateSourceName]: sinks };
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (typeof sinks !== 'object') {
|
|
511
|
+
throw new Error(`Entry for each action must be an object: ${ this.name } ${ action }`)
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const sinkEntries = Object.entries(sinks);
|
|
515
|
+
|
|
516
|
+
sinkEntries.forEach((entry) => {
|
|
517
|
+
const [sink, reducer] = entry;
|
|
518
|
+
|
|
519
|
+
const isStateSink = (sink == this.stateSourceName);
|
|
520
|
+
|
|
521
|
+
const on = isStateSink ? onState : onNormal;
|
|
522
|
+
const onned = on(action, reducer);
|
|
523
|
+
|
|
524
|
+
const wrapped = onned.compose(this.log(data => {
|
|
525
|
+
if (isStateSink) {
|
|
526
|
+
return `State reducer added: <${ action }>`
|
|
527
|
+
} else {
|
|
528
|
+
const extra = data && (data.type || data.command || data.name || data.key || (Array.isArray(data) && 'Array') || data);
|
|
529
|
+
return `Data sent to [${ sink }]: <${ action }> ${ extra }`
|
|
530
|
+
}
|
|
531
|
+
}));
|
|
532
|
+
|
|
533
|
+
if (Array.isArray(reducers[sink])) {
|
|
534
|
+
reducers[sink].push(wrapped);
|
|
535
|
+
} else {
|
|
536
|
+
reducers[sink] = [wrapped];
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
const model$ = Object.entries(reducers).reduce((acc, entry) => {
|
|
542
|
+
const [sink, streams] = entry;
|
|
543
|
+
acc[sink] = xs__default["default"].merge(xs__default["default"].never(), ...streams);
|
|
544
|
+
return acc
|
|
545
|
+
}, {});
|
|
546
|
+
|
|
547
|
+
this.model$ = model$;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
initSendResponse$() {
|
|
551
|
+
const responseType = typeof this.response;
|
|
552
|
+
if (responseType != 'function' && responseType != 'undefined') {
|
|
553
|
+
throw new Error('The response parameter must be a function')
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (responseType == 'undefined') {
|
|
557
|
+
if (this.response$) {
|
|
558
|
+
this.response$.subscribe({
|
|
559
|
+
next: this.log(({ _reqId, _action }) => `Unhandled response for request: ${ _action } ${ _reqId }`)
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
this.sendResponse$ = xs__default["default"].never();
|
|
563
|
+
return
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const selectable = {
|
|
567
|
+
select: (actions) => {
|
|
568
|
+
if (typeof actions == 'undefined') return this.response$
|
|
569
|
+
if (!Array.isArray(actions)) actions = [actions];
|
|
570
|
+
return this.response$.filter(({_action}) => (actions.length > 0) ? (_action === 'FUNCTION' || actions.includes(_action)) : true)
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
const out = this.response(selectable);
|
|
575
|
+
if (typeof out != 'object') {
|
|
576
|
+
throw new Error('The response function must return an object')
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const entries = Object.entries(out);
|
|
580
|
+
const out$ = entries.reduce((acc, [command, response$]) => {
|
|
581
|
+
const mapped$ = response$.map(({ _reqId, _action, data }) => {
|
|
582
|
+
if (!_reqId) {
|
|
583
|
+
throw new Error(`No request id found for response for: ${ command }`)
|
|
584
|
+
}
|
|
585
|
+
return { _reqId, _action, command, data }
|
|
586
|
+
});
|
|
587
|
+
return [ ...acc, mapped$ ]
|
|
588
|
+
}, []);
|
|
589
|
+
|
|
590
|
+
this.sendResponse$ = xs__default["default"].merge(...out$)
|
|
591
|
+
.compose(this.log(({ _reqId, _action }) => `[${ this.requestSourceName }] response sent for: <${ _action }>`));
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
initChildren$() {
|
|
595
|
+
const initial = this.sourceNames.reduce((acc, name) => {
|
|
596
|
+
if (name == this.DOMSourceName) {
|
|
597
|
+
acc[name] = {};
|
|
598
|
+
} else {
|
|
599
|
+
acc[name] = [];
|
|
600
|
+
}
|
|
601
|
+
return acc
|
|
602
|
+
}, {});
|
|
603
|
+
|
|
604
|
+
this.children$ = Object.entries(this.children).reduce((acc, [childName, childFactory]) => {
|
|
605
|
+
const child$ = childFactory(this.sources);
|
|
606
|
+
this.sourceNames.forEach(source => {
|
|
607
|
+
if (source == this.DOMSourceName) {
|
|
608
|
+
acc[source][childName] = child$[source];
|
|
609
|
+
} else {
|
|
610
|
+
acc[source].push(child$[source]);
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
return acc
|
|
614
|
+
}, initial);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
initSubComponentSink$() {
|
|
618
|
+
const subComponentSink$ = xs__default["default"].create({
|
|
619
|
+
start: listener => {
|
|
620
|
+
this.newSubComponentSinks = listener.next.bind(listener);
|
|
621
|
+
},
|
|
622
|
+
stop: _ => {
|
|
623
|
+
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
subComponentSink$.subscribe({ next: _ => _ });
|
|
627
|
+
this.subComponentSink$ = subComponentSink$.filter(sinks => Object.keys(sinks).length > 0);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
initVdom$() {
|
|
631
|
+
if (typeof this.view != 'function') {
|
|
632
|
+
this.vdom$ = xs__default["default"].of(null);
|
|
633
|
+
return
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const state$1 = this.sources[this.stateSourceName];
|
|
637
|
+
const renderParams = { ...this.children$[this.DOMSourceName] };
|
|
638
|
+
|
|
639
|
+
const enhancedState = state$1 && state$1.isolateSource(state$1, { get: state => this.addCalculated(state) });
|
|
640
|
+
const stateStream = (enhancedState && enhancedState.stream) || xs__default["default"].never();
|
|
641
|
+
|
|
642
|
+
renderParams.state = stateStream;
|
|
643
|
+
renderParams[this.stateSourceName] = stateStream;
|
|
644
|
+
|
|
645
|
+
if (this.sources.props$) {
|
|
646
|
+
renderParams.props = this.sources.props$;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
if (this.sources.children$) {
|
|
650
|
+
renderParams.children = this.sources.children$;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const pulled = Object.entries(renderParams).reduce((acc, [name, stream]) => {
|
|
654
|
+
acc.names.push(name);
|
|
655
|
+
acc.streams.push(stream);
|
|
656
|
+
return acc
|
|
657
|
+
}, {names: [], streams: []});
|
|
658
|
+
|
|
659
|
+
const merged = xs__default["default"].combine(...pulled.streams);
|
|
660
|
+
|
|
661
|
+
const throttled = merged
|
|
662
|
+
.compose(debounce__default$1["default"](5))
|
|
663
|
+
.map(arr => {
|
|
664
|
+
return pulled.names.reduce((acc, name, index) => {
|
|
665
|
+
acc[name] = arr[index];
|
|
666
|
+
return acc
|
|
667
|
+
}, {})
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
const componentNames = Object.keys(this.components);
|
|
671
|
+
|
|
672
|
+
const subComponentRenderedProxy$ = xs__default["default"].create();
|
|
673
|
+
const vDom$ = throttled.map((params) => params).map(this.view).map(vDom => vDom || { sel: 'div', data: {}, children: [] });
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
const componentInstances$ = vDom$
|
|
677
|
+
.fold((previousComponents, vDom) => {
|
|
678
|
+
const foundComponents = getComponents(vDom, componentNames);
|
|
679
|
+
const entries = Object.entries(foundComponents);
|
|
680
|
+
|
|
681
|
+
const rootEntry = { '::ROOT::': vDom };
|
|
682
|
+
|
|
683
|
+
if (entries.length === 0) {
|
|
684
|
+
return rootEntry
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const sinkArrsByType = {};
|
|
688
|
+
|
|
689
|
+
const newComponents = entries.reduce((acc, [id, el]) => {
|
|
690
|
+
const componentName = el.sel;
|
|
691
|
+
const data = el.data;
|
|
692
|
+
const props = data.props || {};
|
|
693
|
+
const children = el.children || [];
|
|
694
|
+
const isCollection = data.isCollection || false;
|
|
695
|
+
const isSwitchable = data.isSwitchable || false;
|
|
696
|
+
|
|
697
|
+
if (previousComponents[id]) {
|
|
698
|
+
const entry = previousComponents[id];
|
|
699
|
+
acc[id] = entry;
|
|
700
|
+
entry.props$.shamefullySendNext(props);
|
|
701
|
+
entry.children$.shamefullySendNext(children);
|
|
702
|
+
return acc
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const factory = componentName === 'sygnal-factory' ? props.sygnalFactory : (this.components[componentName] || props.sygnalFactory);
|
|
706
|
+
if (!factory && !isCollection && !isSwitchable) {
|
|
707
|
+
if (componentName === 'sygnal-factory') throw new Error(`Component not found on element with Capitalized selector and nameless function: JSX transpilation replaces selectors starting with upper case letters with functions in-scope with the same name, Sygnal cannot see the name of the resulting component.`)
|
|
708
|
+
throw new Error(`Component not found: ${ componentName }`)
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
const props$ = xs__default["default"].create().startWith(props);
|
|
712
|
+
const children$ = xs__default["default"].create().startWith(children);
|
|
713
|
+
let stateSource = new state.StateSource(this.sources[this.stateSourceName].stream.startWith(this.currentState));
|
|
714
|
+
let sink$;
|
|
715
|
+
let preventStateUpdates = true;
|
|
716
|
+
|
|
717
|
+
if (isCollection) {
|
|
718
|
+
let field, lense;
|
|
719
|
+
|
|
720
|
+
const stateGetter = state => {
|
|
721
|
+
const arr = state[field];
|
|
722
|
+
if (typeof arr === 'undefined') return
|
|
723
|
+
if (!Array.isArray(arr)) {
|
|
724
|
+
const label = typeof data.props.of === 'string' ? data.props.of : 'components';
|
|
725
|
+
console.warn(`Collection of ${ label } does not have a valid array in the 'for' property: expects either an array or a string of the name of an array property on the state`);
|
|
726
|
+
return []
|
|
727
|
+
}
|
|
728
|
+
return arr
|
|
729
|
+
};
|
|
730
|
+
|
|
731
|
+
if (typeof props.for === 'undefined') {
|
|
732
|
+
lense = {
|
|
733
|
+
get: state => {
|
|
734
|
+
if (!Array.isArray(state)) {
|
|
735
|
+
console.warn(`Collection sub-component of ${ this.name } has no 'for' attribute and the parent state is not an array: Provide a 'for' attribute with either an array or the name of a state property containing an array`);
|
|
736
|
+
return []
|
|
737
|
+
}
|
|
738
|
+
return state
|
|
739
|
+
},
|
|
740
|
+
set: (oldState, newState) => newState
|
|
741
|
+
};
|
|
742
|
+
preventStateUpdates = false;
|
|
743
|
+
} else if (typeof props.for === 'string') {
|
|
744
|
+
field = props.for;
|
|
745
|
+
lense = {
|
|
746
|
+
get: stateGetter,
|
|
747
|
+
set: (state, arr) => {
|
|
748
|
+
if (this.calculated && field in this.calculated) {
|
|
749
|
+
console.warn(`Collection sub-component of ${ this.name } attempted to update state on a calculated field '${ field }': Update ignored`);
|
|
750
|
+
return state
|
|
751
|
+
}
|
|
752
|
+
return { ...state, [field]: arr }
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
preventStateUpdates = false;
|
|
756
|
+
} else {
|
|
757
|
+
field = 'for';
|
|
758
|
+
stateSource = new state.StateSource(props$.remember());
|
|
759
|
+
lense = {
|
|
760
|
+
get: stateGetter,
|
|
761
|
+
set: (state, arr) => state
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
const sources = { ...this.sources, [this.stateSourceName]: stateSource, props$, children$ };
|
|
765
|
+
const factory = typeof data.props.of === 'function' ? data.props.of : this.components[data.props.of];
|
|
766
|
+
sink$ = collection(factory, lense, { container: null })(sources);
|
|
767
|
+
if (typeof sink$ !== 'object') {
|
|
768
|
+
throw new Error('Invalid sinks returned from component factory of collection element')
|
|
769
|
+
}
|
|
770
|
+
} else if (isSwitchable) {
|
|
771
|
+
const stateField = data.props.state;
|
|
772
|
+
let isolateSwitchable = false;
|
|
773
|
+
let lense;
|
|
774
|
+
if (typeof stateField === 'string') {
|
|
775
|
+
isolateSwitchable = true;
|
|
776
|
+
lense = {
|
|
777
|
+
get: state => {
|
|
778
|
+
return state[stateField]
|
|
779
|
+
},
|
|
780
|
+
set: (oldState, newState) => {
|
|
781
|
+
if (this.calculated && stateField in this.calculated) {
|
|
782
|
+
console.warn(`Switchable sub-component of ${ this.name } attempted to update state on a calculated field '${ stateField }': Update ignored`);
|
|
783
|
+
return oldState
|
|
784
|
+
}
|
|
785
|
+
return { ...oldState, [stateField]: newState }
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
preventStateUpdates = false;
|
|
789
|
+
} else if (typeof stateField === 'undefined') {
|
|
790
|
+
isolateSwitchable = true;
|
|
791
|
+
lense = {
|
|
792
|
+
get: state => state,
|
|
793
|
+
set: (oldState, newState) => newState
|
|
794
|
+
};
|
|
795
|
+
preventStateUpdates = false;
|
|
796
|
+
} else if (typeof stateField === 'object') {
|
|
797
|
+
stateSource = new state.StateSource(props$.map(props => props.state));
|
|
798
|
+
} else {
|
|
799
|
+
throw new Error(`Invalid state provided to collection sub-component of ${ this.name }: Expecting string, object, or none, but found ${ typeof stateField }`)
|
|
800
|
+
}
|
|
801
|
+
const switchableComponents = data.props.of;
|
|
802
|
+
const sources = { ...this.sources, [this.stateSourceName]: stateSource, props$, children$ };
|
|
803
|
+
if (isolateSwitchable) {
|
|
804
|
+
sink$ = isolate__default["default"](switchable(switchableComponents, props$.map(props => props.current)), { [this.stateSourceName]: lense })(sources);
|
|
805
|
+
} else {
|
|
806
|
+
sink$ = switchable(switchableComponents, props$.map(props => props.current))(sources);
|
|
807
|
+
}
|
|
808
|
+
if (typeof sink$ !== 'object') {
|
|
809
|
+
throw new Error('Invalid sinks returned from component factory of switchable element')
|
|
810
|
+
}
|
|
811
|
+
} else {
|
|
812
|
+
const { state: stateProp, sygnalFactory, id, ...sanitizedProps } = props;
|
|
813
|
+
if (typeof stateProp === 'undefined' && (typeof sanitizedProps !== 'object' || Object.keys(sanitizedProps).length === 0)) {
|
|
814
|
+
const sources = { ...this.sources, [this.stateSourceName]: stateSource, props$: xs__default["default"].never().startWith(null), children$ };
|
|
815
|
+
sink$ = factory(sources);
|
|
816
|
+
preventStateUpdates = false;
|
|
817
|
+
} else {
|
|
818
|
+
const lense = (props) => {
|
|
819
|
+
const state = props.state;
|
|
820
|
+
if (typeof state === 'undefined') return props
|
|
821
|
+
if (typeof state !== 'object') return state
|
|
822
|
+
|
|
823
|
+
const copy = { ...props };
|
|
824
|
+
delete copy.state;
|
|
825
|
+
return { ...copy, ...state }
|
|
826
|
+
};
|
|
827
|
+
stateSource = new state.StateSource(props$.map(lense));
|
|
828
|
+
const sources = { ...this.sources, [this.stateSourceName]: stateSource, props$, children$ };
|
|
829
|
+
sink$ = factory(sources);
|
|
830
|
+
}
|
|
831
|
+
if (typeof sink$ !== 'object') {
|
|
832
|
+
const name = componentName === 'sygnal-factory' ? 'custom element' : componentName;
|
|
833
|
+
throw new Error('Invalid sinks returned from component factory:', name)
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
if (preventStateUpdates) {
|
|
838
|
+
const originalStateSink = sink$[this.stateSourceName];
|
|
839
|
+
sink$[this.stateSourceName] = originalStateSink.filter(state => {
|
|
840
|
+
console.warn('State update attempt from component with inderect link to state: Components with state set through HTML properties/attributes cannot update application state directly');
|
|
841
|
+
return false
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
const originalDOMSink = sink$[this.DOMSourceName].remember();
|
|
846
|
+
const repeatChecker = (a, b) => {
|
|
847
|
+
const aa = JSON.stringify(a);
|
|
848
|
+
const bb = JSON.stringify(b);
|
|
849
|
+
return aa === bb
|
|
850
|
+
};
|
|
851
|
+
sink$[this.DOMSourceName] = stateSource.stream.compose(dropRepeats__default$1["default"](repeatChecker)).map(state => {
|
|
852
|
+
subComponentRenderedProxy$.shamefullySendNext(null);
|
|
853
|
+
return originalDOMSink
|
|
854
|
+
}).compose(debounce__default$1["default"](10)).flatten().remember();
|
|
855
|
+
acc[id] = { sink$, props$, children$ };
|
|
856
|
+
|
|
857
|
+
Object.entries(sink$).map(([name, stream]) => {
|
|
858
|
+
sinkArrsByType[name] ||= [];
|
|
859
|
+
if (name !== this.DOMSourceName) sinkArrsByType[name].push(stream);
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
return acc
|
|
863
|
+
}, rootEntry);
|
|
864
|
+
|
|
865
|
+
const mergedSinksByType = Object.entries(sinkArrsByType).reduce((acc, [name, streamArr]) => {
|
|
866
|
+
if (streamArr.length === 0) return acc
|
|
867
|
+
acc[name] = streamArr.length === 1 ? streamArr[0] : xs__default["default"].merge(...streamArr);
|
|
868
|
+
return acc
|
|
869
|
+
}, {});
|
|
870
|
+
|
|
871
|
+
this.newSubComponentSinks(mergedSinksByType);
|
|
872
|
+
|
|
873
|
+
// subComponentRenderedProxy$.shamefullySendNext(null)
|
|
874
|
+
return newComponents
|
|
875
|
+
}, {});
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
this.vdom$ = xs__default["default"].combine(subComponentRenderedProxy$.startWith(null), componentInstances$).map(([_, components]) => {
|
|
879
|
+
|
|
880
|
+
const root = components['::ROOT::'];
|
|
881
|
+
let ids = [];
|
|
882
|
+
const entries = Object.entries(components).filter(([id]) => id !== '::ROOT::');
|
|
883
|
+
|
|
884
|
+
if (entries.length === 0) {
|
|
885
|
+
return xs__default["default"].of(root)
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
const vdom$ = entries
|
|
889
|
+
.map(([id, val]) => {
|
|
890
|
+
ids.push(id);
|
|
891
|
+
return val.sink$[this.DOMSourceName].startWith(undefined)
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
if (vdom$.length === 0) return xs__default["default"].of(root)
|
|
895
|
+
|
|
896
|
+
return xs__default["default"].combine(...vdom$).compose(debounce__default$1["default"](5)).map(vdoms => {
|
|
897
|
+
const withIds = vdoms.reduce((acc, vdom, index) => {
|
|
898
|
+
acc[ids[index]] = vdom;
|
|
899
|
+
return acc
|
|
900
|
+
}, {});
|
|
901
|
+
const rootCopy = deepCopyVdom(root);
|
|
902
|
+
const injected = injectComponents(rootCopy, withIds, componentNames);
|
|
903
|
+
return injected
|
|
904
|
+
})
|
|
905
|
+
})
|
|
906
|
+
.flatten()
|
|
907
|
+
.filter(val => !!val)
|
|
908
|
+
.compose(debounce__default$1["default"](5))
|
|
909
|
+
.remember()
|
|
910
|
+
.compose(this.log('View Rendered'));
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
initSinks() {
|
|
914
|
+
this.sinks = this.sourceNames.reduce((acc, name) => {
|
|
915
|
+
if (name == this.DOMSourceName) return acc
|
|
916
|
+
const subComponentSink$ = this.subComponentSink$ ? this.subComponentSink$.map(sinks => sinks[name]).filter(sink => !!sink).flatten() : xs__default["default"].never();
|
|
917
|
+
if (name === this.stateSourceName) {
|
|
918
|
+
acc[name] = xs__default["default"].merge((this.model$[name] || xs__default["default"].never()), subComponentSink$, this.sources[this.stateSourceName].stream.filter(_ => false), ...this.children$[name]);
|
|
919
|
+
} else {
|
|
920
|
+
acc[name] = xs__default["default"].merge((this.model$[name] || xs__default["default"].never()), subComponentSink$, ...this.children$[name]);
|
|
921
|
+
}
|
|
922
|
+
return acc
|
|
923
|
+
}, {});
|
|
924
|
+
|
|
925
|
+
this.sinks[this.DOMSourceName] = this.vdom$;
|
|
926
|
+
this.sinks[this.requestSourceName] = xs__default["default"].merge(this.sendResponse$ ,this.sinks[this.requestSourceName]);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
makeOnAction(action$, isStateSink=true, rootAction$) {
|
|
930
|
+
rootAction$ = rootAction$ || action$;
|
|
931
|
+
return (name, reducer) => {
|
|
932
|
+
const filtered$ = action$.filter(({type}) => type == name);
|
|
933
|
+
|
|
934
|
+
let returnStream$;
|
|
935
|
+
if (typeof reducer === 'function') {
|
|
936
|
+
returnStream$ = filtered$.map(action => {
|
|
937
|
+
const next = (type, data) => {
|
|
938
|
+
const _reqId = action._reqId || (action.req && action.req.id);
|
|
939
|
+
const _data = _reqId ? (typeof data == 'object' ? { ...data, _reqId, _action: name } : { data, _reqId, _action: name }) : data;
|
|
940
|
+
// put the "next" action request at the end of the event loop so the "current" action completes first
|
|
941
|
+
setTimeout(() => {
|
|
942
|
+
// push the "next" action request into the action$ stream
|
|
943
|
+
rootAction$.shamefullySendNext({ type, data: _data });
|
|
944
|
+
}, 10);
|
|
945
|
+
};
|
|
946
|
+
|
|
947
|
+
let data = action.data;
|
|
948
|
+
if (data && data.data && data._reqId) data = data.data;
|
|
949
|
+
if (isStateSink) {
|
|
950
|
+
return (state) => {
|
|
951
|
+
const _state = this.isSubComponent ? this.currentState : state;
|
|
952
|
+
const enhancedState = this.addCalculated(_state);
|
|
953
|
+
const newState = reducer(enhancedState, data, next, action.req);
|
|
954
|
+
if (newState == ABORT) return _state
|
|
955
|
+
return this.cleanupCalculated(newState)
|
|
956
|
+
}
|
|
957
|
+
} else {
|
|
958
|
+
const enhancedState = this.addCalculated(this.currentState);
|
|
959
|
+
const reduced = reducer(enhancedState, data, next, action.req);
|
|
960
|
+
const type = typeof reduced;
|
|
961
|
+
const _reqId = action._reqId || (action.req && action.req.id);
|
|
962
|
+
if (['string', 'number', 'boolean', 'function'].includes(type)) return reduced
|
|
963
|
+
if (type == 'object') return { ...reduced, _reqId, _action: name }
|
|
964
|
+
if (type == 'undefined') {
|
|
965
|
+
console.warn(`'undefined' value sent to ${ name }`);
|
|
966
|
+
return reduced
|
|
967
|
+
}
|
|
968
|
+
throw new Error(`Invalid reducer type for ${ name } ${ type }`)
|
|
969
|
+
}
|
|
970
|
+
}).filter(result => result != ABORT);
|
|
971
|
+
} else if (reducer === undefined || reducer === true) {
|
|
972
|
+
returnStream$ = filtered$.map(({data}) => data);
|
|
973
|
+
} else {
|
|
974
|
+
const value = reducer;
|
|
975
|
+
returnStream$ = filtered$.mapTo(value);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
return returnStream$
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
addCalculated(state) {
|
|
983
|
+
if (!this.calculated || typeof state !== 'object') return state
|
|
984
|
+
if (typeof this.calculated !== 'object') throw new Error(`'calculated' parameter must be an object mapping calculated state field named to functions`)
|
|
985
|
+
const entries = Object.entries(this.calculated);
|
|
986
|
+
const calculated = entries.reduce((acc, [field, fn]) => {
|
|
987
|
+
if (typeof fn !== 'function') throw new Error(`Missing or invalid calculator function for calculated field '${ field }`)
|
|
988
|
+
try {
|
|
989
|
+
acc[field] = fn(state);
|
|
990
|
+
} catch(e) {
|
|
991
|
+
console.warn(`Calculated field '${ field }' threw an error during calculation: ${ e.message }`);
|
|
992
|
+
}
|
|
993
|
+
return acc
|
|
994
|
+
}, {});
|
|
995
|
+
return { ...state, ...calculated }
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
cleanupCalculated(state) {
|
|
999
|
+
if (this.storeCalculatedInState) return this.addCalculated(state)
|
|
1000
|
+
if (!this.calculated || !state || typeof state !== 'object') return state
|
|
1001
|
+
const keys = Object.keys(this.calculated);
|
|
1002
|
+
const copy = { ...state };
|
|
1003
|
+
keys.forEach(key => {
|
|
1004
|
+
if (this.initialState && typeof this.initialState[key] !== 'undefined') {
|
|
1005
|
+
copy[key] = this.initialState[key];
|
|
1006
|
+
} else {
|
|
1007
|
+
delete copy[key];
|
|
1008
|
+
}
|
|
1009
|
+
});
|
|
1010
|
+
return copy
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
|
|
1016
|
+
|
|
1017
|
+
|
|
1018
|
+
|
|
1019
|
+
|
|
1020
|
+
|
|
1021
|
+
/**
|
|
1022
|
+
* factory to create a logging function meant to be used inside of an xstream .compose()
|
|
1023
|
+
*
|
|
1024
|
+
* @param {String} context name of the component or file to be prepended to any messages
|
|
1025
|
+
* @return {Function}
|
|
1026
|
+
*
|
|
1027
|
+
* returned function accepts either a `String` of `Function`
|
|
1028
|
+
* `String` values will be logged to `console` as is
|
|
1029
|
+
* `Function` values will be called with the current `stream` value and the result will be logged to `console`
|
|
1030
|
+
* all output will be prepended with the `context` (ex. "[CONTEXT] My output")
|
|
1031
|
+
* ONLY outputs if the global `DEBUG` variable is set to `true`
|
|
1032
|
+
*/
|
|
1033
|
+
function makeLog (context) {
|
|
1034
|
+
return function (msg) {
|
|
1035
|
+
const fixedMsg = (typeof msg === 'function') ? msg : _ => msg;
|
|
1036
|
+
return stream => {
|
|
1037
|
+
return stream.debug(msg => {
|
|
1038
|
+
if (ENVIRONMENT.DEBUG == 'true' || ENVIRONMENT.DEBUG === true) {
|
|
1039
|
+
console.log(`[${context}] ${fixedMsg(msg)}`);
|
|
1040
|
+
}
|
|
1041
|
+
})
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
|
|
1047
|
+
|
|
1048
|
+
function getComponents(currentElement, componentNames, depth=0, index=0) {
|
|
1049
|
+
if (!currentElement) return {}
|
|
1050
|
+
|
|
1051
|
+
if (currentElement.data?.componentsProcessed) return {}
|
|
1052
|
+
if (depth === 0) currentElement.data.componentsProcessed = true;
|
|
1053
|
+
|
|
1054
|
+
const sel = currentElement.sel;
|
|
1055
|
+
const isCollection = sel && sel.toLowerCase() === 'collection';
|
|
1056
|
+
const isSwitchable = sel && sel.toLowerCase() === 'switchable';
|
|
1057
|
+
const isComponent = sel && (['collection', 'switchable', 'sygnal-factory', ...componentNames].includes(currentElement.sel)) || typeof currentElement.data?.props?.sygnalFactory === 'function';
|
|
1058
|
+
const props = (currentElement.data && currentElement.data.props) || {};
|
|
1059
|
+
const attrs = (currentElement.data && currentElement.data.attrs) || {};
|
|
1060
|
+
const children = currentElement.children || [];
|
|
1061
|
+
|
|
1062
|
+
let found = {};
|
|
1063
|
+
|
|
1064
|
+
if (isComponent) {
|
|
1065
|
+
const id = getComponentIdFromElement(currentElement, depth, index);
|
|
1066
|
+
if (isCollection) {
|
|
1067
|
+
if (!props.of) throw new Error(`Collection element missing required 'component' property`)
|
|
1068
|
+
if (typeof props.of !== 'string' && typeof props.of !== 'function') throw new Error(`Invalid 'component' property of collection element: found ${ typeof props.of } requires string or component factory function`)
|
|
1069
|
+
if (typeof props.of !== 'function' && !componentNames.includes(props.of)) throw new Error(`Specified component for collection not found: ${ props.of }`)
|
|
1070
|
+
if (typeof attrs.for !== 'undefined' && !(typeof attrs.for === 'string' || Array.isArray(attrs.for))) console.warn(`No valid array found in the 'value' property of collection ${ typeof props.of === 'string' ? props.of : 'function component' }: no collection components will be created`);
|
|
1071
|
+
currentElement.data.isCollection = true;
|
|
1072
|
+
currentElement.data.props ||= {};
|
|
1073
|
+
currentElement.data.props.for = attrs.for;
|
|
1074
|
+
currentElement.data.attrs = undefined;
|
|
1075
|
+
} else if (isSwitchable) {
|
|
1076
|
+
if (!props.of) throw new Error(`Switchable element missing required 'of' property`)
|
|
1077
|
+
if (typeof props.of !== 'object') throw new Error(`Invalid 'components' property of switchable element: found ${ typeof props.of } requires object mapping names to component factories`)
|
|
1078
|
+
const switchableComponents = Object.values(props.of);
|
|
1079
|
+
if (!switchableComponents.every(comp => typeof comp === 'function')) throw new Error(`One or more components provided to switchable element is not a valid component factory`)
|
|
1080
|
+
if (!props.current || (typeof props.current !== 'string' && typeof props.current !== 'function')) throw new Error(`Missing or invalid 'current' property for switchable element: found '${ typeof props.current }' requires string or function`)
|
|
1081
|
+
const switchableComponentNames = Object.keys(props.of);
|
|
1082
|
+
if (!switchableComponentNames.includes(props.current)) throw new Error(`Component '${ props.current }' not found in switchable element`)
|
|
1083
|
+
currentElement.data.isSwitchable = true;
|
|
1084
|
+
} else ;
|
|
1085
|
+
found[id] = currentElement;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
if (children.length > 0) {
|
|
1089
|
+
children.map((child, i) => getComponents(child, componentNames, depth + 1, i))
|
|
1090
|
+
.forEach((child) => {
|
|
1091
|
+
Object.entries(child).forEach(([id, el]) => found[id] = el);
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
return found
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
function injectComponents(currentElement, components, componentNames, depth=0, index) {
|
|
1099
|
+
if (!currentElement) return
|
|
1100
|
+
if (currentElement.data?.componentsInjected) return currentElement
|
|
1101
|
+
if (depth === 0 && currentElement.data) currentElement.data.componentsInjected = true;
|
|
1102
|
+
|
|
1103
|
+
|
|
1104
|
+
const sel = currentElement.sel || 'NO SELECTOR';
|
|
1105
|
+
const isComponent = ['collection', 'switchable', 'sygnal-factory', ...componentNames].includes(sel) || typeof currentElement.data?.props?.sygnalFactory === 'function';
|
|
1106
|
+
const isCollection = currentElement?.data?.isCollection;
|
|
1107
|
+
const isSwitchable = currentElement?.data?.isSwitchable;
|
|
1108
|
+
(currentElement.data && currentElement.data.props) || {};
|
|
1109
|
+
const children = currentElement.children || [];
|
|
1110
|
+
|
|
1111
|
+
if (isComponent) {
|
|
1112
|
+
const id = getComponentIdFromElement(currentElement, depth, index);
|
|
1113
|
+
const component = components[id];
|
|
1114
|
+
if (isCollection) {
|
|
1115
|
+
currentElement.sel = 'div';
|
|
1116
|
+
currentElement.children = Array.isArray(component) ? component : [component];
|
|
1117
|
+
return currentElement
|
|
1118
|
+
} else if (isSwitchable) {
|
|
1119
|
+
return component
|
|
1120
|
+
} else {
|
|
1121
|
+
return component
|
|
1122
|
+
}
|
|
1123
|
+
} else if (children.length > 0) {
|
|
1124
|
+
currentElement.children = children.map((child, i) => injectComponents(child, components, componentNames, depth + 1, i)).flat();
|
|
1125
|
+
return currentElement
|
|
1126
|
+
} else {
|
|
1127
|
+
return currentElement
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
const selMap = new Map();
|
|
1132
|
+
function getComponentIdFromElement(el, depth, index) {
|
|
1133
|
+
const sel = el.sel;
|
|
1134
|
+
const name = typeof sel === 'string' ? sel : 'functionComponent';
|
|
1135
|
+
let base = selMap.get(sel);
|
|
1136
|
+
if (!base) {
|
|
1137
|
+
const date = Date.now();
|
|
1138
|
+
const rand = Math.floor(Math.random() * 10000);
|
|
1139
|
+
base = `${date}-${rand}`;
|
|
1140
|
+
selMap.set(sel, base);
|
|
1141
|
+
}
|
|
1142
|
+
const uid = `${base}-${depth}-${index}`;
|
|
1143
|
+
const props = (el.data && el.data.props) || {};
|
|
1144
|
+
const id = (props.id && JSON.stringify(props.id)) || uid;
|
|
1145
|
+
const fullId = `${ name }::${ id }`;
|
|
1146
|
+
return fullId
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
|
|
1150
|
+
function deepCopyVdom(obj) {
|
|
1151
|
+
if (typeof obj === 'undefined') return obj
|
|
1152
|
+
return { ...obj, children: Array.isArray(obj.children) ? obj.children.map(deepCopyVdom) : undefined, data: obj.data && { ...obj.data, componentsInjected: false } }
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
function processForm(form, options={}) {
|
|
1156
|
+
let { events = ['input', 'submit'], preventDefault = true } = options;
|
|
1157
|
+
if (typeof events === 'string') events = [events];
|
|
1158
|
+
|
|
1159
|
+
const eventStream$ = events.map(event => form.events(event));
|
|
1160
|
+
|
|
1161
|
+
const merged$ = xs__default["default"].merge(...eventStream$);
|
|
1162
|
+
|
|
1163
|
+
return merged$.map((e) => {
|
|
1164
|
+
if (preventDefault) e.preventDefault();
|
|
1165
|
+
const form = (e.type === 'submit') ? e.srcElement : e.currentTarget;
|
|
1166
|
+
const formData = new FormData(form);
|
|
1167
|
+
let entries = {};
|
|
1168
|
+
entries.event = e;
|
|
1169
|
+
entries.eventType = e.type;
|
|
1170
|
+
const submitBtn = form.querySelector('input[type=submit]:focus');
|
|
1171
|
+
if (submitBtn) {
|
|
1172
|
+
const { name, value } = submitBtn;
|
|
1173
|
+
entries[name || 'submit'] = value;
|
|
1174
|
+
}
|
|
1175
|
+
for (let [name, value] of formData.entries()) {
|
|
1176
|
+
entries[name] = value;
|
|
1177
|
+
}
|
|
1178
|
+
return entries
|
|
1179
|
+
})
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
function eventBusDriver(out$) {
|
|
1183
|
+
const events = new EventTarget();
|
|
1184
|
+
|
|
1185
|
+
out$.subscribe({
|
|
1186
|
+
next: event => events.dispatchEvent(new CustomEvent('data', { detail: event }))
|
|
1187
|
+
});
|
|
1188
|
+
|
|
1189
|
+
return {
|
|
1190
|
+
select: (type) => {
|
|
1191
|
+
const all = !type;
|
|
1192
|
+
const _type = (Array.isArray(type)) ? type : [type];
|
|
1193
|
+
let cb;
|
|
1194
|
+
const in$ = xs__default["default"].create({
|
|
1195
|
+
start: (listener) => {
|
|
1196
|
+
cb = ({detail: event}) => {
|
|
1197
|
+
const data = (event && event.data) || null;
|
|
1198
|
+
if (all || _type.includes(event.type)) listener.next(data);
|
|
1199
|
+
};
|
|
1200
|
+
events.addEventListener('data', cb);
|
|
1201
|
+
},
|
|
1202
|
+
stop: _ => events.removeEventListener('data', cb)
|
|
1203
|
+
});
|
|
1204
|
+
|
|
1205
|
+
return adapt.adapt(in$)
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
function logDriver(out$) {
|
|
1211
|
+
out$.addListener({
|
|
1212
|
+
next: (val) => {
|
|
1213
|
+
console.log(val);
|
|
1214
|
+
}
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
function run(app, drivers={}, options={}) {
|
|
1219
|
+
const { mountPoint='#root', fragments=true } = options;
|
|
1220
|
+
|
|
1221
|
+
const wrapped = state.withState(app, 'STATE');
|
|
1222
|
+
|
|
1223
|
+
const baseDrivers = {
|
|
1224
|
+
EVENTS: eventBusDriver,
|
|
1225
|
+
DOM: dom.makeDOMDriver(mountPoint, { snabbdomOptions: { experimental: { fragments } } }),
|
|
1226
|
+
LOG: logDriver
|
|
1227
|
+
};
|
|
1228
|
+
|
|
1229
|
+
const combinedDrivers = { ...baseDrivers, ...drivers };
|
|
1230
|
+
|
|
1231
|
+
return run$1.run(wrapped, combinedDrivers)
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
/**
|
|
1235
|
+
* return a validated and properly separated string of CSS class names from any number of strings, arrays, and objects
|
|
1236
|
+
*
|
|
1237
|
+
* @param {...String|Array|Object} args any number of strings or arrays with valid CSS class names, or objects where the keys are valid class names and the values evaluate to true or false
|
|
1238
|
+
* @return {String} list of `active` classes separated by spaces
|
|
1239
|
+
*
|
|
1240
|
+
* any `string` or `array` arguments are simply validated and appended to the result
|
|
1241
|
+
* `objects` will evaluate the values (which can be booleans or functions), and the keys with `thruthy` values will be validated and appended to the result
|
|
1242
|
+
* this function makes it easier to set dynamic classes on HTML elements
|
|
1243
|
+
*/
|
|
1244
|
+
function classes(...args) {
|
|
1245
|
+
return args.reduce((acc, arg) => {
|
|
1246
|
+
if (typeof arg === 'string' && !acc.includes(arg)) {
|
|
1247
|
+
acc.push(...classes_processString(arg));
|
|
1248
|
+
} else if (Array.isArray(arg)) {
|
|
1249
|
+
acc.push(...classes_processArray(arg));
|
|
1250
|
+
} else if (typeof arg === 'object') {
|
|
1251
|
+
acc.push(...classes_processObject(arg));
|
|
1252
|
+
}
|
|
1253
|
+
return acc
|
|
1254
|
+
}, []).join(' ')
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
|
|
1258
|
+
|
|
1259
|
+
/**
|
|
1260
|
+
* validate a string as a CSS class name
|
|
1261
|
+
*
|
|
1262
|
+
* @param {String} className CSS class name to validate
|
|
1263
|
+
* @return {Boolean} true if the name is a valid CSS class, false otherwise
|
|
1264
|
+
*/
|
|
1265
|
+
function isValidClassName (className) {
|
|
1266
|
+
return /^[a-zA-Z0-9-_]+$/.test(className)
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
/**
|
|
1270
|
+
* find and validate CSS class names in a string
|
|
1271
|
+
*
|
|
1272
|
+
* @param {String} str string containing one or more CSS class names
|
|
1273
|
+
* @return {Array} valid CSS classnames from the provided string
|
|
1274
|
+
*/
|
|
1275
|
+
function classes_processString(str) {
|
|
1276
|
+
if (typeof str !== 'string') throw new Error('Class name must be a string')
|
|
1277
|
+
return str.trim().split(' ').reduce((acc, item) => {
|
|
1278
|
+
if (item.trim().length === 0) return acc
|
|
1279
|
+
if (!isValidClassName(item)) throw new Error(`${item} is not a valid CSS class name`)
|
|
1280
|
+
acc.push(item);
|
|
1281
|
+
return acc
|
|
1282
|
+
}, [])
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
/**
|
|
1286
|
+
* find and validate CSS class names in an array of strings
|
|
1287
|
+
*
|
|
1288
|
+
* @param {Array} arr array containing one or more strings with valid CSS class names
|
|
1289
|
+
* @return {Array} valid CSS class names from the provided array
|
|
1290
|
+
*/
|
|
1291
|
+
function classes_processArray(arr) {
|
|
1292
|
+
return arr.map(classes_processString).flat()
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
/**
|
|
1296
|
+
* find and validate CSS class names in an object, and exclude keys whose value evaluates to `false`
|
|
1297
|
+
*
|
|
1298
|
+
* @param {Object} obj object with keys as CSS class names and values which if `truthy` cause the associated key to be returned
|
|
1299
|
+
* @return {Array} valid CSS class names from the keys of the provided object where the associated value evaluated to `true`
|
|
1300
|
+
*
|
|
1301
|
+
* the value for each key can be either a value that evaluates to a boolean or a function that returns a boolean
|
|
1302
|
+
* if the value is a function, it will be run and the returned value will be used
|
|
1303
|
+
*/
|
|
1304
|
+
function classes_processObject(obj) {
|
|
1305
|
+
const ret = Object.entries(obj)
|
|
1306
|
+
.filter(([key, predicate]) => (typeof predicate === 'function') ? predicate() : !!predicate)
|
|
1307
|
+
.map(([key, _]) => {
|
|
1308
|
+
const trimmed = key.trim();
|
|
1309
|
+
if (!isValidClassName(trimmed)) throw new Error (`${trimmed} is not a valid CSS class name`)
|
|
1310
|
+
return trimmed
|
|
1311
|
+
});
|
|
1312
|
+
return ret
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
Object.defineProperty(exports, 'xs', {
|
|
1316
|
+
enumerable: true,
|
|
1317
|
+
get: function () { return xs__default["default"]; }
|
|
1318
|
+
});
|
|
1319
|
+
Object.defineProperty(exports, 'dropRepeats', {
|
|
1320
|
+
enumerable: true,
|
|
1321
|
+
get: function () { return dropRepeats__default["default"]; }
|
|
1322
|
+
});
|
|
1323
|
+
Object.defineProperty(exports, 'debounce', {
|
|
1324
|
+
enumerable: true,
|
|
1325
|
+
get: function () { return debounce__default["default"]; }
|
|
1326
|
+
});
|
|
1327
|
+
Object.defineProperty(exports, 'throttle', {
|
|
1328
|
+
enumerable: true,
|
|
1329
|
+
get: function () { return throttle__default["default"]; }
|
|
1330
|
+
});
|
|
1331
|
+
Object.defineProperty(exports, 'delay', {
|
|
1332
|
+
enumerable: true,
|
|
1333
|
+
get: function () { return delay__default["default"]; }
|
|
1334
|
+
});
|
|
1335
|
+
Object.defineProperty(exports, 'sampleCombine', {
|
|
1336
|
+
enumerable: true,
|
|
1337
|
+
get: function () { return sampleCombine__default["default"]; }
|
|
1338
|
+
});
|
|
1339
|
+
exports.ABORT = ABORT;
|
|
1340
|
+
exports.classes = classes;
|
|
1341
|
+
exports.collection = collection;
|
|
1342
|
+
exports.component = component;
|
|
1343
|
+
exports.processForm = processForm;
|
|
1344
|
+
exports.run = run;
|
|
1345
|
+
exports.switchable = switchable;
|
|
1346
|
+
Object.keys(dom).forEach(function (k) {
|
|
1347
|
+
if (k !== 'default' && !exports.hasOwnProperty(k)) Object.defineProperty(exports, k, {
|
|
1348
|
+
enumerable: true,
|
|
1349
|
+
get: function () { return dom[k]; }
|
|
1350
|
+
});
|
|
1351
|
+
});
|