@ixfx/ui 0.50.3 → 0.56.2
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/LICENSE +26 -1
- package/dist/chunk-BN_g-Awi.js +18 -0
- package/dist/index.d.ts +557 -0
- package/dist/index.js +958 -0
- package/package.json +32 -18
- package/bundle/chunk-Cl8Af3a2.js +0 -1
- package/bundle/index.d.ts +0 -569
- package/bundle/index.d.ts.map +0 -1
- package/bundle/index.js +0 -2
- package/bundle/index.js.map +0 -1
- package/dist/__tests__/test.d.ts +0 -2
- package/dist/__tests__/test.d.ts.map +0 -1
- package/dist/__tests__/test.js +0 -1
- package/dist/src/index.d.ts +0 -2
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/index.js +0 -1
- package/dist/src/rx/browser-resize.d.ts +0 -26
- package/dist/src/rx/browser-resize.d.ts.map +0 -1
- package/dist/src/rx/browser-resize.js +0 -41
- package/dist/src/rx/browser-theme-change.d.ts +0 -13
- package/dist/src/rx/browser-theme-change.d.ts.map +0 -1
- package/dist/src/rx/browser-theme-change.js +0 -28
- package/dist/src/rx/colour.d.ts +0 -8
- package/dist/src/rx/colour.d.ts.map +0 -1
- package/dist/src/rx/colour.js +0 -20
- package/dist/src/rx/dom-source.d.ts +0 -96
- package/dist/src/rx/dom-source.d.ts.map +0 -1
- package/dist/src/rx/dom-source.js +0 -374
- package/dist/src/rx/dom-types.d.ts +0 -128
- package/dist/src/rx/dom-types.d.ts.map +0 -1
- package/dist/src/rx/dom-types.js +0 -1
- package/dist/src/rx/dom.d.ts +0 -284
- package/dist/src/rx/dom.d.ts.map +0 -1
- package/dist/src/rx/dom.js +0 -727
- package/dist/src/rx/index.d.ts +0 -7
- package/dist/src/rx/index.d.ts.map +0 -1
- package/dist/src/rx/index.js +0 -5
- package/dist/tsconfig.tsbuildinfo +0 -1
package/dist/index.js
ADDED
|
@@ -0,0 +1,958 @@
|
|
|
1
|
+
import { t as __exportAll } from "./chunk-BN_g-Awi.js";
|
|
2
|
+
import * as RxFrom from "@ixfx/rx/from";
|
|
3
|
+
import { eventTrigger, observable } from "@ixfx/rx/from";
|
|
4
|
+
import { debounce } from "@ixfx/rx/ops";
|
|
5
|
+
import * as Rx from "@ixfx/rx";
|
|
6
|
+
import { hasLast, initStream, transform } from "@ixfx/rx";
|
|
7
|
+
import { observable as observable$1 } from "@ixfx/rx/from/observable";
|
|
8
|
+
import { resolveEl } from "@ixfx/dom";
|
|
9
|
+
import { Colour } from "@ixfx/visual";
|
|
10
|
+
import { Pathed } from "@ixfx/core";
|
|
11
|
+
import { findBySomeKey } from "@ixfx/core/maps";
|
|
12
|
+
import { afterMatch, beforeMatch, stringSegmentsWholeToEnd, stringSegmentsWholeToFirst } from "@ixfx/core/text";
|
|
13
|
+
import { QueueMutable } from "@ixfx/collections";
|
|
14
|
+
|
|
15
|
+
//#region src/rx/browser-resize.ts
|
|
16
|
+
/**
|
|
17
|
+
* Observe when element resizes. Specify `interval` to debounce, uses 100ms by default.
|
|
18
|
+
*
|
|
19
|
+
* ```
|
|
20
|
+
* const o = resizeObservable(myEl, 500);
|
|
21
|
+
* o.subscribe(() => {
|
|
22
|
+
* // called 500ms after last resize
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
* @param elem
|
|
26
|
+
* @param interval Tiemout before event gets triggered
|
|
27
|
+
* @returns
|
|
28
|
+
*/
|
|
29
|
+
const browserResizeObservable = (elem, interval) => {
|
|
30
|
+
if (elem === null) throw new Error(`Param 'elem' is null. Expected element to observe`);
|
|
31
|
+
if (elem === void 0) throw new Error(`Param 'elem' is undefined. Expected element to observe`);
|
|
32
|
+
const m = observable((stream) => {
|
|
33
|
+
const ro = new ResizeObserver((entries) => {
|
|
34
|
+
stream.set(entries);
|
|
35
|
+
});
|
|
36
|
+
ro.observe(elem);
|
|
37
|
+
return () => {
|
|
38
|
+
ro.unobserve(elem);
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
return debounce({ elapsed: interval ?? 100 })(m);
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Returns an Reactive for window resize. Default 100ms debounce.
|
|
45
|
+
* @param elapsed
|
|
46
|
+
* @returns
|
|
47
|
+
*/
|
|
48
|
+
const windowResize = (elapsed) => debounce({ elapsed: elapsed ?? 100 })(Rx.From.event(window, `resize`, {
|
|
49
|
+
innerWidth: 0,
|
|
50
|
+
innerHeight: 0
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/rx/browser-theme-change.ts
|
|
55
|
+
/**
|
|
56
|
+
* Observe when a class changes on a target element, by default the document.
|
|
57
|
+
* Useful for tracking theme changes.
|
|
58
|
+
*
|
|
59
|
+
* ```js
|
|
60
|
+
* const c = cssClassChange();
|
|
61
|
+
* c.on(msg => {
|
|
62
|
+
* // some class has changed on the document
|
|
63
|
+
* });
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
const cssClassChange = (target = document.documentElement) => {
|
|
67
|
+
return observable$1((stream) => {
|
|
68
|
+
const ro = new MutationObserver((entries) => {
|
|
69
|
+
stream.set(entries);
|
|
70
|
+
});
|
|
71
|
+
ro.observe(target, {
|
|
72
|
+
attributeFilter: [`class`],
|
|
73
|
+
attributes: true
|
|
74
|
+
});
|
|
75
|
+
return () => {
|
|
76
|
+
ro.disconnect();
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region src/rx/colour.ts
|
|
83
|
+
function colour(initialValue) {
|
|
84
|
+
let value = initialValue;
|
|
85
|
+
const events = initStream();
|
|
86
|
+
const set = (v) => {
|
|
87
|
+
value = v;
|
|
88
|
+
events.set(v);
|
|
89
|
+
};
|
|
90
|
+
return {
|
|
91
|
+
dispose: events.dispose,
|
|
92
|
+
isDisposed: events.isDisposed,
|
|
93
|
+
last: () => value,
|
|
94
|
+
on: events.on,
|
|
95
|
+
onValue: events.onValue,
|
|
96
|
+
set,
|
|
97
|
+
setHsl: (hsl) => {
|
|
98
|
+
set(hsl);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
//#endregion
|
|
104
|
+
//#region src/rx/dom-source.ts
|
|
105
|
+
/**
|
|
106
|
+
* Reactive getting/setting of values to a HTML INPUT element.
|
|
107
|
+
*
|
|
108
|
+
* Options:
|
|
109
|
+
* - relative: if _true_, values are 0..1 (default: false)
|
|
110
|
+
* - inverted: if _true_, values are 1..0 (default: false)
|
|
111
|
+
*
|
|
112
|
+
* If element is missing a 'type' attribute, this will be set to 'range'.
|
|
113
|
+
* @param targetOrQuery
|
|
114
|
+
* @param options
|
|
115
|
+
* @returns
|
|
116
|
+
*/
|
|
117
|
+
function domNumberInputValue(targetOrQuery, options = {}) {
|
|
118
|
+
const input = domInputValue(targetOrQuery, options);
|
|
119
|
+
const el = input.el;
|
|
120
|
+
const relative = options.relative ?? false;
|
|
121
|
+
const inverted = options.inverted ?? false;
|
|
122
|
+
const rx = transform(input, (v) => {
|
|
123
|
+
return Number.parseFloat(v);
|
|
124
|
+
});
|
|
125
|
+
if (relative) {
|
|
126
|
+
el.max = inverted ? "0" : "1";
|
|
127
|
+
el.min = inverted ? "1" : "0";
|
|
128
|
+
if (!el.hasAttribute(`step`)) el.step = "0.1";
|
|
129
|
+
}
|
|
130
|
+
if (el.getAttribute(`type`) === null) el.type = `range`;
|
|
131
|
+
const set = (value) => {
|
|
132
|
+
input.set(value.toString());
|
|
133
|
+
};
|
|
134
|
+
return {
|
|
135
|
+
...rx,
|
|
136
|
+
last() {
|
|
137
|
+
return Number.parseFloat(input.last());
|
|
138
|
+
},
|
|
139
|
+
set
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function domHslInputValue(targetOrQuery, options = {}) {
|
|
143
|
+
const input = domInputValue(targetOrQuery, {
|
|
144
|
+
...options,
|
|
145
|
+
upstreamFilter: (value) => {
|
|
146
|
+
return typeof value === `object` ? Colour.toCssColour(value) : value;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
return {
|
|
150
|
+
...transform(input, (v) => {
|
|
151
|
+
return Colour.HslSpace.fromCss(v, {
|
|
152
|
+
scalar: true,
|
|
153
|
+
ensureSafe: true
|
|
154
|
+
});
|
|
155
|
+
}),
|
|
156
|
+
last() {
|
|
157
|
+
return Colour.HslSpace.fromCss(input.last(), {
|
|
158
|
+
scalar: true,
|
|
159
|
+
ensureSafe: true
|
|
160
|
+
});
|
|
161
|
+
},
|
|
162
|
+
set(value) {
|
|
163
|
+
input.set(Colour.HslSpace.toCssString(value));
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* A stream of values when the a HTMLInputElement changes. Eg a <input type="range">
|
|
169
|
+
* ```js
|
|
170
|
+
* const r = Rx.From.domInputValue(`#myEl`);
|
|
171
|
+
* r.onValue(value => {
|
|
172
|
+
* // value will be string
|
|
173
|
+
* });
|
|
174
|
+
* ```
|
|
175
|
+
*
|
|
176
|
+
* Options:
|
|
177
|
+
* * emitInitialValue: If _true_ emits the HTML value of element (default: false)
|
|
178
|
+
* * attributeName: If set, this is the HTML attribute value is set to when writing to stream (default: 'value')
|
|
179
|
+
* * fieldName: If set, this is the DOM object field set when writing to stream (default: 'value')
|
|
180
|
+
* * when: 'changed'|'changing' when values are emitted. (default: 'changed')
|
|
181
|
+
* * fallbackValue: Fallback value to use if field/attribute cannot be read (default: '')
|
|
182
|
+
* @param targetOrQuery
|
|
183
|
+
* @param options
|
|
184
|
+
* @returns
|
|
185
|
+
*/
|
|
186
|
+
function domInputValue(targetOrQuery, options = {}) {
|
|
187
|
+
const target = typeof targetOrQuery === `string` ? document.querySelector(targetOrQuery) : targetOrQuery;
|
|
188
|
+
if (target === null && typeof targetOrQuery === `string`) throw new Error(`Element query could not be resolved '${targetOrQuery}'`);
|
|
189
|
+
if (target === null) throw new Error(`targetOrQuery is null`);
|
|
190
|
+
const el = resolveEl(targetOrQuery);
|
|
191
|
+
const eventName = (options.when ?? `changed`) === `changed` ? `change` : `input`;
|
|
192
|
+
const emitInitialValue = options.emitInitialValue ?? false;
|
|
193
|
+
const fallbackValue = options.fallbackValue ?? ``;
|
|
194
|
+
const upstreamSource = options.upstreamSource;
|
|
195
|
+
let upstreamSourceUnsub = () => {};
|
|
196
|
+
let attribName = options.attributeName;
|
|
197
|
+
let fieldName = options.fieldName;
|
|
198
|
+
if (fieldName === void 0 && attribName === void 0) attribName = fieldName = `value`;
|
|
199
|
+
const readValue = () => {
|
|
200
|
+
let value;
|
|
201
|
+
if (attribName) value = el.getAttribute(attribName);
|
|
202
|
+
if (fieldName) value = el[fieldName];
|
|
203
|
+
if (value === void 0 || value === null) value = fallbackValue;
|
|
204
|
+
return value;
|
|
205
|
+
};
|
|
206
|
+
const setValue = (value) => {
|
|
207
|
+
if (attribName) el.setAttribute(attribName, value);
|
|
208
|
+
if (fieldName) el[fieldName] = value;
|
|
209
|
+
};
|
|
210
|
+
const setUpstream = (v) => {
|
|
211
|
+
v = options.upstreamFilter ? options.upstreamFilter(v) : v;
|
|
212
|
+
setValue(v);
|
|
213
|
+
};
|
|
214
|
+
if (upstreamSource) {
|
|
215
|
+
upstreamSourceUnsub = upstreamSource.onValue(setUpstream);
|
|
216
|
+
if (hasLast(upstreamSource)) setUpstream(upstreamSource.last());
|
|
217
|
+
}
|
|
218
|
+
const rxEvents = eventTrigger(el, eventName, {
|
|
219
|
+
fireInitial: emitInitialValue,
|
|
220
|
+
debugFiring: options.debugFiring ?? false,
|
|
221
|
+
debugLifecycle: options.debugLifecycle ?? false
|
|
222
|
+
});
|
|
223
|
+
const rxValues = transform(rxEvents, (_trigger) => readValue());
|
|
224
|
+
return {
|
|
225
|
+
...rxValues,
|
|
226
|
+
el,
|
|
227
|
+
last() {
|
|
228
|
+
return readValue();
|
|
229
|
+
},
|
|
230
|
+
set(value) {
|
|
231
|
+
setValue(value);
|
|
232
|
+
},
|
|
233
|
+
dispose(reason) {
|
|
234
|
+
upstreamSourceUnsub();
|
|
235
|
+
rxValues.dispose(reason);
|
|
236
|
+
rxEvents.dispose(reason);
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Listens for data changes from elements within a HTML form element.
|
|
242
|
+
* Input elements must have a 'name' attribute.
|
|
243
|
+
*
|
|
244
|
+
* Simple usage:
|
|
245
|
+
* ```js
|
|
246
|
+
* const rx = Rx.From.domForm(`#my-form`);
|
|
247
|
+
* rx.onValue(value => {
|
|
248
|
+
* // Object containing values from form
|
|
249
|
+
* });
|
|
250
|
+
*
|
|
251
|
+
* rx.last(); // Read current values of form
|
|
252
|
+
* ```
|
|
253
|
+
*
|
|
254
|
+
* UI can be updated
|
|
255
|
+
* ```js
|
|
256
|
+
* // Set using an object of key-value pairs
|
|
257
|
+
* rx.set({
|
|
258
|
+
* size: 'large'
|
|
259
|
+
* });
|
|
260
|
+
*
|
|
261
|
+
* // Or set a single name-value pair
|
|
262
|
+
* rx.setNamedValue(`size`, `large`);
|
|
263
|
+
* ```
|
|
264
|
+
*
|
|
265
|
+
* If an 'upstream' reactive is provided, this is used to set initial values of the UI, overriding
|
|
266
|
+
* whatever may be in the HTML. Upstream changes modify UI elements, but UI changes do not modify the upstream
|
|
267
|
+
* source.
|
|
268
|
+
*
|
|
269
|
+
* ```js
|
|
270
|
+
* // Create a reactive object
|
|
271
|
+
* const obj = Rx.From.object({
|
|
272
|
+
* when: `2024-10-03`,
|
|
273
|
+
* size: 12,
|
|
274
|
+
* checked: true
|
|
275
|
+
* });
|
|
276
|
+
*
|
|
277
|
+
* // Use this as initial values for a HTML form
|
|
278
|
+
* // (assuming appropriate INPUT/SELECT elements exist)
|
|
279
|
+
* const rx = Rx.From.domForm(`form`, {
|
|
280
|
+
* upstreamSource: obj
|
|
281
|
+
* });
|
|
282
|
+
*
|
|
283
|
+
* // Listen for changes in the UI
|
|
284
|
+
* rx.onValue(value => {
|
|
285
|
+
*
|
|
286
|
+
* });
|
|
287
|
+
* ```
|
|
288
|
+
* @param formElOrQuery
|
|
289
|
+
* @param options
|
|
290
|
+
* @returns
|
|
291
|
+
*/
|
|
292
|
+
function domForm(formElOrQuery, options = {}) {
|
|
293
|
+
const formEl = resolveEl(formElOrQuery);
|
|
294
|
+
const eventName = (options.when ?? `changed`) === `changed` ? `change` : `input`;
|
|
295
|
+
const emitInitialValue = options.emitInitialValue ?? false;
|
|
296
|
+
const upstreamSource = options.upstreamSource;
|
|
297
|
+
const typeHints = /* @__PURE__ */ new Map();
|
|
298
|
+
let upstreamSourceUnsub = () => {};
|
|
299
|
+
const readValue = () => {
|
|
300
|
+
const fd = new FormData(formEl);
|
|
301
|
+
const entries = [];
|
|
302
|
+
for (const [k, v] of fd.entries()) {
|
|
303
|
+
const vString = v.toString();
|
|
304
|
+
let typeHint = typeHints.get(k);
|
|
305
|
+
if (!typeHint) {
|
|
306
|
+
const el = getFormElement(k, vString);
|
|
307
|
+
if (el) {
|
|
308
|
+
if (el.type === `range` || el.type === `number`) typeHint = `number`;
|
|
309
|
+
else if (el.type === `color`) typeHint = `colour`;
|
|
310
|
+
else if (el.type === `checkbox` && (v === `true` || v === `on`)) typeHint = `boolean`;
|
|
311
|
+
else typeHint = `string`;
|
|
312
|
+
typeHints.set(k, typeHint);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (typeHint === `number`) entries.push([k, Number.parseFloat(vString)]);
|
|
316
|
+
else if (typeHint === `boolean`) {
|
|
317
|
+
const vBool = vString === `true` ? true : false;
|
|
318
|
+
entries.push([k, vBool]);
|
|
319
|
+
} else if (typeHint === `colour`) {
|
|
320
|
+
const vRgb = Colour.toCssColour(vString);
|
|
321
|
+
entries.push([k, Colour.SrgbSpace.fromCss(vRgb, { scalar: false })]);
|
|
322
|
+
} else entries.push([k, v.toString()]);
|
|
323
|
+
}
|
|
324
|
+
for (const el of formEl.querySelectorAll(`input[type="checkbox"]`)) if (!el.checked && el.value === `true`) entries.push([el.name, false]);
|
|
325
|
+
return Object.fromEntries(entries);
|
|
326
|
+
};
|
|
327
|
+
const getFormElement = (name, value) => {
|
|
328
|
+
const el = formEl.querySelector(`[name="${name}"]`);
|
|
329
|
+
if (!el) {
|
|
330
|
+
console.warn(`Form does not contain an element with name="${name}"`);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (el.type === `radio`) {
|
|
334
|
+
const radioEl = formEl.querySelector(`[name="${name}"][value="${value}"]`);
|
|
335
|
+
if (!radioEl) {
|
|
336
|
+
console.warn(`Form does not contain radio option for name=${name} value=${value}`);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
return radioEl;
|
|
340
|
+
}
|
|
341
|
+
return el;
|
|
342
|
+
};
|
|
343
|
+
const setNamedValue = (name, value) => {
|
|
344
|
+
const el = getFormElement(name, value);
|
|
345
|
+
if (!el) return;
|
|
346
|
+
if (el.nodeName === `INPUT` || el.nodeName === `SELECT`) {
|
|
347
|
+
if (el.type === `color`) {
|
|
348
|
+
if (typeof value === `object`) value = Colour.toCssColour(value);
|
|
349
|
+
} else if (el.type === `checkbox`) if (typeof value === `boolean`) {
|
|
350
|
+
el.checked = value;
|
|
351
|
+
return;
|
|
352
|
+
} else console.warn(`Rx.Sources.domForm: Trying to set non boolean type to a checkbox. Name: ${name} Value: ${value} (${typeof value})`);
|
|
353
|
+
else if (el.type === `radio`) {
|
|
354
|
+
el.checked = true;
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
el.value = value;
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
const setFromUpstream = (value) => {
|
|
361
|
+
for (const [name, v] of Object.entries(value)) {
|
|
362
|
+
let hint = typeHints.get(name);
|
|
363
|
+
if (!hint) {
|
|
364
|
+
hint = typeof v;
|
|
365
|
+
if (hint === `object`) {
|
|
366
|
+
Colour.toColour(v);
|
|
367
|
+
hint = `colour`;
|
|
368
|
+
}
|
|
369
|
+
typeHints.set(name, hint);
|
|
370
|
+
}
|
|
371
|
+
setNamedValue(name, options.upstreamFilter ? options.upstreamFilter(name, v) : v);
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
if (upstreamSource) {
|
|
375
|
+
upstreamSourceUnsub = upstreamSource.onValue(setFromUpstream);
|
|
376
|
+
if (hasLast(upstreamSource)) setFromUpstream(upstreamSource.last());
|
|
377
|
+
}
|
|
378
|
+
const rxEvents = eventTrigger(formEl, eventName, {
|
|
379
|
+
fireInitial: emitInitialValue,
|
|
380
|
+
debugFiring: options.debugFiring ?? false,
|
|
381
|
+
debugLifecycle: options.debugLifecycle ?? false
|
|
382
|
+
});
|
|
383
|
+
const rxValues = transform(rxEvents, (_trigger) => readValue());
|
|
384
|
+
return {
|
|
385
|
+
...rxValues,
|
|
386
|
+
el: formEl,
|
|
387
|
+
last() {
|
|
388
|
+
return readValue();
|
|
389
|
+
},
|
|
390
|
+
set: setFromUpstream,
|
|
391
|
+
setNamedValue,
|
|
392
|
+
dispose(reason) {
|
|
393
|
+
upstreamSourceUnsub();
|
|
394
|
+
rxValues.dispose(reason);
|
|
395
|
+
rxEvents.dispose(reason);
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
//#endregion
|
|
401
|
+
//#region src/rx/dom.ts
|
|
402
|
+
/**
|
|
403
|
+
* Reactive stream of array of elements that match `query`.
|
|
404
|
+
* @param query
|
|
405
|
+
* @returns
|
|
406
|
+
*/
|
|
407
|
+
function fromDomQuery(query) {
|
|
408
|
+
const elements = [...document.querySelectorAll(query)];
|
|
409
|
+
return Rx.From.object(elements);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Updates an element's `textContent` when the source value changes.
|
|
413
|
+
* ```js
|
|
414
|
+
* bindText(source, `#blah`);
|
|
415
|
+
* ```
|
|
416
|
+
* @param elOrQuery
|
|
417
|
+
* @param source
|
|
418
|
+
* @param bindOpts
|
|
419
|
+
*/
|
|
420
|
+
const bindText = (source, elOrQuery, bindOpts = {}) => {
|
|
421
|
+
return bindElement(source, elOrQuery, {
|
|
422
|
+
...bindOpts,
|
|
423
|
+
elField: `textContent`
|
|
424
|
+
});
|
|
425
|
+
};
|
|
426
|
+
/**
|
|
427
|
+
* Updates an element's `value` (as well as the 'value' attribute) when the source value changes.s
|
|
428
|
+
* @param source
|
|
429
|
+
* @param elOrQuery
|
|
430
|
+
* @param bindOpts
|
|
431
|
+
* @returns
|
|
432
|
+
*/
|
|
433
|
+
const bindValueText = (source, elOrQuery, bindOpts = {}) => {
|
|
434
|
+
return bindElement(source, elOrQuery, {
|
|
435
|
+
...bindOpts,
|
|
436
|
+
elField: `value`,
|
|
437
|
+
attribName: `value`
|
|
438
|
+
});
|
|
439
|
+
};
|
|
440
|
+
/**
|
|
441
|
+
* Updates an element's `innerHTML` when the source value changes
|
|
442
|
+
* ```js
|
|
443
|
+
* bindHtml(source, `#blah`);
|
|
444
|
+
* ```
|
|
445
|
+
*
|
|
446
|
+
* Uses {@link bindElement}, with `{elField:'innerHTML'}` as the options.
|
|
447
|
+
* @param elOrQuery
|
|
448
|
+
* @param source
|
|
449
|
+
* @param bindOpts
|
|
450
|
+
* @returns
|
|
451
|
+
*/
|
|
452
|
+
const bindHtml = (source, elOrQuery, bindOpts = {}) => {
|
|
453
|
+
return bindElement(source, elOrQuery, {
|
|
454
|
+
...bindOpts,
|
|
455
|
+
elField: `innerHTML`
|
|
456
|
+
});
|
|
457
|
+
};
|
|
458
|
+
/**
|
|
459
|
+
* Shortcut to bind to an elements attribute
|
|
460
|
+
* @param elOrQuery
|
|
461
|
+
* @param source
|
|
462
|
+
* @param attribute
|
|
463
|
+
* @param bindOpts
|
|
464
|
+
* @returns
|
|
465
|
+
*/
|
|
466
|
+
/**
|
|
467
|
+
* Shortcut to bind to a CSS variable
|
|
468
|
+
* @param elOrQuery
|
|
469
|
+
* @param source
|
|
470
|
+
* @param cssVariable
|
|
471
|
+
* @param bindOpts
|
|
472
|
+
* @returns
|
|
473
|
+
*/
|
|
474
|
+
/**
|
|
475
|
+
* Creates a new HTML element, calling {@link bind} on it to update when `source` emits new values.
|
|
476
|
+
*
|
|
477
|
+
*
|
|
478
|
+
* ```js
|
|
479
|
+
* // Set textContent of a SPAN with values from `source`
|
|
480
|
+
* create(source, { tagName: `span`, parentEl: document.body })
|
|
481
|
+
* ```
|
|
482
|
+
*
|
|
483
|
+
* If `parentEl` is not given in the options, the created element needs to be manually added
|
|
484
|
+
* ```js
|
|
485
|
+
* const b = create(source);
|
|
486
|
+
* someEl.append(b.el); // Append manually
|
|
487
|
+
* ```
|
|
488
|
+
*
|
|
489
|
+
* ```
|
|
490
|
+
* // Set 'title' attribute based on values from `source`
|
|
491
|
+
* create(source, { parentEl: document.body, attribName: `title` })
|
|
492
|
+
* ```
|
|
493
|
+
* @param source
|
|
494
|
+
* @param options
|
|
495
|
+
* @returns
|
|
496
|
+
*/
|
|
497
|
+
/**
|
|
498
|
+
* Update a DOM element's field, attribute or CSS variable when `source` produces a value.
|
|
499
|
+
*
|
|
500
|
+
* ```js
|
|
501
|
+
* // Access via DOM query. Binds to 'textContent' by default
|
|
502
|
+
* bind(readableSource, `#someEl`);
|
|
503
|
+
*
|
|
504
|
+
* // Set innerHTML instead
|
|
505
|
+
* bind(readableSource, someEl, { elField: `innerHTML` });
|
|
506
|
+
*
|
|
507
|
+
* // An attribute
|
|
508
|
+
* bind(readableSource, someEl, { attribName: `width` });
|
|
509
|
+
*
|
|
510
|
+
* // A css variable ('--' optiona)
|
|
511
|
+
* bind(readableSource, someEl, { cssVariable: `hue` });
|
|
512
|
+
*
|
|
513
|
+
* // Pluck a particular field from source data.
|
|
514
|
+
* // Ie someEl.textContent = value.colour
|
|
515
|
+
* bind(readableSource, someEl, { sourceField: `colour` });
|
|
516
|
+
*
|
|
517
|
+
* // Transform value before setting it to field
|
|
518
|
+
* bind(readableSource, someEl, {
|
|
519
|
+
* field: `innerHTML`,
|
|
520
|
+
* transform: (v) => `Colour: ${v.colour}`
|
|
521
|
+
* })
|
|
522
|
+
* ```
|
|
523
|
+
*
|
|
524
|
+
* If `source` has an initial value, this is used when first bound.
|
|
525
|
+
*
|
|
526
|
+
* Returns {@link PipeDomBinding} to control binding:
|
|
527
|
+
* ```js
|
|
528
|
+
* const bind = bind(source, `#someEl`);
|
|
529
|
+
* bind.remove(); // Unbind
|
|
530
|
+
* bind.remove(true); // Unbind and remove HTML element
|
|
531
|
+
* ```
|
|
532
|
+
*
|
|
533
|
+
* If several fields need to be updated based on a new value, consider using {@link bindUpdate} instead.
|
|
534
|
+
* @param elOrQuery Element to update to, or query string such as '#someid'
|
|
535
|
+
* @param source Source of data
|
|
536
|
+
* @param binds Bindings
|
|
537
|
+
*/
|
|
538
|
+
const bindElement = (source, elOrQuery, ...binds) => {
|
|
539
|
+
if (elOrQuery === null) throw new Error(`Param 'elOrQuery' is null`);
|
|
540
|
+
if (elOrQuery === void 0) throw new Error(`Param 'elOrQuery' is undefined`);
|
|
541
|
+
const el = resolveEl(elOrQuery);
|
|
542
|
+
let b = [];
|
|
543
|
+
if (binds.length === 0) b.push({ elField: `textContent` });
|
|
544
|
+
else b = [...binds];
|
|
545
|
+
return bind(source, ...b.map((bind) => {
|
|
546
|
+
if (`element` in bind) return bind;
|
|
547
|
+
return {
|
|
548
|
+
...bind,
|
|
549
|
+
element: el
|
|
550
|
+
};
|
|
551
|
+
}));
|
|
552
|
+
};
|
|
553
|
+
const resolveBindUpdater = (bind, element) => {
|
|
554
|
+
const b = resolveBindUpdaterBase(bind);
|
|
555
|
+
return (value) => {
|
|
556
|
+
b(value, element);
|
|
557
|
+
};
|
|
558
|
+
};
|
|
559
|
+
const resolveBindUpdaterBase = (bind) => {
|
|
560
|
+
if (bind.elField !== void 0 || bind.cssVariable === void 0 && bind.attribName === void 0 && bind.cssProperty === void 0 && bind.textContent === void 0 && bind.htmlContent === void 0) {
|
|
561
|
+
const field = bind.elField ?? `textContent`;
|
|
562
|
+
return (v, element) => {
|
|
563
|
+
element[field] = v;
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
if (bind.attribName !== void 0) {
|
|
567
|
+
const attrib = bind.attribName;
|
|
568
|
+
return (v, element) => {
|
|
569
|
+
element.setAttribute(attrib, v);
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
if (bind.textContent) return (v, element) => {
|
|
573
|
+
element.textContent = v;
|
|
574
|
+
};
|
|
575
|
+
if (bind.htmlContent) return (v, element) => {
|
|
576
|
+
element.innerHTML = v;
|
|
577
|
+
};
|
|
578
|
+
if (bind.cssVariable !== void 0) {
|
|
579
|
+
let css = bind.cssVariable;
|
|
580
|
+
if (!css.startsWith(`--`)) css = `--` + css;
|
|
581
|
+
return (v, element) => {
|
|
582
|
+
element.style.setProperty(css, v);
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
if (bind.cssProperty !== void 0) return (v, element) => {
|
|
586
|
+
element.style[bind.cssProperty] = v;
|
|
587
|
+
};
|
|
588
|
+
return (_, _element) => {
|
|
589
|
+
/** no-op */
|
|
590
|
+
};
|
|
591
|
+
};
|
|
592
|
+
const resolveTransform = (bind) => {
|
|
593
|
+
if (!bind.transform && !bind.transformValue) return;
|
|
594
|
+
if (bind.transformValue) {
|
|
595
|
+
if (bind.sourceField === void 0) throw new Error(`Expects 'sourceField' to be set when 'transformValue' is set`);
|
|
596
|
+
return (value) => {
|
|
597
|
+
const fieldValue = value[bind.sourceField];
|
|
598
|
+
return bind.transformValue(fieldValue);
|
|
599
|
+
};
|
|
600
|
+
} else if (bind.transform) {
|
|
601
|
+
if (bind.sourceField !== void 0) throw new Error(`If 'transform' is set, 'sourceField' is ignored`);
|
|
602
|
+
return (value) => bind.transform(value);
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
/**
|
|
606
|
+
* Binds `source` to one or more element(s). One or more bindings for the same source
|
|
607
|
+
* can be provided.
|
|
608
|
+
*
|
|
609
|
+
* ```js
|
|
610
|
+
* bind(source,
|
|
611
|
+
* // Binds .name field of source values to textContent of #some-element
|
|
612
|
+
* { query: `#some-element`, sourceField: `name` },
|
|
613
|
+
* { query: `section`, }
|
|
614
|
+
* );
|
|
615
|
+
* ```
|
|
616
|
+
*
|
|
617
|
+
* Can update
|
|
618
|
+
* * CSS variables
|
|
619
|
+
* * CSS styles
|
|
620
|
+
* * textContent / innerHTML
|
|
621
|
+
* * HTML DOM attributes and object fields
|
|
622
|
+
*
|
|
623
|
+
* Can use a particular field on source values, or use the whole value. These can
|
|
624
|
+
* pass through `transformValue` or `transform` respectively.
|
|
625
|
+
*
|
|
626
|
+
* Returns a function to unbind from source and optionally remove HTML element
|
|
627
|
+
* ```js
|
|
628
|
+
* const unbind = bind( . . . );
|
|
629
|
+
* unbind(); // Unbind
|
|
630
|
+
* unbind(true); // Unbind and remove HTML element(s)
|
|
631
|
+
* ```
|
|
632
|
+
* @param source
|
|
633
|
+
* @param bindsUnresolvedElements
|
|
634
|
+
* @returns
|
|
635
|
+
*/
|
|
636
|
+
const bind = (source, ...bindsUnresolvedElements) => {
|
|
637
|
+
const binds = bindsUnresolvedElements.map((bind) => {
|
|
638
|
+
if (bind.element && bind.element !== void 0) return bind;
|
|
639
|
+
if (bind.query) return {
|
|
640
|
+
...bind,
|
|
641
|
+
element: resolveEl(bind.query)
|
|
642
|
+
};
|
|
643
|
+
throw new Error(`Unable to resolve element. Missing 'element' or 'query' values on bind. ${JSON.stringify(bind)}`);
|
|
644
|
+
});
|
|
645
|
+
const bindsResolved = binds.map((bind) => ({
|
|
646
|
+
update: resolveBindUpdater(bind, bind.element),
|
|
647
|
+
transformer: resolveTransform(bind),
|
|
648
|
+
sourceField: bind.sourceField
|
|
649
|
+
}));
|
|
650
|
+
const update = (value) => {
|
|
651
|
+
for (const bind of bindsResolved) if (bind.transformer) bind.update(bind.transformer(value));
|
|
652
|
+
else {
|
|
653
|
+
const v = bind.sourceField ? value[bind.sourceField] : value;
|
|
654
|
+
if (typeof v === `object`) if (bind.sourceField) bind.update(JSON.stringify(v));
|
|
655
|
+
else bind.update(JSON.stringify(v));
|
|
656
|
+
else bind.update(v);
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
const unsub = source.on((message) => {
|
|
660
|
+
if (Rx.messageHasValue(message)) update(message.value);
|
|
661
|
+
else if (Rx.messageIsSignal(message)) console.warn(message);
|
|
662
|
+
});
|
|
663
|
+
if (Rx.hasLast(source)) update(source.last());
|
|
664
|
+
return { remove: (removeElements) => {
|
|
665
|
+
unsub();
|
|
666
|
+
if (removeElements) for (const bind of binds) bind.element.remove();
|
|
667
|
+
} };
|
|
668
|
+
};
|
|
669
|
+
/**
|
|
670
|
+
* Calls `updater` whenever `source` produces a value. Useful when several fields from a value
|
|
671
|
+
* are needed to update an element.
|
|
672
|
+
* ```js
|
|
673
|
+
* bindUpdate(source, `#someEl`, (v, el) => {
|
|
674
|
+
* el.setAttribute(`width`, v.width);
|
|
675
|
+
* el.setAttribute(`height`, v.height);
|
|
676
|
+
* });
|
|
677
|
+
* ```
|
|
678
|
+
*
|
|
679
|
+
* Returns a {@link PipeDomBinding} to manage binding
|
|
680
|
+
* ```js
|
|
681
|
+
* const b = bindUpdate(...);
|
|
682
|
+
* b.remove(); // Disconnect binding
|
|
683
|
+
* b.remove(true); // Disconnect binding and remove element
|
|
684
|
+
* b.el; // HTML element
|
|
685
|
+
* ```
|
|
686
|
+
* @param elOrQuery
|
|
687
|
+
* @param source
|
|
688
|
+
* @param updater
|
|
689
|
+
* @returns
|
|
690
|
+
*/
|
|
691
|
+
const bindUpdate = (source, elOrQuery, updater) => {
|
|
692
|
+
const el = resolveEl(elOrQuery);
|
|
693
|
+
const update = (value) => {
|
|
694
|
+
updater(value, el);
|
|
695
|
+
};
|
|
696
|
+
const unsub = source.on((message) => {
|
|
697
|
+
if (Rx.messageHasValue(message)) {
|
|
698
|
+
console.log(message);
|
|
699
|
+
update(message.value);
|
|
700
|
+
} else console.warn(message);
|
|
701
|
+
});
|
|
702
|
+
if (Rx.hasLast(source)) update(source.last());
|
|
703
|
+
return { remove: (removeElement) => {
|
|
704
|
+
unsub();
|
|
705
|
+
if (removeElement) el.remove();
|
|
706
|
+
} };
|
|
707
|
+
};
|
|
708
|
+
/**
|
|
709
|
+
* Updates a HTML element based on diffs on an object.
|
|
710
|
+
* ```js
|
|
711
|
+
* // Wrap an object
|
|
712
|
+
* const o = Rx.object({ name: `Jane`, ticks: 0 });
|
|
713
|
+
* const b = bindDiffUpdate(`#test`, o, (diffs, el) => {
|
|
714
|
+
* // el = reference to #test
|
|
715
|
+
* // diff = Array of Changes,
|
|
716
|
+
* // eg [ { path: `ticks`, value: 797, previous: 0 } ]
|
|
717
|
+
* for (const diff of diffs) {
|
|
718
|
+
* if (diff.path === `ticks`) el.textContent = `${diff.previous} -> ${diff.value}`
|
|
719
|
+
* }
|
|
720
|
+
* })
|
|
721
|
+
*
|
|
722
|
+
* // Eg. update field
|
|
723
|
+
* o.updateField(`ticks`, Math.floor(Math.random()*1000));
|
|
724
|
+
* ```
|
|
725
|
+
*
|
|
726
|
+
* If `initial` is provided as an option, this will be called if `source` has an initial value. Without this, the DOM won't be updated until the first data
|
|
727
|
+
* update happens.
|
|
728
|
+
* ```js
|
|
729
|
+
* bindDiffUpdate(el, source, updater, {
|
|
730
|
+
* initial: (v, el) => {
|
|
731
|
+
* el.innerHTML = v.name;
|
|
732
|
+
* }
|
|
733
|
+
* })
|
|
734
|
+
* ```
|
|
735
|
+
* @param elOrQuery
|
|
736
|
+
* @param source
|
|
737
|
+
* @param updater
|
|
738
|
+
* @param opts
|
|
739
|
+
* @returns
|
|
740
|
+
*/
|
|
741
|
+
const bindDiffUpdate = (source, elOrQuery, updater, opts = {}) => {
|
|
742
|
+
if (elOrQuery === null) throw new Error(`Param 'elOrQuery' is null`);
|
|
743
|
+
if (elOrQuery === void 0) throw new Error(`Param 'elOrQuery' is undefined`);
|
|
744
|
+
const el = resolveEl(elOrQuery);
|
|
745
|
+
const update = (value) => {
|
|
746
|
+
updater(value, el);
|
|
747
|
+
};
|
|
748
|
+
const unsub = source.onDiff((value) => {
|
|
749
|
+
update(value);
|
|
750
|
+
});
|
|
751
|
+
const init = () => {
|
|
752
|
+
if (Rx.hasLast(source) && opts.initial) opts.initial(source.last(), el);
|
|
753
|
+
};
|
|
754
|
+
init();
|
|
755
|
+
return {
|
|
756
|
+
refresh: () => {
|
|
757
|
+
init();
|
|
758
|
+
},
|
|
759
|
+
remove: (removeElement) => {
|
|
760
|
+
unsub();
|
|
761
|
+
if (removeElement) el.remove();
|
|
762
|
+
}
|
|
763
|
+
};
|
|
764
|
+
};
|
|
765
|
+
/**
|
|
766
|
+
* Creates a new HTML element and calls `bindUpdate` so values from `source` can be used
|
|
767
|
+
* to update it.
|
|
768
|
+
*
|
|
769
|
+
*
|
|
770
|
+
* ```js
|
|
771
|
+
* // Creates a span, adding it to <body>
|
|
772
|
+
* const b = createUpdate(dataSource, (value, el) => {
|
|
773
|
+
* el.width = value.width;
|
|
774
|
+
* el.height = value.height;
|
|
775
|
+
* }, {
|
|
776
|
+
* tagName: `SPAN`,
|
|
777
|
+
* parentEl: document.body
|
|
778
|
+
* })
|
|
779
|
+
* ```
|
|
780
|
+
* @param source
|
|
781
|
+
* @param updater
|
|
782
|
+
* @param options
|
|
783
|
+
* @returns
|
|
784
|
+
*/
|
|
785
|
+
/**
|
|
786
|
+
* Creates, updates & deletes elements based on pathed values from a reactive.
|
|
787
|
+
*
|
|
788
|
+
* This means that elements are only manipulated if its associated data changes,
|
|
789
|
+
* and elements are not modified if there's no need to.
|
|
790
|
+
* @param source
|
|
791
|
+
* @param options
|
|
792
|
+
*/
|
|
793
|
+
const elements = (source, options) => {
|
|
794
|
+
const containerEl = options.container ? resolveEl(options.container) : document.body;
|
|
795
|
+
const defaultTag = options.defaultTag ?? `div`;
|
|
796
|
+
const elByField = /* @__PURE__ */ new Map();
|
|
797
|
+
const binds = /* @__PURE__ */ new Map();
|
|
798
|
+
for (const [key, value] of Object.entries(options.binds ?? {})) {
|
|
799
|
+
const tagName = value.tagName ?? defaultTag;
|
|
800
|
+
binds.set(key, {
|
|
801
|
+
...value,
|
|
802
|
+
update: resolveBindUpdaterBase(value),
|
|
803
|
+
transform: resolveTransform(value),
|
|
804
|
+
tagName,
|
|
805
|
+
path: key
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
const findBind = (path) => {
|
|
809
|
+
const bind = findBySomeKey(binds, stringSegmentsWholeToEnd(path));
|
|
810
|
+
if (bind !== void 0) return bind;
|
|
811
|
+
if (!path.includes(`.`)) return binds.get(`_root`);
|
|
812
|
+
};
|
|
813
|
+
function* ancestorBinds(path) {
|
|
814
|
+
for (const p of stringSegmentsWholeToFirst(path)) if (binds.has(p)) yield binds.get(p);
|
|
815
|
+
if (binds.has(`_root`) && path.includes(`.`)) yield binds.get(`_root`);
|
|
816
|
+
}
|
|
817
|
+
const create = (path, value) => {
|
|
818
|
+
const rootedPath = getRootedPath(path);
|
|
819
|
+
console.log(`Rx.Dom.elements.create: ${path} rooted: ${rootedPath} value: ${JSON.stringify(value)}`);
|
|
820
|
+
const bind = findBind(getRootedPath(path));
|
|
821
|
+
let tagName = defaultTag;
|
|
822
|
+
if (bind?.tagName) tagName = bind.tagName;
|
|
823
|
+
const el = document.createElement(tagName);
|
|
824
|
+
el.setAttribute(`data-path`, path);
|
|
825
|
+
update(path, el, value);
|
|
826
|
+
let parentForEl;
|
|
827
|
+
for (const b of ancestorBinds(rootedPath)) if (b?.nestChildren) {
|
|
828
|
+
const absoluteRoot = beforeMatch(path, `.`);
|
|
829
|
+
const findBy = b.path.replace(`_root`, absoluteRoot);
|
|
830
|
+
parentForEl = elByField.get(findBy);
|
|
831
|
+
if (parentForEl === void 0) {} else break;
|
|
832
|
+
}
|
|
833
|
+
(parentForEl ?? containerEl).append(el);
|
|
834
|
+
elByField.set(path, el);
|
|
835
|
+
console.log(`Added el: ${path}`);
|
|
836
|
+
};
|
|
837
|
+
const update = (path, el, value) => {
|
|
838
|
+
console.log(`Rx.dom.update path: ${path} value:`, value);
|
|
839
|
+
const bind = findBind(getRootedPath(path));
|
|
840
|
+
if (bind === void 0) {
|
|
841
|
+
if (typeof value === `object`) value = JSON.stringify(value);
|
|
842
|
+
el.textContent = value;
|
|
843
|
+
} else {
|
|
844
|
+
if (bind.transform) value = bind.transform(value);
|
|
845
|
+
bind.update(value, el);
|
|
846
|
+
}
|
|
847
|
+
};
|
|
848
|
+
const changes = (changes) => {
|
|
849
|
+
const queue = new QueueMutable({}, changes);
|
|
850
|
+
let d = queue.dequeue();
|
|
851
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
852
|
+
while (d !== void 0) {
|
|
853
|
+
const path = d.path;
|
|
854
|
+
if (!(`previous` in d) || d.previous === void 0) {
|
|
855
|
+
console.log(`Rx.Dom.elements.changes no previous. path: ${path}`);
|
|
856
|
+
create(path, d.value);
|
|
857
|
+
const subdata = [...Pathed.getPathsAndData(d.value, false, Number.MAX_SAFE_INTEGER, path)];
|
|
858
|
+
console.log(subdata);
|
|
859
|
+
for (const dd of subdata) if (!seenPaths.has(dd.path)) {
|
|
860
|
+
queue.enqueue(dd);
|
|
861
|
+
seenPaths.add(dd.path);
|
|
862
|
+
}
|
|
863
|
+
} else if (d.value === void 0) {
|
|
864
|
+
const el = elByField.get(path);
|
|
865
|
+
if (el === void 0) console.warn(`No element to delete? ${path} `);
|
|
866
|
+
else {
|
|
867
|
+
console.log(`Rx.Dom.elements.changes delete ${path}`);
|
|
868
|
+
el.remove();
|
|
869
|
+
}
|
|
870
|
+
} else {
|
|
871
|
+
const el = elByField.get(path);
|
|
872
|
+
if (el === void 0) {
|
|
873
|
+
console.warn(`Rx.Dom.elements.changes No element to update ? ${path} `);
|
|
874
|
+
create(path, d.value);
|
|
875
|
+
} else update(path, el, d.value);
|
|
876
|
+
}
|
|
877
|
+
d = queue.dequeue();
|
|
878
|
+
}
|
|
879
|
+
};
|
|
880
|
+
/**
|
|
881
|
+
* Source has changed
|
|
882
|
+
*/
|
|
883
|
+
source.onDiff((value) => {
|
|
884
|
+
changes(value);
|
|
885
|
+
});
|
|
886
|
+
if (Rx.hasLast(source)) {
|
|
887
|
+
const last = source.last();
|
|
888
|
+
changes([...Pathed.getPathsAndData(last, false, 1)]);
|
|
889
|
+
}
|
|
890
|
+
};
|
|
891
|
+
/**
|
|
892
|
+
* Replaces the root portion of `path` with the magic keyword `_root`
|
|
893
|
+
* @param path
|
|
894
|
+
* @returns
|
|
895
|
+
*/
|
|
896
|
+
const getRootedPath = (path) => {
|
|
897
|
+
const after = afterMatch(path, `.`);
|
|
898
|
+
return after === path ? `_root` : `_root.` + after;
|
|
899
|
+
};
|
|
900
|
+
function win() {
|
|
901
|
+
const generateRect = () => ({
|
|
902
|
+
width: window.innerWidth,
|
|
903
|
+
height: window.innerHeight
|
|
904
|
+
});
|
|
905
|
+
const size = RxFrom.event(window, `resize`, {
|
|
906
|
+
lazy: `very`,
|
|
907
|
+
transform: () => generateRect()
|
|
908
|
+
});
|
|
909
|
+
const pointer = RxFrom.event(window, `pointermove`, {
|
|
910
|
+
lazy: `very`,
|
|
911
|
+
transform: (args) => {
|
|
912
|
+
if (args === void 0) return {
|
|
913
|
+
x: 0,
|
|
914
|
+
y: 0
|
|
915
|
+
};
|
|
916
|
+
const pe = args;
|
|
917
|
+
return {
|
|
918
|
+
x: pe.x,
|
|
919
|
+
y: pe.y
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
});
|
|
923
|
+
const dispose = (reason = `Reactive.win.dispose`) => {
|
|
924
|
+
size.dispose(reason);
|
|
925
|
+
pointer.dispose(reason);
|
|
926
|
+
};
|
|
927
|
+
return {
|
|
928
|
+
dispose,
|
|
929
|
+
size,
|
|
930
|
+
pointer
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
//#endregion
|
|
935
|
+
//#region src/rx/index.ts
|
|
936
|
+
var rx_exports = /* @__PURE__ */ __exportAll({
|
|
937
|
+
bind: () => bind,
|
|
938
|
+
bindDiffUpdate: () => bindDiffUpdate,
|
|
939
|
+
bindElement: () => bindElement,
|
|
940
|
+
bindHtml: () => bindHtml,
|
|
941
|
+
bindText: () => bindText,
|
|
942
|
+
bindUpdate: () => bindUpdate,
|
|
943
|
+
bindValueText: () => bindValueText,
|
|
944
|
+
browserResizeObservable: () => browserResizeObservable,
|
|
945
|
+
colour: () => colour,
|
|
946
|
+
cssClassChange: () => cssClassChange,
|
|
947
|
+
domForm: () => domForm,
|
|
948
|
+
domHslInputValue: () => domHslInputValue,
|
|
949
|
+
domInputValue: () => domInputValue,
|
|
950
|
+
domNumberInputValue: () => domNumberInputValue,
|
|
951
|
+
elements: () => elements,
|
|
952
|
+
fromDomQuery: () => fromDomQuery,
|
|
953
|
+
win: () => win,
|
|
954
|
+
windowResize: () => windowResize
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
//#endregion
|
|
958
|
+
export { rx_exports as RxUi };
|