sygnal 5.3.6 → 5.4.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/README.md +53 -38
- package/dist/astro/client.cjs.js +18 -8106
- package/dist/astro/client.cjs.js.map +1 -0
- package/dist/astro/client.mjs +17 -8105
- package/dist/astro/client.mjs.map +1 -0
- package/dist/astro/index.cjs.js +768 -9
- package/dist/astro/index.cjs.js.map +1 -0
- package/dist/astro/index.mjs +768 -9
- package/dist/astro/index.mjs.map +1 -0
- package/dist/astro/server.cjs.js +4 -1
- package/dist/astro/server.cjs.js.map +1 -0
- package/dist/astro/server.mjs +4 -1
- package/dist/astro/server.mjs.map +1 -0
- package/dist/diagnostics.cjs.js +1297 -0
- package/dist/diagnostics.cjs.js.map +1 -0
- package/dist/diagnostics.esm.js +1267 -0
- package/dist/diagnostics.esm.js.map +1 -0
- package/dist/index.cjs.js +2981 -1386
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +848 -111
- package/dist/index.esm.js +2997 -1411
- package/dist/index.esm.js.map +1 -0
- package/dist/jsx-dev-runtime.cjs.js +15 -9
- package/dist/jsx-dev-runtime.cjs.js.map +1 -0
- package/dist/jsx-dev-runtime.esm.js +15 -9
- package/dist/jsx-dev-runtime.esm.js.map +1 -0
- package/dist/jsx-runtime.cjs.js +15 -9
- package/dist/jsx-runtime.cjs.js.map +1 -0
- package/dist/jsx-runtime.esm.js +15 -9
- package/dist/jsx-runtime.esm.js.map +1 -0
- package/dist/jsx.cjs.js +15 -9
- package/dist/jsx.cjs.js.map +1 -0
- package/dist/jsx.esm.js +15 -9
- package/dist/jsx.esm.js.map +1 -0
- package/dist/sygnal.min.js +2 -1
- package/dist/sygnal.min.js.map +1 -0
- package/dist/vike/+config.cjs.js +1 -0
- package/dist/vike/+config.cjs.js.map +1 -0
- package/dist/vike/+config.js +1 -0
- package/dist/vike/+config.js.map +1 -0
- package/dist/vike/ClientOnly.cjs.js +1 -0
- package/dist/vike/ClientOnly.cjs.js.map +1 -0
- package/dist/vike/ClientOnly.mjs +1 -0
- package/dist/vike/ClientOnly.mjs.map +1 -0
- package/dist/vike/onRenderClient.cjs.js +1 -0
- package/dist/vike/onRenderClient.cjs.js.map +1 -0
- package/dist/vike/onRenderClient.mjs +1 -0
- package/dist/vike/onRenderClient.mjs.map +1 -0
- package/dist/vike/onRenderHtml.cjs.js +1 -0
- package/dist/vike/onRenderHtml.cjs.js.map +1 -0
- package/dist/vike/onRenderHtml.mjs +1 -0
- package/dist/vike/onRenderHtml.mjs.map +1 -0
- package/dist/vite/plugin.cjs.js +680 -47
- package/dist/vite/plugin.cjs.js.map +1 -0
- package/dist/vite/plugin.mjs +680 -47
- package/dist/vite/plugin.mjs.map +1 -0
- package/llms.txt +250 -0
- package/package.json +15 -6
- package/src/astro/client.ts +13 -2
- package/src/astro/index.d.ts +24 -1
- package/src/astro/index.ts +40 -9
- package/src/astro/server.ts +3 -1
- package/src/collection.ts +5 -2
- package/src/component.ts +375 -330
- package/src/cycle/dom/MainDOMSource.ts +4 -0
- package/src/cycle/dom/classNameModule.ts +74 -0
- package/src/cycle/dom/controlledInputModule.ts +33 -0
- package/src/cycle/dom/enrichEventStream.ts +9 -3
- package/src/cycle/dom/makeDOMDriver.ts +1 -2
- package/src/cycle/dom/mockDOMSource.ts +62 -8
- package/src/cycle/dom/modules.ts +5 -1
- package/src/cycle/state/Collection.ts +13 -3
- package/src/cycle/state/StateSource.ts +1 -1
- package/src/cycle/state/pickCombine.ts +35 -1
- package/src/cycle/state/withState.ts +1 -1
- package/src/extra/devtools.ts +8 -0
- package/src/extra/diagnostics/checks/collections.ts +67 -0
- package/src/extra/diagnostics/checks/dom.ts +241 -0
- package/src/extra/diagnostics/checks/events.ts +68 -0
- package/src/extra/diagnostics/checks/index.ts +109 -0
- package/src/extra/diagnostics/checks/inspect.ts +319 -0
- package/src/extra/diagnostics/checks/props.ts +51 -0
- package/src/extra/diagnostics/checks/public.d.ts +211 -0
- package/src/extra/diagnostics/checks/rxjsHints.ts +88 -0
- package/src/extra/diagnostics/checks/shared.ts +119 -0
- package/src/extra/diagnostics/checks/state.ts +55 -0
- package/src/extra/diagnostics/checks/strict.ts +112 -0
- package/src/extra/diagnostics/checks/wiring.ts +65 -0
- package/src/extra/diagnostics/codes.ts +232 -0
- package/src/extra/diagnostics/index.ts +328 -0
- package/src/extra/diagnostics/legacy.ts +59 -0
- package/src/extra/driverFactories.ts +106 -58
- package/src/extra/eventDriver.ts +4 -0
- package/src/extra/flatten.ts +75 -0
- package/src/extra/reducers.ts +12 -5
- package/src/extra/run.ts +11 -0
- package/src/extra/testing.ts +890 -180
- package/src/extra/xstreamExtras.ts +269 -0
- package/src/index.d.ts +428 -17
- package/src/index.ts +7 -6
- package/src/pragma/index.ts +14 -9
- package/src/switchable.ts +10 -8
- package/src/vike/types.ts +21 -4
- package/src/vite/plugin.d.ts +95 -2
- package/src/vite/plugin.ts +730 -55
- package/dist/astro/astro/client.d.ts +0 -5
- package/dist/astro/astro/index.d.ts +0 -16
- package/dist/astro/astro/server.d.ts +0 -12
- package/dist/astro/client.d.ts +0 -5
- package/dist/astro/client.esm.js +0 -4665
- package/dist/astro/collection.d.ts +0 -12
- package/dist/astro/component.d.ts +0 -22
- package/dist/astro/cycle/dom/BodyDOMSource.d.ts +0 -10
- package/dist/astro/cycle/dom/DOMSource.d.ts +0 -11
- package/dist/astro/cycle/dom/DocumentDOMSource.d.ts +0 -12
- package/dist/astro/cycle/dom/ElementFinder.d.ts +0 -8
- package/dist/astro/cycle/dom/EventDelegator.d.ts +0 -34
- package/dist/astro/cycle/dom/IsolateModule.d.ts +0 -23
- package/dist/astro/cycle/dom/MainDOMSource.d.ts +0 -32
- package/dist/astro/cycle/dom/PriorityQueue.d.ts +0 -7
- package/dist/astro/cycle/dom/ScopeChecker.d.ts +0 -9
- package/dist/astro/cycle/dom/SymbolTree.d.ts +0 -9
- package/dist/astro/cycle/dom/VNodeWrapper.d.ts +0 -8
- package/dist/astro/cycle/dom/enrichEventStream.d.ts +0 -24
- package/dist/astro/cycle/dom/fromEvent.d.ts +0 -6
- package/dist/astro/cycle/dom/index.d.ts +0 -8
- package/dist/astro/cycle/dom/isolate.d.ts +0 -9
- package/dist/astro/cycle/dom/makeDOMDriver.d.ts +0 -11
- package/dist/astro/cycle/dom/mockDOMSource.d.ts +0 -17
- package/dist/astro/cycle/dom/modules.d.ts +0 -5
- package/dist/astro/cycle/dom/snabbdom.d.ts +0 -22
- package/dist/astro/cycle/dom/styleModule.d.ts +0 -13
- package/dist/astro/cycle/dom/thunk.d.ts +0 -11
- package/dist/astro/cycle/dom/utils.d.ts +0 -8
- package/dist/astro/cycle/isolate/index.d.ts +0 -42
- package/dist/astro/cycle/run/adapt.d.ts +0 -6
- package/dist/astro/cycle/run/index.d.ts +0 -36
- package/dist/astro/cycle/run/internals.d.ts +0 -8
- package/dist/astro/cycle/run/types.d.ts +0 -49
- package/dist/astro/cycle/state/Collection.d.ts +0 -29
- package/dist/astro/cycle/state/StateSource.d.ts +0 -20
- package/dist/astro/cycle/state/index.d.ts +0 -5
- package/dist/astro/cycle/state/pickCombine.d.ts +0 -3
- package/dist/astro/cycle/state/pickMerge.d.ts +0 -3
- package/dist/astro/cycle/state/types.d.ts +0 -18
- package/dist/astro/cycle/state/withState.d.ts +0 -17
- package/dist/astro/extra/classes.d.ts +0 -7
- package/dist/astro/extra/devtools.d.ts +0 -78
- package/dist/astro/extra/dragDriver.d.ts +0 -20
- package/dist/astro/extra/driverFactories.d.ts +0 -12
- package/dist/astro/extra/eventDriver.d.ts +0 -9
- package/dist/astro/extra/exactState.d.ts +0 -1
- package/dist/astro/extra/hmr.d.ts +0 -17
- package/dist/astro/extra/logDriver.d.ts +0 -2
- package/dist/astro/extra/processDrag.d.ts +0 -19
- package/dist/astro/extra/processForm.d.ts +0 -15
- package/dist/astro/extra/run.d.ts +0 -13
- package/dist/astro/extra/xstreamCompat.d.ts +0 -5
- package/dist/astro/index.d.ts +0 -19
- package/dist/astro/index.esm.js +0 -27
- package/dist/astro/jsx-dev-runtime.d.ts +0 -1
- package/dist/astro/jsx-runtime.d.ts +0 -4
- package/dist/astro/jsx.d.ts +0 -2
- package/dist/astro/pragma/fn.d.ts +0 -7
- package/dist/astro/pragma/index.d.ts +0 -7
- package/dist/astro/pragma/is.d.ts +0 -10
- package/dist/astro/server.d.ts +0 -12
- package/dist/astro/server.esm.js +0 -25
- package/dist/astro/switchable.d.ts +0 -7
- package/dist/collection.d.ts +0 -12
- package/dist/component.d.ts +0 -22
- package/dist/cycle/dom/BodyDOMSource.d.ts +0 -10
- package/dist/cycle/dom/DOMSource.d.ts +0 -11
- package/dist/cycle/dom/DocumentDOMSource.d.ts +0 -12
- package/dist/cycle/dom/ElementFinder.d.ts +0 -8
- package/dist/cycle/dom/EventDelegator.d.ts +0 -34
- package/dist/cycle/dom/IsolateModule.d.ts +0 -23
- package/dist/cycle/dom/MainDOMSource.d.ts +0 -32
- package/dist/cycle/dom/PriorityQueue.d.ts +0 -7
- package/dist/cycle/dom/ScopeChecker.d.ts +0 -9
- package/dist/cycle/dom/SymbolTree.d.ts +0 -9
- package/dist/cycle/dom/VNodeWrapper.d.ts +0 -8
- package/dist/cycle/dom/enrichEventStream.d.ts +0 -24
- package/dist/cycle/dom/fromEvent.d.ts +0 -6
- package/dist/cycle/dom/index.d.ts +0 -8
- package/dist/cycle/dom/isolate.d.ts +0 -9
- package/dist/cycle/dom/makeDOMDriver.d.ts +0 -11
- package/dist/cycle/dom/mockDOMSource.d.ts +0 -17
- package/dist/cycle/dom/modules.d.ts +0 -5
- package/dist/cycle/dom/snabbdom.d.ts +0 -22
- package/dist/cycle/dom/styleModule.d.ts +0 -13
- package/dist/cycle/dom/thunk.d.ts +0 -11
- package/dist/cycle/dom/utils.d.ts +0 -8
- package/dist/cycle/isolate/index.d.ts +0 -42
- package/dist/cycle/run/adapt.d.ts +0 -6
- package/dist/cycle/run/index.d.ts +0 -36
- package/dist/cycle/run/internals.d.ts +0 -8
- package/dist/cycle/run/types.d.ts +0 -49
- package/dist/cycle/state/Collection.d.ts +0 -29
- package/dist/cycle/state/StateSource.d.ts +0 -20
- package/dist/cycle/state/index.d.ts +0 -5
- package/dist/cycle/state/pickCombine.d.ts +0 -3
- package/dist/cycle/state/pickMerge.d.ts +0 -3
- package/dist/cycle/state/types.d.ts +0 -18
- package/dist/cycle/state/withState.d.ts +0 -17
- package/dist/extra/classes.d.ts +0 -7
- package/dist/extra/devtools.d.ts +0 -78
- package/dist/extra/dragDriver.d.ts +0 -20
- package/dist/extra/driverFactories.d.ts +0 -12
- package/dist/extra/eventDriver.d.ts +0 -9
- package/dist/extra/exactState.d.ts +0 -1
- package/dist/extra/hmr.d.ts +0 -17
- package/dist/extra/logDriver.d.ts +0 -2
- package/dist/extra/processDrag.d.ts +0 -19
- package/dist/extra/processForm.d.ts +0 -15
- package/dist/extra/run.d.ts +0 -13
- package/dist/extra/xstreamCompat.d.ts +0 -5
- package/dist/jsx-dev-runtime.d.ts +0 -1
- package/dist/jsx-dev-runtime.js +0 -224
- package/dist/jsx-runtime.d.ts +0 -4
- package/dist/jsx-runtime.js +0 -224
- package/dist/jsx.d.ts +0 -2
- package/dist/pragma/fn.d.ts +0 -7
- package/dist/pragma/index.d.ts +0 -7
- package/dist/pragma/is.d.ts +0 -10
- package/dist/switchable.d.ts +0 -7
|
@@ -11,6 +11,8 @@ import {ElementFinder} from './ElementFinder';
|
|
|
11
11
|
import {makeIsolateSink, getScopeObj, Scope, IsolateSink} from './isolate';
|
|
12
12
|
import {IsolateModule} from './IsolateModule';
|
|
13
13
|
import {EventDelegator} from './EventDelegator';
|
|
14
|
+
// [diagnostics hook] no-op when diagnostics are off
|
|
15
|
+
import {onSelector} from '../../extra/diagnostics/index';
|
|
14
16
|
|
|
15
17
|
export interface SpecialSelector {
|
|
16
18
|
body: BodyDOMSource;
|
|
@@ -91,6 +93,8 @@ export class MainDOMSource {
|
|
|
91
93
|
return new BodyDOMSource(this._name);
|
|
92
94
|
}
|
|
93
95
|
|
|
96
|
+
if (selector !== ':root') onSelector(this, selector.trim());
|
|
97
|
+
|
|
94
98
|
const namespace =
|
|
95
99
|
selector === ':root'
|
|
96
100
|
? []
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snabbdom module that unsets a removed `className` prop (B-012).
|
|
3
|
+
*
|
|
4
|
+
* Problem: `className` goes through snabbdom's propsModule, which writes new
|
|
5
|
+
* and changed props but never unsets a prop that disappears. So when snabbdom
|
|
6
|
+
* reuses an element (`<p className="placeholder">` -> `<p>`), the old class
|
|
7
|
+
* stays. A nullish `className` is worse: propsModule writes the string
|
|
8
|
+
* "null"/"false".
|
|
9
|
+
*
|
|
10
|
+
* Fix: runs after propsModule. When `className` is gone (or nullish/false),
|
|
11
|
+
* drop the classes it had set, keeping the classes from the selector
|
|
12
|
+
* (`p.foo`) and the truthy `class={{...}}` entries.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type {VNode} from 'snabbdom/build/vnode.js';
|
|
16
|
+
|
|
17
|
+
function classObject(vnode: VNode): Array<string> {
|
|
18
|
+
const out: Array<string> = [];
|
|
19
|
+
const klass: any = vnode.data?.class;
|
|
20
|
+
if (klass && typeof klass === 'object') {
|
|
21
|
+
for (const name in klass) if (klass[name]) out.push(name);
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function baseClasses(vnode: VNode): Array<string> {
|
|
27
|
+
const sel = vnode.sel || '';
|
|
28
|
+
const hashIdx = sel.indexOf('#');
|
|
29
|
+
const dotIdx = sel.indexOf('.', hashIdx);
|
|
30
|
+
const fromSel = dotIdx > 0 ? sel.slice(dotIdx + 1).split('.') : [];
|
|
31
|
+
return fromSel.concat(classObject(vnode));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function addAll(elm: any, names: Array<string>): void {
|
|
35
|
+
for (const name of names) if (name) elm.classList.add(name);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const isSet = (cn: any) => cn != null && cn !== false;
|
|
39
|
+
const tokens = (cn: any) => (typeof cn === 'string' ? cn.split(/\s+/).filter(Boolean) : []);
|
|
40
|
+
|
|
41
|
+
function syncClassName(oldVnode: VNode, vnode: VNode): void {
|
|
42
|
+
const elm = vnode.elm as any;
|
|
43
|
+
if (!elm || !elm.classList) return;
|
|
44
|
+
const props: any = vnode.data?.props;
|
|
45
|
+
const oldProps: any = oldVnode.data?.props;
|
|
46
|
+
const hasKey = !!props && 'className' in props;
|
|
47
|
+
const cn = hasKey ? props.className : undefined;
|
|
48
|
+
if (isSet(cn)) {
|
|
49
|
+
// propsModule replaced the whole attribute: put the class={{...}} entries back
|
|
50
|
+
if (!oldProps || oldProps.className !== cn) addAll(elm, classObject(vnode));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (hasKey) {
|
|
54
|
+
// propsModule wrote String(cn) over the whole attribute: rebuild it
|
|
55
|
+
if (!oldProps || oldProps.className !== cn) {
|
|
56
|
+
elm.className = baseClasses(vnode).join(' ');
|
|
57
|
+
if (elm.className === '') elm.removeAttribute('class');
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const oldCn = oldProps ? oldProps.className : undefined;
|
|
62
|
+
if (!isSet(oldCn)) return;
|
|
63
|
+
const keep = baseClasses(vnode);
|
|
64
|
+
for (const name of tokens(oldCn)) {
|
|
65
|
+
if (keep.indexOf(name) === -1) elm.classList.remove(name);
|
|
66
|
+
}
|
|
67
|
+
addAll(elm, keep);
|
|
68
|
+
if (elm.getAttribute('class') === '') elm.removeAttribute('class');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const classNameModule = {
|
|
72
|
+
create: (emptyVnode: VNode, vnode: VNode) => syncClassName(emptyVnode, vnode),
|
|
73
|
+
update: syncClassName,
|
|
74
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snabbdom module that keeps controlled form fields in sync with the vnode (B-004).
|
|
3
|
+
*
|
|
4
|
+
* Problem: snabbdom's propsModule only writes `value`/`checked` when the prop
|
|
5
|
+
* differs from the previous vnode's prop. If the user types 'x' and an action
|
|
6
|
+
* resets the state to '' within one render window, the 'x' render is
|
|
7
|
+
* coalesced away: the vnodes go '' -> '' and the typed text stays in the DOM.
|
|
8
|
+
*
|
|
9
|
+
* Fix: after propsModule has run, compare `value`/`checked` props against the
|
|
10
|
+
* live element and write them when they differ. Only form fields (INPUT,
|
|
11
|
+
* TEXTAREA, SELECT; not input type=file) are controlled: `value` compares as a
|
|
12
|
+
* string, `checked` as a boolean (1H-7). Other elements with a value prop
|
|
13
|
+
* (<progress>, <meter>, custom elements with object values) are left alone.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type {VNode} from 'snabbdom/build/vnode.js';
|
|
17
|
+
|
|
18
|
+
function syncControlled(oldVnode: VNode, vnode: VNode): void {
|
|
19
|
+
const props = vnode.data?.props;
|
|
20
|
+
const elm = vnode.elm as any;
|
|
21
|
+
if (!props || !elm || !/^(INPUT|TEXTAREA|SELECT)$/.test(elm.tagName) || elm.type == 'file') return;
|
|
22
|
+
const oldProps: any = oldVnode.data?.props || {};
|
|
23
|
+
const {value, checked} = props;
|
|
24
|
+
// Only the case propsModule skipped (prop unchanged); it handles real changes.
|
|
25
|
+
if (value === oldProps.value && value != null && elm.value !== String(value)) {
|
|
26
|
+
elm.value = String(value);
|
|
27
|
+
}
|
|
28
|
+
if (checked === oldProps.checked && checked != null && elm.checked !== !!checked) {
|
|
29
|
+
elm.checked = !!checked;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const controlledInputModule = {update: syncControlled};
|
|
@@ -46,11 +46,17 @@ export function enrichEventStream(stream$: any): any {
|
|
|
46
46
|
return enrichEventStream(mapped);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
// .data(name, fn?) — extract
|
|
49
|
+
// .data(name, fn?) — extract dataset[name] from e.target or its nearest ancestor that has
|
|
50
|
+
// it. dataset keys are camelCase but attributes are kebab-case (`taskId` is
|
|
51
|
+
// `data-task-id`), so the closest() selector converts (B-028). The name may be given
|
|
52
|
+
// either way: `.data('taskId')` and `.data('task-id')` read the same attribute (3E/R3).
|
|
50
53
|
stream$.data = function data(name: string, fn?: (val: any) => any): any {
|
|
54
|
+
const key = String(name).replace(/-([a-z])/g, (_, c) => c.toUpperCase())
|
|
55
|
+
const attr = `[data-${key.replace(/[A-Z]/g, c => '-' + c.toLowerCase())}]`
|
|
51
56
|
const mapped = stream$.map((e: any) => {
|
|
52
|
-
const
|
|
53
|
-
const
|
|
57
|
+
const t = e?.target
|
|
58
|
+
const el = typeof t?.closest == 'function' ? t.closest(attr) || t : t
|
|
59
|
+
const val = el?.dataset?.[key]
|
|
54
60
|
return fn ? fn(val) : val
|
|
55
61
|
});
|
|
56
62
|
return enrichEventStream(mapped);
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import {Driver} from '../run/types';
|
|
2
2
|
import {init, Module, Options as SnabbdomOptions, VNode, toVNode} from './snabbdom';
|
|
3
3
|
import xs, {Stream, Listener} from 'xstream';
|
|
4
|
-
import concat from '
|
|
5
|
-
import sampleCombine from 'xstream/extra/sampleCombine.js';
|
|
4
|
+
import {concat, sampleCombine} from '../../extra/xstreamExtras';
|
|
6
5
|
import {MainDOMSource} from './MainDOMSource';
|
|
7
6
|
import {VNodeWrapper} from './VNodeWrapper';
|
|
8
7
|
import {getValidNode, checkValidContainer} from './utils';
|
|
@@ -3,6 +3,22 @@ import {DevToolEnabledSource, FantasyObservable} from '../run/types';
|
|
|
3
3
|
import {VNode} from './snabbdom';
|
|
4
4
|
import {EventsFnOptions} from './DOMSource';
|
|
5
5
|
import {adapt} from '../run/adapt';
|
|
6
|
+
import {enrichEventStream} from './enrichEventStream';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Optional simulated-event hub (used by renderComponent's simulateEvent): a
|
|
10
|
+
* stream of `{type, event, match(path)}`; each source's events(type) also emits
|
|
11
|
+
* the hub events whose `match` accepts its selector path (isolation scopes
|
|
12
|
+
* included as '.___scope' segments).
|
|
13
|
+
*/
|
|
14
|
+
export type MockEventHub = Stream<{type: string; event: any; match: (path: string[]) => boolean}>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Optional listener callback (renderComponent): called with `live` undefined when events() is
|
|
18
|
+
* called (SYG103/104 checks), then with true / false when that hub listener is subscribed /
|
|
19
|
+
* unsubscribed (simulateEvent waits for a just-mounted child's listeners, G-039).
|
|
20
|
+
*/
|
|
21
|
+
export type MockOnEvents = (path: string[], eventType: string, live?: boolean) => void;
|
|
6
22
|
|
|
7
23
|
export type MockConfig = {
|
|
8
24
|
[name: string]: FantasyObservable<any> | MockConfig;
|
|
@@ -13,7 +29,12 @@ const SCOPE_PREFIX = '___';
|
|
|
13
29
|
export class MockedDOMSource {
|
|
14
30
|
private _elements: FantasyObservable<any>;
|
|
15
31
|
|
|
16
|
-
constructor(
|
|
32
|
+
constructor(
|
|
33
|
+
private _mockConfig: MockConfig,
|
|
34
|
+
private _hub?: MockEventHub,
|
|
35
|
+
public _path: string[] = [],
|
|
36
|
+
private _onEvents?: MockOnEvents
|
|
37
|
+
) {
|
|
17
38
|
if (_mockConfig.elements) {
|
|
18
39
|
this._elements = _mockConfig.elements as FantasyObservable<any>;
|
|
19
40
|
} else {
|
|
@@ -43,10 +64,31 @@ export class MockedDOMSource {
|
|
|
43
64
|
options?: EventsFnOptions,
|
|
44
65
|
bubbles?: boolean
|
|
45
66
|
): any {
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
67
|
+
const configured = this._mockConfig[eventType] as any;
|
|
68
|
+
const {_hub: hub, _path: path, _onEvents: on} = this;
|
|
69
|
+
if (on) on(path, eventType);
|
|
70
|
+
let hub$: Stream<any> | undefined;
|
|
71
|
+
if (hub) {
|
|
72
|
+
const ev$ = hub.filter(e => e.type === eventType && e.match(path)).map(e => e.event);
|
|
73
|
+
let l: any;
|
|
74
|
+
hub$ = on
|
|
75
|
+
? xs.create({
|
|
76
|
+
start: (x: any) => {
|
|
77
|
+
ev$.addListener(l = {next: (v: any) => x.next(v), error: (e: any) => x.error(e), complete: () => x.complete()});
|
|
78
|
+
on(path, eventType, true);
|
|
79
|
+
},
|
|
80
|
+
stop: () => {
|
|
81
|
+
ev$.removeListener(l);
|
|
82
|
+
on(path, eventType, false);
|
|
83
|
+
},
|
|
84
|
+
})
|
|
85
|
+
: ev$;
|
|
86
|
+
}
|
|
87
|
+
const out: DevToolEnabledSource & FantasyObservable<any> = enrichEventStream(adapt(
|
|
88
|
+
hub$
|
|
89
|
+
? xs.merge(configured ? xs.fromObservable(configured) : xs.empty(), hub$)
|
|
90
|
+
: configured || xs.empty()
|
|
91
|
+
));
|
|
50
92
|
|
|
51
93
|
out._isCycleSource = 'MockedDOM';
|
|
52
94
|
|
|
@@ -56,7 +98,12 @@ export class MockedDOMSource {
|
|
|
56
98
|
public select(selector: string): MockedDOMSource {
|
|
57
99
|
const mockConfigForSelector = this._mockConfig[selector] || {};
|
|
58
100
|
|
|
59
|
-
return new MockedDOMSource(
|
|
101
|
+
return new MockedDOMSource(
|
|
102
|
+
mockConfigForSelector as MockConfig,
|
|
103
|
+
this._hub,
|
|
104
|
+
this._path.concat(selector),
|
|
105
|
+
this._onEvents
|
|
106
|
+
);
|
|
60
107
|
}
|
|
61
108
|
|
|
62
109
|
public isolateSource(
|
|
@@ -69,6 +116,9 @@ export class MockedDOMSource {
|
|
|
69
116
|
public isolateSink(sink: any, scope: string): any {
|
|
70
117
|
return adapt(
|
|
71
118
|
xs.fromObservable<any>(sink).map((vnode: VNode) => {
|
|
119
|
+
// B-025: a Switchable's DOM sink starts with undefined (nothing rendered yet); the
|
|
120
|
+
// real DOM driver's isolateSink passes it through too
|
|
121
|
+
if (!vnode) return vnode;
|
|
72
122
|
if (vnode.sel && vnode.sel.indexOf(SCOPE_PREFIX + scope) !== -1) {
|
|
73
123
|
return vnode;
|
|
74
124
|
} else {
|
|
@@ -80,6 +130,10 @@ export class MockedDOMSource {
|
|
|
80
130
|
}
|
|
81
131
|
}
|
|
82
132
|
|
|
83
|
-
export function mockDOMSource(
|
|
84
|
-
|
|
133
|
+
export function mockDOMSource(
|
|
134
|
+
mockConfig: MockConfig,
|
|
135
|
+
hub?: MockEventHub,
|
|
136
|
+
onEvents?: MockOnEvents
|
|
137
|
+
): MockedDOMSource {
|
|
138
|
+
return new MockedDOMSource(mockConfig, hub, [], onEvents);
|
|
85
139
|
}
|
package/src/cycle/dom/modules.ts
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import {Module, classModule, propsModule, attributesModule, datasetModule} from './snabbdom';
|
|
2
2
|
import {styleModule} from './styleModule';
|
|
3
3
|
import {selectModule} from './selectModule';
|
|
4
|
+
import {controlledInputModule} from './controlledInputModule';
|
|
5
|
+
import {classNameModule} from './classNameModule';
|
|
4
6
|
|
|
5
7
|
const modules: Array<Module> = [
|
|
6
8
|
styleModule,
|
|
7
9
|
classModule,
|
|
8
10
|
propsModule,
|
|
11
|
+
classNameModule, // after propsModule (B-012)
|
|
9
12
|
attributesModule,
|
|
10
13
|
datasetModule,
|
|
11
14
|
selectModule,
|
|
15
|
+
controlledInputModule,
|
|
12
16
|
];
|
|
13
17
|
|
|
14
|
-
export {styleModule, classModule, propsModule, attributesModule, datasetModule, selectModule};
|
|
18
|
+
export {styleModule, classModule, propsModule, attributesModule, datasetModule, selectModule, controlledInputModule, classNameModule};
|
|
15
19
|
|
|
16
20
|
export default modules;
|
|
@@ -106,9 +106,11 @@ export function makeCollection<S, So = any, Si = any>(
|
|
|
106
106
|
const itemKey = opts.itemKey;
|
|
107
107
|
const itemScope = opts.itemScope || defaultItemScope;
|
|
108
108
|
const state$ = xs.fromObservable((sources[name] as StateSource<S>).stream);
|
|
109
|
+
const dict = new Map();
|
|
110
|
+
let disposed = false;
|
|
109
111
|
const instances$ = state$.fold(
|
|
110
112
|
(acc: InternalInstances<Si>, nextState: Array<any> | any) => {
|
|
111
|
-
|
|
113
|
+
if (disposed) return acc;
|
|
112
114
|
if (Array.isArray(nextState)) {
|
|
113
115
|
const nextInstArray = Array(nextState.length) as Array<
|
|
114
116
|
Si & {_key: string}
|
|
@@ -167,8 +169,16 @@ export function makeCollection<S, So = any, Si = any>(
|
|
|
167
169
|
return {dict: dict, arr: [sinks]};
|
|
168
170
|
}
|
|
169
171
|
},
|
|
170
|
-
{dict
|
|
172
|
+
{dict, arr: []} as InternalInstances<Si>
|
|
171
173
|
);
|
|
172
|
-
|
|
174
|
+
const sinks = opts.collectSinks(new Instances<Si>(instances$));
|
|
175
|
+
// B-024: disposing the collection (its owner was disposed or stopped rendering it)
|
|
176
|
+
// disposes every live item, and so their subtrees
|
|
177
|
+
sinks.__dispose = () => {
|
|
178
|
+
disposed = true;
|
|
179
|
+
dict.forEach((s: any) => s.__dispose?.());
|
|
180
|
+
dict.clear();
|
|
181
|
+
};
|
|
182
|
+
return sinks;
|
|
173
183
|
};
|
|
174
184
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import xs, {Stream, MemoryStream} from 'xstream';
|
|
2
|
-
import dropRepeats from '
|
|
2
|
+
import {dropRepeats} from '../../extra/xstreamExtras';
|
|
3
3
|
import {DevToolEnabledSource} from '../run/types';
|
|
4
4
|
import {adapt} from '../run/adapt';
|
|
5
5
|
import {Getter, Setter, Scope, Reducer} from './types';
|
|
@@ -43,6 +43,28 @@ class PickCombineListener<Si, T>
|
|
|
43
43
|
public _c(): void {}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
// True when the items present both before and after appear in a different relative
|
|
47
|
+
// order. Additions emit through the new item's sink and removals are handled
|
|
48
|
+
// separately, so only a permutation of the surviving items counts here.
|
|
49
|
+
function isReordered(prev: Array<string>, next: Array<string>, dict: Map<string, any>): boolean {
|
|
50
|
+
const m = prev.length;
|
|
51
|
+
if (m === next.length) {
|
|
52
|
+
let same = true;
|
|
53
|
+
for (let i = 0; i < m && same; ++i) same = prev[i] === next[i];
|
|
54
|
+
if (same) return false;
|
|
55
|
+
}
|
|
56
|
+
let j = 0;
|
|
57
|
+
const seen = new Set(prev);
|
|
58
|
+
for (let i = 0; i < next.length; ++i) {
|
|
59
|
+
const key = next[i];
|
|
60
|
+
if (!seen.has(key)) continue;
|
|
61
|
+
while (j < m && !dict.has(prev[j])) ++j;
|
|
62
|
+
if (prev[j] !== key) return true;
|
|
63
|
+
++j;
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
46
68
|
class PickCombine<Si, R> implements Operator<InternalInstances<Si>, Array<R>> {
|
|
47
69
|
public type = 'combine';
|
|
48
70
|
public ins: Stream<InternalInstances<Si>>;
|
|
@@ -50,6 +72,8 @@ class PickCombine<Si, R> implements Operator<InternalInstances<Si>, Array<R>> {
|
|
|
50
72
|
public sel: string;
|
|
51
73
|
public ils: Map<string, PickCombineListener<Si, R>>;
|
|
52
74
|
public inst: InternalInstances<Si>;
|
|
75
|
+
// item keys in the order of the last instances (B-010: detect pure reorders)
|
|
76
|
+
public keys: Array<string>;
|
|
53
77
|
|
|
54
78
|
constructor(sel: string, ins: Stream<InternalInstances<Si>>) {
|
|
55
79
|
this.ins = ins;
|
|
@@ -57,6 +81,7 @@ class PickCombine<Si, R> implements Operator<InternalInstances<Si>, Array<R>> {
|
|
|
57
81
|
this.out = null as any;
|
|
58
82
|
this.ils = new Map();
|
|
59
83
|
this.inst = null as any;
|
|
84
|
+
this.keys = [];
|
|
60
85
|
}
|
|
61
86
|
|
|
62
87
|
public _start(out: Stream<Array<R>>): void {
|
|
@@ -77,6 +102,7 @@ class PickCombine<Si, R> implements Operator<InternalInstances<Si>, Array<R>> {
|
|
|
77
102
|
this.out = null as any;
|
|
78
103
|
this.ils = new Map();
|
|
79
104
|
this.inst = null as any;
|
|
105
|
+
this.keys = [];
|
|
80
106
|
}
|
|
81
107
|
|
|
82
108
|
public up(): void {
|
|
@@ -120,13 +146,16 @@ class PickCombine<Si, R> implements Operator<InternalInstances<Si>, Array<R>> {
|
|
|
120
146
|
}
|
|
121
147
|
});
|
|
122
148
|
if (n === 0) {
|
|
149
|
+
this.keys = [];
|
|
123
150
|
out._n([]);
|
|
124
151
|
return;
|
|
125
152
|
}
|
|
126
153
|
// add
|
|
154
|
+
const keys: Array<string> = Array(n);
|
|
127
155
|
for (let i = 0; i < n; ++i) {
|
|
128
156
|
const sinks = arrSinks[i];
|
|
129
157
|
const key = (sinks as any)._key as string;
|
|
158
|
+
keys[i] = key;
|
|
130
159
|
if (!(sinks as any)[sel]) {
|
|
131
160
|
throw new Error('pickCombine found an undefined child sink stream');
|
|
132
161
|
}
|
|
@@ -136,7 +165,12 @@ class PickCombine<Si, R> implements Operator<InternalInstances<Si>, Array<R>> {
|
|
|
136
165
|
sink._add(ils.get(key) as PickCombineListener<Si, R>);
|
|
137
166
|
}
|
|
138
167
|
}
|
|
139
|
-
|
|
168
|
+
const reordered = isReordered(this.keys, keys, dict);
|
|
169
|
+
this.keys = keys;
|
|
170
|
+
// B-010: a permutation (swap, reverse, move) neither removes an item nor makes an
|
|
171
|
+
// item sink emit, so re-emit whenever the order of keys changed. up() waits until
|
|
172
|
+
// every item has emitted, so a brand new item still triggers its own emission.
|
|
173
|
+
if (removed || reordered) {
|
|
140
174
|
this.up();
|
|
141
175
|
}
|
|
142
176
|
}
|
package/src/extra/devtools.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {getDiagnostics} from './diagnostics/index';
|
|
2
|
+
import type {Diagnostic} from './diagnostics/index';
|
|
3
|
+
|
|
1
4
|
const DEVTOOLS_SOURCE = '__SYGNAL_DEVTOOLS_PAGE__';
|
|
2
5
|
const EXTENSION_SOURCE = '__SYGNAL_DEVTOOLS_EXTENSION__';
|
|
3
6
|
const DEFAULT_MAX_HISTORY = 200;
|
|
@@ -68,6 +71,11 @@ class SygnalDevTools {
|
|
|
68
71
|
this._maxHistory = DEFAULT_MAX_HISTORY;
|
|
69
72
|
}
|
|
70
73
|
|
|
74
|
+
/** Diagnostics collected by the shared diagnostics core (see src/extra/diagnostics). */
|
|
75
|
+
getDiagnostics(): Diagnostic[] {
|
|
76
|
+
return getDiagnostics();
|
|
77
|
+
}
|
|
78
|
+
|
|
71
79
|
get connected(): boolean {
|
|
72
80
|
return this._connected && typeof window !== 'undefined';
|
|
73
81
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SYG401 — <Collection> has no `from` and the whole state is not an array (warn).
|
|
3
|
+
* A defined `from` is checked by the core (D22).
|
|
4
|
+
*
|
|
5
|
+
* Reported through diagnostics in addition to the existing console output.
|
|
6
|
+
*
|
|
7
|
+
* Mechanism: the core has no hook at Collection instantiation, and the
|
|
8
|
+
* Collection marker vnode is gone by the time onRender sees the tree. So, at
|
|
9
|
+
* onModel (construction time, before the first render), this check wraps the
|
|
10
|
+
* component INSTANCE's instantiateCollection method (an own property that
|
|
11
|
+
* shadows the prototype method; Component calls `this.instantiateCollection`
|
|
12
|
+
* for every new Collection). The wrapper inspects the marker's props and the
|
|
13
|
+
* component's current state, then always calls the original. This only
|
|
14
|
+
* happens when the dev-only 'sygnal/diagnostics' entry is loaded and
|
|
15
|
+
* diagnostics are on. Reported once per component name and `from` value.
|
|
16
|
+
*/
|
|
17
|
+
import type {DiagnosticCheck} from '../index'
|
|
18
|
+
import {reportSafely, once, nameOf, isPlainObject} from './shared'
|
|
19
|
+
|
|
20
|
+
const describe = (v: any): string =>
|
|
21
|
+
v === null ? 'null' : v === undefined ? 'undefined' : Array.isArray(v) ? 'an array' : typeof v === 'object' ? 'an object' : `a ${typeof v}`
|
|
22
|
+
|
|
23
|
+
function checkCollection(component: any, el: any): void {
|
|
24
|
+
const props = (el && el.data && el.data.props) || {}
|
|
25
|
+
const from = props.from
|
|
26
|
+
const of = props.of
|
|
27
|
+
const ofName = typeof of === 'function' ? (of.componentName || of.label || of.name || 'component') : String(of)
|
|
28
|
+
const name = nameOf(component)
|
|
29
|
+
|
|
30
|
+
let state = component.currentState
|
|
31
|
+
try { if (typeof component.addCalculated === 'function') state = component.addCalculated(state) } catch (_) {}
|
|
32
|
+
|
|
33
|
+
let problem: string | undefined
|
|
34
|
+
let fix = `Pass the name of an array field in ${name}'s state, e.g. <Collection of={${ofName}} from="items" />, and initialize that field to [] in initialState`
|
|
35
|
+
// Every defined `from` (string field, lens object, or an invalid value) is reported by
|
|
36
|
+
// the core itself (SYG401/SYG412, retrofitted in 1E) in every diagnostics mode (D22), so
|
|
37
|
+
// this check only covers a missing `from`.
|
|
38
|
+
if (from !== undefined) return
|
|
39
|
+
if (!Array.isArray(state) && !(state && Array.isArray(state.value))) {
|
|
40
|
+
problem = `has no 'from' prop, so it uses ${name}'s whole state, which is ${describe(state)}, not an array`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!problem || !once(`SYG401:${name}:undefined`)) return
|
|
44
|
+
reportSafely('SYG401', {
|
|
45
|
+
component,
|
|
46
|
+
message: `<Collection of={${ofName}}> ${problem}, so it renders no items`,
|
|
47
|
+
fix,
|
|
48
|
+
data: {from: 'undefined', of: ofName},
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const WRAPPED = '__sygnalDiagWrapped'
|
|
53
|
+
|
|
54
|
+
export const collectionsCheck: DiagnosticCheck = {
|
|
55
|
+
id: 'collections',
|
|
56
|
+
|
|
57
|
+
onModel(component) {
|
|
58
|
+
const original = component && component.instantiateCollection
|
|
59
|
+
if (typeof original !== 'function' || original[WRAPPED]) return
|
|
60
|
+
const wrapped = function (this: any, el: any, ...rest: any[]) {
|
|
61
|
+
try { checkCollection(this, el) } catch (_) {}
|
|
62
|
+
return original.call(this, el, ...rest)
|
|
63
|
+
}
|
|
64
|
+
;(wrapped as any)[WRAPPED] = true
|
|
65
|
+
component.instantiateCollection = wrapped
|
|
66
|
+
},
|
|
67
|
+
}
|