sygnal 4.2.0 → 4.3.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/astro/client.cjs.js +547 -82
- package/dist/astro/client.mjs +547 -82
- package/dist/index.cjs.js +569 -94
- package/dist/index.esm.js +569 -95
- package/dist/jsx-dev-runtime.cjs.js +49 -12
- package/dist/jsx-dev-runtime.esm.js +49 -12
- package/dist/jsx-runtime.cjs.js +49 -12
- package/dist/jsx-runtime.esm.js +49 -12
- package/dist/jsx.cjs.js +49 -12
- package/dist/jsx.esm.js +49 -12
- package/dist/sygnal.min.js +1 -1
- package/package.json +6 -3
- package/src/collection.js +5 -2
- package/src/component.js +278 -79
- package/src/extra/devtools.js +249 -0
- package/src/extra/driverFactories.js +12 -12
- package/src/extra/eventDriver.js +2 -1
- package/src/extra/logDriver.js +3 -0
- package/src/extra/processDrag.js +6 -0
- package/src/extra/processForm.js +3 -0
- package/src/extra/run.js +12 -0
- package/src/index.d.ts +8 -4
- package/src/index.js +1 -0
- package/src/pragma/index.js +21 -9
- package/src/pragma/is.js +27 -2
- package/src/switchable.js +1 -1
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const DEVTOOLS_SOURCE = '__SYGNAL_DEVTOOLS_PAGE__'
|
|
4
|
+
const EXTENSION_SOURCE = '__SYGNAL_DEVTOOLS_EXTENSION__'
|
|
5
|
+
const DEFAULT_MAX_HISTORY = 200
|
|
6
|
+
|
|
7
|
+
class SygnalDevTools {
|
|
8
|
+
constructor() {
|
|
9
|
+
this._connected = false
|
|
10
|
+
this._components = new Map()
|
|
11
|
+
this._stateHistory = []
|
|
12
|
+
this._maxHistory = DEFAULT_MAX_HISTORY
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
get connected() {
|
|
16
|
+
return this._connected && typeof window !== 'undefined'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ─── Initialization ─────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
init() {
|
|
22
|
+
if (typeof window === 'undefined') return
|
|
23
|
+
|
|
24
|
+
window.__SYGNAL_DEVTOOLS__ = this
|
|
25
|
+
|
|
26
|
+
window.addEventListener('message', (event) => {
|
|
27
|
+
if (event.source !== window) return
|
|
28
|
+
if (event.data?.source === EXTENSION_SOURCE) {
|
|
29
|
+
this._handleExtensionMessage(event.data)
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_handleExtensionMessage(msg) {
|
|
35
|
+
switch (msg.type) {
|
|
36
|
+
case 'CONNECT':
|
|
37
|
+
this._connected = true
|
|
38
|
+
if (msg.payload?.maxHistory) this._maxHistory = msg.payload.maxHistory
|
|
39
|
+
this._sendFullTree()
|
|
40
|
+
break
|
|
41
|
+
case 'DISCONNECT':
|
|
42
|
+
this._connected = false
|
|
43
|
+
break
|
|
44
|
+
case 'SET_DEBUG':
|
|
45
|
+
this._setDebug(msg.payload)
|
|
46
|
+
break
|
|
47
|
+
case 'TIME_TRAVEL':
|
|
48
|
+
this._timeTravel(msg.payload)
|
|
49
|
+
break
|
|
50
|
+
case 'GET_STATE':
|
|
51
|
+
this._sendComponentState(msg.payload.componentId)
|
|
52
|
+
break
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ─── Hooks (called from component.js) ────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
onComponentCreated(componentNumber, name, instance) {
|
|
59
|
+
const meta = {
|
|
60
|
+
id: componentNumber,
|
|
61
|
+
name: name,
|
|
62
|
+
isSubComponent: instance.isSubComponent,
|
|
63
|
+
hasModel: !!instance.model,
|
|
64
|
+
hasIntent: !!instance.intent,
|
|
65
|
+
hasContext: !!instance.context,
|
|
66
|
+
hasCalculated: !!instance.calculated,
|
|
67
|
+
components: Object.keys(instance.components || {}),
|
|
68
|
+
parentId: null,
|
|
69
|
+
children: [],
|
|
70
|
+
debug: instance._debug,
|
|
71
|
+
createdAt: Date.now(),
|
|
72
|
+
_instanceRef: new WeakRef(instance),
|
|
73
|
+
}
|
|
74
|
+
this._components.set(componentNumber, meta)
|
|
75
|
+
|
|
76
|
+
if (!this.connected) return
|
|
77
|
+
this._post('COMPONENT_CREATED', this._serializeMeta(meta))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
onStateChanged(componentNumber, name, state) {
|
|
81
|
+
if (!this.connected) return
|
|
82
|
+
|
|
83
|
+
const entry = {
|
|
84
|
+
componentId: componentNumber,
|
|
85
|
+
componentName: name,
|
|
86
|
+
timestamp: Date.now(),
|
|
87
|
+
state: this._safeClone(state),
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
this._stateHistory.push(entry)
|
|
91
|
+
if (this._stateHistory.length > this._maxHistory) {
|
|
92
|
+
this._stateHistory.shift()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
this._post('STATE_CHANGED', {
|
|
96
|
+
componentId: componentNumber,
|
|
97
|
+
componentName: name,
|
|
98
|
+
state: entry.state,
|
|
99
|
+
historyIndex: this._stateHistory.length - 1,
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
onActionDispatched(componentNumber, name, actionType, data) {
|
|
104
|
+
if (!this.connected) return
|
|
105
|
+
this._post('ACTION_DISPATCHED', {
|
|
106
|
+
componentId: componentNumber,
|
|
107
|
+
componentName: name,
|
|
108
|
+
actionType: actionType,
|
|
109
|
+
data: this._safeClone(data),
|
|
110
|
+
timestamp: Date.now(),
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
onSubComponentRegistered(parentNumber, childNumber) {
|
|
115
|
+
const parent = this._components.get(parentNumber)
|
|
116
|
+
const child = this._components.get(childNumber)
|
|
117
|
+
if (parent && child) {
|
|
118
|
+
child.parentId = parentNumber
|
|
119
|
+
if (!parent.children.includes(childNumber)) {
|
|
120
|
+
parent.children.push(childNumber)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!this.connected) return
|
|
125
|
+
this._post('TREE_UPDATED', {
|
|
126
|
+
parentId: parentNumber,
|
|
127
|
+
childId: childNumber,
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
onContextChanged(componentNumber, name, context) {
|
|
132
|
+
if (!this.connected) return
|
|
133
|
+
this._post('CONTEXT_CHANGED', {
|
|
134
|
+
componentId: componentNumber,
|
|
135
|
+
componentName: name,
|
|
136
|
+
context: this._safeClone(context),
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
onDebugLog(componentNumber, message) {
|
|
141
|
+
if (!this.connected) return
|
|
142
|
+
this._post('DEBUG_LOG', {
|
|
143
|
+
componentId: componentNumber,
|
|
144
|
+
message: message,
|
|
145
|
+
timestamp: Date.now(),
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ─── Commands (from extension to page) ───────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
_setDebug({ componentId, enabled }) {
|
|
152
|
+
if (typeof componentId === 'undefined' || componentId === null) {
|
|
153
|
+
if (typeof window !== 'undefined') window.SYGNAL_DEBUG = enabled ? 'true' : false
|
|
154
|
+
this._post('DEBUG_TOGGLED', { global: true, enabled })
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const meta = this._components.get(componentId)
|
|
159
|
+
if (meta && meta._instanceRef) {
|
|
160
|
+
const instance = meta._instanceRef.deref()
|
|
161
|
+
if (instance) {
|
|
162
|
+
instance._debug = enabled
|
|
163
|
+
meta.debug = enabled
|
|
164
|
+
this._post('DEBUG_TOGGLED', { componentId, enabled })
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
_timeTravel({ historyIndex }) {
|
|
170
|
+
const entry = this._stateHistory[historyIndex]
|
|
171
|
+
if (!entry) return
|
|
172
|
+
|
|
173
|
+
const app = typeof window !== 'undefined' && window.__SYGNAL_DEVTOOLS_APP__
|
|
174
|
+
if (app?.sinks?.STATE?.shamefullySendNext) {
|
|
175
|
+
app.sinks.STATE.shamefullySendNext(() => ({ ...entry.state }))
|
|
176
|
+
this._post('TIME_TRAVEL_APPLIED', {
|
|
177
|
+
historyIndex,
|
|
178
|
+
state: entry.state,
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
_sendComponentState(componentId) {
|
|
184
|
+
const meta = this._components.get(componentId)
|
|
185
|
+
if (meta && meta._instanceRef) {
|
|
186
|
+
const instance = meta._instanceRef.deref()
|
|
187
|
+
if (instance) {
|
|
188
|
+
this._post('COMPONENT_STATE', {
|
|
189
|
+
componentId,
|
|
190
|
+
state: this._safeClone(instance.currentState),
|
|
191
|
+
context: this._safeClone(instance.currentContext),
|
|
192
|
+
props: this._safeClone(instance.currentProps),
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
_sendFullTree() {
|
|
199
|
+
const tree = []
|
|
200
|
+
for (const [id, meta] of this._components) {
|
|
201
|
+
const instance = meta._instanceRef?.deref()
|
|
202
|
+
tree.push({
|
|
203
|
+
...this._serializeMeta(meta),
|
|
204
|
+
state: instance ? this._safeClone(instance.currentState) : null,
|
|
205
|
+
context: instance ? this._safeClone(instance.currentContext) : null,
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
this._post('FULL_TREE', {
|
|
209
|
+
components: tree,
|
|
210
|
+
history: this._stateHistory,
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ─── Transport ───────────────────────────────────────────────────────────────
|
|
215
|
+
|
|
216
|
+
_post(type, payload) {
|
|
217
|
+
if (typeof window === 'undefined') return
|
|
218
|
+
window.postMessage({
|
|
219
|
+
source: DEVTOOLS_SOURCE,
|
|
220
|
+
type,
|
|
221
|
+
payload,
|
|
222
|
+
}, '*')
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
_safeClone(obj) {
|
|
226
|
+
if (obj === undefined || obj === null) return obj
|
|
227
|
+
try {
|
|
228
|
+
return JSON.parse(JSON.stringify(obj))
|
|
229
|
+
} catch (e) {
|
|
230
|
+
return '[unserializable]'
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
_serializeMeta(meta) {
|
|
235
|
+
const { _instanceRef, ...rest } = meta
|
|
236
|
+
return rest
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ─── Singleton ────────────────────────────────────────────────────────────────
|
|
241
|
+
|
|
242
|
+
let instance = null
|
|
243
|
+
|
|
244
|
+
export function getDevTools() {
|
|
245
|
+
if (!instance) instance = new SygnalDevTools()
|
|
246
|
+
return instance
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export default getDevTools
|
|
@@ -14,11 +14,11 @@ function driverFromAsync(promiseReturningFunction, opts = {}) {
|
|
|
14
14
|
const functionName = promiseReturningFunction.name || '[anonymous function]'
|
|
15
15
|
const functionArgsType = typeof functionArgs
|
|
16
16
|
if (functionArgsType !== 'string' && functionArgsType !== 'function' && !(Array.isArray(functionArgs) && functionArgs.every((arg) => typeof arg === 'string'))) {
|
|
17
|
-
throw new Error(`The 'args' option for driverFromAsync(${
|
|
17
|
+
throw new Error(`The 'args' option for driverFromAsync(${functionName}) must be a string, array of strings, or a function. Received ${functionArgsType}`)
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
if (typeof selectorProperty !== 'string') {
|
|
21
|
-
throw new Error(`The 'selector' option for driverFromAsync(${
|
|
21
|
+
throw new Error(`The 'selector' option for driverFromAsync(${functionName}) must be a string. Received ${typeof selectorProperty}`)
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
return (fromApp$) => {
|
|
@@ -47,7 +47,7 @@ function driverFromAsync(promiseReturningFunction, opts = {}) {
|
|
|
47
47
|
argArr = functionArgs.map((arg) => preProcessed[arg])
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
-
const errMsg = `Error in driver created using driverFromAsync(${
|
|
50
|
+
const errMsg = `Error in driver created using driverFromAsync(${functionName})`
|
|
51
51
|
promiseReturningFunction(...argArr)
|
|
52
52
|
.then((innerVal) => {
|
|
53
53
|
const constructReply = (rawVal) => {
|
|
@@ -57,7 +57,7 @@ function driverFromAsync(promiseReturningFunction, opts = {}) {
|
|
|
57
57
|
if (typeof outgoing === 'object' && outgoing !== null) {
|
|
58
58
|
outgoing[selectorProperty] = incoming[selectorProperty]
|
|
59
59
|
} else {
|
|
60
|
-
console.warn(`The 'return' option for driverFromAsync(${
|
|
60
|
+
console.warn(`The 'return' option for driverFromAsync(${functionName}) was not set, but the promise returned an non-object. The result will be returned as-is, but the '${selectorProperty}' property will not be set, so will not be filtered by the 'select' method of the driver.`)
|
|
61
61
|
}
|
|
62
62
|
} else if (typeof returnProperty === 'string') {
|
|
63
63
|
outgoing = {
|
|
@@ -65,7 +65,7 @@ function driverFromAsync(promiseReturningFunction, opts = {}) {
|
|
|
65
65
|
[selectorProperty]: incoming[selectorProperty]
|
|
66
66
|
}
|
|
67
67
|
} else {
|
|
68
|
-
throw new Error(`The 'return' option for driverFromAsync(${
|
|
68
|
+
throw new Error(`The 'return' option for driverFromAsync(${functionName}) must be a string. Received ${typeof returnProperty}`)
|
|
69
69
|
}
|
|
70
70
|
return outgoing
|
|
71
71
|
}
|
|
@@ -81,12 +81,12 @@ function driverFromAsync(promiseReturningFunction, opts = {}) {
|
|
|
81
81
|
.then((innerProcessedOutgoing) => {
|
|
82
82
|
sendFn(constructReply(innerProcessedOutgoing))
|
|
83
83
|
})
|
|
84
|
-
.catch((err) => console.error(`${
|
|
84
|
+
.catch((err) => console.error(`${errMsg}: ${err}`))
|
|
85
85
|
} else {
|
|
86
|
-
sendFn(constructReply(
|
|
86
|
+
sendFn(constructReply(processedOutgoing))
|
|
87
87
|
}
|
|
88
88
|
})
|
|
89
|
-
.catch((err) => console.error(`${
|
|
89
|
+
.catch((err) => console.error(`${errMsg}: ${err}`))
|
|
90
90
|
} else {
|
|
91
91
|
const processedOutgoing = postFunction(innerVal, incoming)
|
|
92
92
|
if (typeof processedOutgoing.then === 'function') {
|
|
@@ -94,19 +94,19 @@ function driverFromAsync(promiseReturningFunction, opts = {}) {
|
|
|
94
94
|
.then((innerProcessedOutgoing) => {
|
|
95
95
|
sendFn(constructReply(innerProcessedOutgoing))
|
|
96
96
|
})
|
|
97
|
-
.catch((err) => console.error(`${
|
|
97
|
+
.catch((err) => console.error(`${errMsg}: ${err}`))
|
|
98
98
|
} else {
|
|
99
99
|
sendFn(constructReply(processedOutgoing))
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
})
|
|
103
|
-
.catch((err) => console.error(`${
|
|
103
|
+
.catch((err) => console.error(`${errMsg}: ${err}`))
|
|
104
104
|
},
|
|
105
105
|
error: (err) => {
|
|
106
|
-
console.error(`Error
|
|
106
|
+
console.error(`Error received from sink stream in driver created using driverFromAsync(${functionName}):`, err)
|
|
107
107
|
},
|
|
108
108
|
complete: () => {
|
|
109
|
-
console.warn(`Unexpected completion of sink stream to driver created using driverFromAsync(${
|
|
109
|
+
console.warn(`Unexpected completion of sink stream to driver created using driverFromAsync(${functionName})`)
|
|
110
110
|
}
|
|
111
111
|
})
|
|
112
112
|
|
package/src/extra/eventDriver.js
CHANGED
|
@@ -9,7 +9,8 @@ export default function eventBusDriver(out$) {
|
|
|
9
9
|
const events = new EventTarget()
|
|
10
10
|
|
|
11
11
|
out$.subscribe({
|
|
12
|
-
next: event => events.dispatchEvent(new CustomEvent('data', { detail: event }))
|
|
12
|
+
next: event => events.dispatchEvent(new CustomEvent('data', { detail: event })),
|
|
13
|
+
error: err => console.error('[EVENTS driver] Error in sink stream:', err)
|
|
13
14
|
})
|
|
14
15
|
|
|
15
16
|
return {
|
package/src/extra/logDriver.js
CHANGED
package/src/extra/processDrag.js
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
import xs from './xstreamCompat.js'
|
|
4
4
|
|
|
5
5
|
export default function processDrag({ draggable, dropZone } = {}, options = {}) {
|
|
6
|
+
if (draggable && typeof draggable.events !== 'function') {
|
|
7
|
+
throw new Error('processDrag: draggable must have an .events() method (e.g. DOM.select(...))')
|
|
8
|
+
}
|
|
9
|
+
if (dropZone && typeof dropZone.events !== 'function') {
|
|
10
|
+
throw new Error('processDrag: dropZone must have an .events() method (e.g. DOM.select(...))')
|
|
11
|
+
}
|
|
6
12
|
const { effectAllowed = 'move' } = options
|
|
7
13
|
|
|
8
14
|
const dragStart$ = draggable
|
package/src/extra/processForm.js
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
import xs from './xstreamCompat.js'
|
|
4
4
|
|
|
5
5
|
export default function processForm(form, options={}) {
|
|
6
|
+
if (!form || typeof form.events !== 'function') {
|
|
7
|
+
throw new Error('processForm: first argument must have an .events() method (e.g. DOM.select(...))')
|
|
8
|
+
}
|
|
6
9
|
let { events = ['input', 'submit'], preventDefault = true } = options
|
|
7
10
|
if (typeof events === 'string') events = [events]
|
|
8
11
|
|
package/src/extra/run.js
CHANGED
|
@@ -4,8 +4,15 @@ import { makeDOMDriver } from "@cycle/dom"
|
|
|
4
4
|
import eventBusDriver from "./eventDriver"
|
|
5
5
|
import logDriver from "./logDriver"
|
|
6
6
|
import component, { ABORT } from "../component"
|
|
7
|
+
import { getDevTools } from "./devtools"
|
|
7
8
|
|
|
8
9
|
export default function run(app, drivers={}, options={}) {
|
|
10
|
+
// Initialize DevTools instrumentation bridge early (before component creation)
|
|
11
|
+
if (typeof window !== 'undefined') {
|
|
12
|
+
const dt = getDevTools()
|
|
13
|
+
dt.init()
|
|
14
|
+
}
|
|
15
|
+
|
|
9
16
|
const { mountPoint='#root', fragments=true, useDefaultDrivers=true } = options
|
|
10
17
|
if (!app.isSygnalComponent) {
|
|
11
18
|
const name = app.name || app.componentName || app.label || "FUNCTIONAL_COMPONENT"
|
|
@@ -55,6 +62,11 @@ export default function run(app, drivers={}, options={}) {
|
|
|
55
62
|
|
|
56
63
|
const exposed = { sources, sinks, dispose }
|
|
57
64
|
|
|
65
|
+
// Store app reference for time-travel
|
|
66
|
+
if (typeof window !== 'undefined') {
|
|
67
|
+
window.__SYGNAL_DEVTOOLS_APP__ = exposed
|
|
68
|
+
}
|
|
69
|
+
|
|
58
70
|
const swapToComponent = (newComponent, state) => {
|
|
59
71
|
const persistedState = (typeof window !== 'undefined') ? window.__SYGNAL_HMR_PERSISTED_STATE : undefined
|
|
60
72
|
const fallbackState = typeof persistedState !== 'undefined' ? persistedState : app.initialState
|
package/src/index.d.ts
CHANGED
|
@@ -3,7 +3,8 @@ import type { StateSource } from '@cycle/state'
|
|
|
3
3
|
import xsDefault from 'xstream'
|
|
4
4
|
import type { MemoryStream, Stream } from 'xstream'
|
|
5
5
|
|
|
6
|
-
export
|
|
6
|
+
export declare const ABORT: unique symbol
|
|
7
|
+
export type ABORT = typeof ABORT
|
|
7
8
|
|
|
8
9
|
export type DriverSpec<SOURCE = any, SINK = any> = {
|
|
9
10
|
source: SOURCE;
|
|
@@ -188,9 +189,13 @@ interface ComponentIntent<STATE, DRIVERS, ACTIONS> {
|
|
|
188
189
|
(args: CombinedSources<STATE, DRIVERS>): Partial<Actions<ACTIONS>>
|
|
189
190
|
}
|
|
190
191
|
|
|
192
|
+
type CalculatedFieldValue<FULL_STATE, RETURN> =
|
|
193
|
+
| StateOnlyReducer<FULL_STATE, RETURN>
|
|
194
|
+
| [ReadonlyArray<string & keyof FULL_STATE>, StateOnlyReducer<FULL_STATE, RETURN>]
|
|
195
|
+
|
|
191
196
|
type Calculated<STATE, CALCULATED> = keyof CALCULATED extends never
|
|
192
|
-
? { [field: string]: boolean |
|
|
193
|
-
: { [CALCULATED_KEY in keyof CALCULATED]: boolean |
|
|
197
|
+
? { [field: string]: boolean | CalculatedFieldValue<STATE, any> }
|
|
198
|
+
: { [CALCULATED_KEY in keyof CALCULATED]: boolean | CalculatedFieldValue<STATE & CALCULATED, CALCULATED[CALCULATED_KEY]> }
|
|
194
199
|
|
|
195
200
|
type Context<STATE, CONTEXT> = keyof CONTEXT extends never
|
|
196
201
|
? { [field: string]: boolean | StateOnlyReducer<STATE, any> }
|
|
@@ -444,7 +449,6 @@ export function driverFromAsync<INCOMING = any, RETURN = any, OUTGOING = any>(
|
|
|
444
449
|
options?: DriverFromAsyncOptions<INCOMING, OUTGOING, RETURN>
|
|
445
450
|
): (fromApp$: Stream<INCOMING>) => AsyncDriverFromFunction<INCOMING, OUTGOING>
|
|
446
451
|
|
|
447
|
-
export const ABORT: ABORT
|
|
448
452
|
export const xs: typeof xsDefault
|
|
449
453
|
|
|
450
454
|
export { default as debounce } from 'xstream/extra/debounce'
|
package/src/index.js
CHANGED
|
@@ -13,6 +13,7 @@ export { default as run } from './extra/run'
|
|
|
13
13
|
export { default as enableHMR } from './extra/hmr'
|
|
14
14
|
export { default as classes } from './extra/classes'
|
|
15
15
|
export { default as xs } from './extra/xstreamCompat.js'
|
|
16
|
+
export { getDevTools } from './extra/devtools'
|
|
16
17
|
|
|
17
18
|
// export dom helper functions (h, div, ...)
|
|
18
19
|
export * from '@cycle/dom'
|
package/src/pragma/index.js
CHANGED
|
@@ -13,17 +13,29 @@ const createTextElement = (text) => !is.text(text) ? undefined : {
|
|
|
13
13
|
key: undefined
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
const applySvg = (vnode) => {
|
|
17
|
+
// Skip text vnodes (sel is undefined) and nullish values
|
|
18
|
+
if (!vnode || is.undefinedv(vnode.sel)) return vnode
|
|
19
|
+
|
|
20
|
+
const data = vnode.data || {}
|
|
21
|
+
const props = data.props || {}
|
|
22
|
+
const propsWithoutClassName = fn.omit('className', props)
|
|
23
|
+
const classAttr = props.className !== undefined ? { class: props.className } : {}
|
|
24
|
+
const mergedAttrs = fn.assign({}, propsWithoutClassName, classAttr, data.attrs || {})
|
|
25
|
+
|
|
26
|
+
return fn.assign(vnode,
|
|
27
|
+
{ data: fn.omit('props', fn.assign({}, data,
|
|
28
|
+
{ ns: 'http://www.w3.org/2000/svg', attrs: mergedAttrs }
|
|
22
29
|
)) },
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
// foreignObject contains HTML, not SVG — do not recurse into its children
|
|
31
|
+
{ children: (!Array.isArray(vnode.children) || vnode.sel === 'foreignObject')
|
|
32
|
+
? vnode.children
|
|
33
|
+
: vnode.children.map((child) => applySvg(child))
|
|
25
34
|
}
|
|
26
35
|
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const considerSvg = (vnode) => !is.svg(vnode) ? vnode : applySvg(vnode)
|
|
27
39
|
|
|
28
40
|
const rewrites = {
|
|
29
41
|
for: 'attrs',
|
|
@@ -79,7 +91,7 @@ const applyFocusProps = (data) => {
|
|
|
79
91
|
return data
|
|
80
92
|
}
|
|
81
93
|
|
|
82
|
-
const sanitizeData = (data, modules) => applyFocusProps(
|
|
94
|
+
const sanitizeData = (data, modules) => applyFocusProps(rewriteModules(fn.deepifyKeys(data, modules), modules))
|
|
83
95
|
|
|
84
96
|
const sanitizeText = (children) => children.length > 1 || !is.text(children[0]) ? undefined : children[0].toString()
|
|
85
97
|
|
package/src/pragma/is.js
CHANGED
|
@@ -17,7 +17,32 @@ export const fun = (v) => typeof v === 'function'
|
|
|
17
17
|
|
|
18
18
|
export const vnode = (v) => object(v) && 'sel' in v && 'data' in v && 'children' in v && 'text' in v
|
|
19
19
|
|
|
20
|
-
const svgPropsMap = {
|
|
21
|
-
|
|
20
|
+
const svgPropsMap = {
|
|
21
|
+
// Container / structural
|
|
22
|
+
svg: 1, g: 1, defs: 1, symbol: 1, use: 1,
|
|
23
|
+
// Shape
|
|
24
|
+
circle: 1, ellipse: 1, line: 1, path: 1, polygon: 1, polyline: 1, rect: 1,
|
|
25
|
+
// Text (no HTML collision: HTML has no <text>, <tspan>, or <textPath>)
|
|
26
|
+
text: 1, tspan: 1, textPath: 1,
|
|
27
|
+
// Gradient / paint
|
|
28
|
+
linearGradient: 1, radialGradient: 1, stop: 1, pattern: 1,
|
|
29
|
+
// Clipping / masking
|
|
30
|
+
clipPath: 1, mask: 1,
|
|
31
|
+
// Marker
|
|
32
|
+
marker: 1,
|
|
33
|
+
// Filter primitives
|
|
34
|
+
filter: 1, feBlend: 1, feColorMatrix: 1, feComponentTransfer: 1,
|
|
35
|
+
feComposite: 1, feConvolveMatrix: 1, feDiffuseLighting: 1,
|
|
36
|
+
feDisplacementMap: 1, feDropShadow: 1, feFlood: 1, feGaussianBlur: 1,
|
|
37
|
+
feImage: 1, feMerge: 1, feMergeNode: 1, feMorphology: 1, feOffset: 1,
|
|
38
|
+
fePointLight: 1, feSpecularLighting: 1, feSpotLight: 1, feTile: 1,
|
|
39
|
+
feTurbulence: 1, feFuncR: 1, feFuncG: 1, feFuncB: 1, feFuncA: 1,
|
|
40
|
+
// Descriptive (excluding 'title' — collides with HTML <title>)
|
|
41
|
+
desc: 1, metadata: 1,
|
|
42
|
+
// Other (excluding 'a', 'image', 'style', 'script' — collide with HTML)
|
|
43
|
+
foreignObject: 1, switch: 1,
|
|
44
|
+
// Animation
|
|
45
|
+
animate: 1, animateMotion: 1, animateTransform: 1, set: 1, mpath: 1,
|
|
46
|
+
}
|
|
22
47
|
|
|
23
48
|
export const svg = (v) => v.sel in svgPropsMap
|
package/src/switchable.js
CHANGED
|
@@ -30,7 +30,7 @@ export default function switchable(factories, name$, initial, opts={}) {
|
|
|
30
30
|
const mapFunction = (nameType === 'function' && name$) || (state => state[name$])
|
|
31
31
|
return sources => {
|
|
32
32
|
const state$ = sources && ((typeof stateSourceName === 'string' && sources[stateSourceName]) || sources.STATE || sources.state).stream
|
|
33
|
-
if (!state$ instanceof Stream) throw new Error(`Could not find the state source: ${
|
|
33
|
+
if (!(state$ instanceof Stream)) throw new Error(`Could not find the state source: ${stateSourceName}`)
|
|
34
34
|
const _name$ = state$
|
|
35
35
|
.map(mapFunction)
|
|
36
36
|
.filter(name => typeof name === 'string')
|