foldkit 0.160.0 → 0.161.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/calendar/index.d.ts +1 -1
- package/dist/calendar/index.d.ts.map +1 -1
- package/dist/calendar/index.js +1 -1
- package/dist/calendar/locale.d.ts +219 -10
- package/dist/calendar/locale.d.ts.map +1 -1
- package/dist/calendar/locale.js +166 -82
- package/dist/calendar/public.d.ts +1 -1
- package/dist/calendar/public.d.ts.map +1 -1
- package/dist/calendar/public.js +1 -1
- package/dist/command/index.d.ts +9 -12
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +9 -12
- package/dist/devTools/protocol.d.ts +9 -1
- package/dist/devTools/protocol.d.ts.map +1 -1
- package/dist/devTools/protocol.js +2 -1
- package/dist/devTools/serialize.d.ts.map +1 -1
- package/dist/devTools/serialize.js +1 -0
- package/dist/devTools/store.d.ts +3 -0
- package/dist/devTools/store.d.ts.map +1 -1
- package/dist/devTools/store.js +24 -0
- package/dist/dom/dialogRuntime.d.ts +9 -0
- package/dist/dom/dialogRuntime.d.ts.map +1 -0
- package/dist/dom/dialogRuntime.js +3 -0
- package/dist/dom/dom.d.ts +22 -11
- package/dist/dom/dom.d.ts.map +1 -1
- package/dist/dom/dom.js +196 -18
- package/dist/dom/inert.d.ts +9 -0
- package/dist/dom/inert.d.ts.map +1 -1
- package/dist/dom/inert.js +74 -2
- package/dist/managedResource/managedResource.d.ts +44 -2
- package/dist/managedResource/managedResource.d.ts.map +1 -1
- package/dist/managedResource/managedResource.js +40 -10
- package/dist/runtime/devToolsIntegration.d.ts +8 -3
- package/dist/runtime/devToolsIntegration.d.ts.map +1 -1
- package/dist/runtime/devToolsIntegration.js +20 -3
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +54 -12
- package/dist/subscription/fromEvent.d.ts +103 -28
- package/dist/subscription/fromEvent.d.ts.map +1 -1
- package/dist/subscription/fromEvent.js +48 -27
- package/dist/subscription/keyboardShortcuts.d.ts +110 -0
- package/dist/subscription/keyboardShortcuts.d.ts.map +1 -0
- package/dist/subscription/keyboardShortcuts.js +444 -0
- package/dist/subscription/public.d.ts +3 -1
- package/dist/subscription/public.d.ts.map +1 -1
- package/dist/subscription/public.js +1 -0
- package/dist/subscription/subscription.d.ts +61 -1
- package/dist/subscription/subscription.d.ts.map +1 -1
- package/dist/subscription/subscription.js +51 -8
- package/dist/update/public.d.ts +1 -1
- package/dist/update/public.d.ts.map +1 -1
- package/dist/update/public.js +1 -1
- package/dist/update/update.d.ts +189 -1
- package/dist/update/update.d.ts.map +1 -1
- package/dist/update/update.js +178 -16
- package/package.json +1 -1
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { Array, Effect, Option, PubSub, Stream, SubscriptionRef, } from 'effect';
|
|
2
2
|
import { createDevToolsStore, } from '../devTools/store.js';
|
|
3
|
+
import { extractSubmodelInfo, isTagged } from '../devTools/submodelPath.js';
|
|
3
4
|
import { startWebSocketBridge } from '../devTools/webSocketBridge.js';
|
|
4
5
|
import { MountRuntime, liveViewStateChanges, } from '../mount/index.js';
|
|
5
6
|
import { resolveDevToolsConfig, resolveDevToolsKeyframeInterval, resolveDevToolsMaxEntries, resolveExcludeFromHistoryTags, } from './devToolsConfig.js';
|
|
6
|
-
const toCommandRecord = (command) => command.args !== undefined
|
|
7
|
-
? {
|
|
8
|
-
|
|
7
|
+
const toCommandRecord = ({ id, command }) => command.args !== undefined
|
|
8
|
+
? {
|
|
9
|
+
id,
|
|
10
|
+
name: command.name,
|
|
11
|
+
args: command.args,
|
|
12
|
+
maybeSubmodelPath: Option.none(),
|
|
13
|
+
}
|
|
14
|
+
: { id, name: command.name, maybeSubmodelPath: Option.none() };
|
|
9
15
|
/**
|
|
10
16
|
* Builds the DevTools integration for one runtime from the `devTools`
|
|
11
17
|
* config. `update` and `maybeFreezeModel` drive time-travel replay, and
|
|
@@ -169,8 +175,18 @@ export const makeDevToolsIntegration = ({ devTools, update, maybeFreezeModel, en
|
|
|
169
175
|
}
|
|
170
176
|
}
|
|
171
177
|
};
|
|
178
|
+
const recordCommandResult = (id, message) => {
|
|
179
|
+
if (devToolsStore === null) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const submodelPath = isTagged(message)
|
|
183
|
+
? extractSubmodelInfo(message._tag, message).submodelPath
|
|
184
|
+
: [];
|
|
185
|
+
Effect.runFork(devToolsStore.recordResolvedCommand(id, submodelPath));
|
|
186
|
+
};
|
|
172
187
|
const resumeDevTools = Effect.suspend(() => devToolsStore !== null ? devToolsStore.resume : Effect.void);
|
|
173
188
|
return {
|
|
189
|
+
isRecordingCommands: Option.isSome(resolvedDevTools),
|
|
174
190
|
mountTracker,
|
|
175
191
|
mountRuntime,
|
|
176
192
|
drainMountEvents,
|
|
@@ -181,6 +197,7 @@ export const makeDevToolsIntegration = ({ devTools, update, maybeFreezeModel, en
|
|
|
181
197
|
resumeDevTools,
|
|
182
198
|
recordInit,
|
|
183
199
|
recordMessage,
|
|
200
|
+
recordCommandResult,
|
|
184
201
|
attachRenderedMounts,
|
|
185
202
|
};
|
|
186
203
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/runtime/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,MAAM,EAEN,KAAK,EACL,KAAK,EACL,MAAM,EAGN,MAAM,EAEP,MAAM,QAAQ,CAAA;
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/runtime/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,MAAM,EAEN,KAAK,EACL,KAAK,EACL,MAAM,EAGN,MAAM,EAEP,MAAM,QAAQ,CAAA;AAIf,OAAO,EACL,QAAQ,EACR,KAAK,WAAW,EAEjB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AACnE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;AAE7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAA;AACpE,OAAO,KAAK,EAAE,MAAM,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAChE,OAAO,EAAE,GAAG,EAA+B,MAAM,iBAAiB,CAAA;AAClE,OAAO,EACL,KAAK,aAAa,EAEnB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAGzD,OAAO,EACL,KAAK,aAAa,EAInB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,eAAe,EAErB,MAAM,uBAAuB,CAAA;AAY9B,OAAO,EACL,KAAK,UAAU,EAKhB,MAAM,gBAAgB,CAAA;AAEvB,OAAO,EACL,KAAK,oBAAoB,EAE1B,MAAM,qBAAqB,CAAA;AAS5B,wHAAwH;AACxH,MAAM,MAAM,aAAa,CACvB,KAAK,EACL,OAAO,EACP,KAAK,EACL,SAAS,GAAG,KAAK,EACjB,uBAAuB,GAAG,KAAK,EAC/B,CAAC,SAAS,KAAK,GAAG,SAAS,GAAG,SAAS,EACvC,IAAI,SAAS,aAAa,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,IAChE,QAAQ,CAAC;IACX,IAAI,EAAE,IAAI,CAAA;IACV,KAAK,EAAE,CAAC,CAAA;IACR,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;IACjD,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;IACjD,eAAe,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,CAAA;IACtE,eAAe,EAAE,OAAO,CAAA;IACxB,IAAI,EAAE,CACJ,KAAK,EAAE,KAAK,EACZ,GAAG,CAAC,EAAE,GAAG,KACN,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,GAAG,uBAAuB,CAAC,CAAA;IACtE,MAAM,EAAE,CACN,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,OAAO,KACb,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,GAAG,uBAAuB,CAAC,CAAA;IACtE,IAAI,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAA;IACzD;;;;;;;OAOG;IACH,cAAc,EAAE,OAAO,CAAA;IACvB,aAAa,CAAC,EAAE,aAAa,CAC3B,KAAK,EACL,OAAO,EACP,SAAS,GAAG,uBAAuB,CACpC,CAAA;IACD,SAAS,EAAE,WAAW,CAAA;IACtB;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,OAAO,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAA;IAChC,KAAK,CAAC,EAAE,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IACnC,IAAI,CAAC,EAAE,UAAU,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IACjC;;;;;;;;;;;;;;;;;;OAkBG;IACH,cAAc,CAAC,EAAE,oBAAoB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IACrD;;;;;;;;;;;OAWG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB;;;;;;;;;;;;;;OAcG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACH,SAAS,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;IAClC;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,uBAAuB,CAAC,CAAA;IAC5E,QAAQ,CAAC,EAAE,cAAc,CAAA;CAC1B,CAAC,CAAA;AAEF,MAAM,MAAM,iBAAiB,CAAC,KAAK,IAAI,QAAQ,CAAC;IAO9C,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,CAAA;CAC9C,CAAC,CAAA;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,CAC3B,CAAC,SAAS,KAAK,GAAG,SAAS,GAAG,SAAS,EACvC,KAAK,GAAG,IAAI,EACZ,SAAS,GAAG,KAAK,EACjB,IAAI,SAAS,aAAa,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,IAChE,QAAQ,CAAC;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,CAAC,cAAc,CAAC,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACxD,KAAK,EAAE,CAAC,CAAA;IACR,sBAAsB,CAAC,EAAE,QAAQ,CAAC;QAChC,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,KAAK,CAAA;QAC9B,SAAS,EAAE,CAAC,SAAS,EAAE,SAAS,KAAK,SAAS,CAAA;QAC9C,IAAI,EAAE,IAAI,CAAA;KACX,CAAC,CAAA;CACH,CAAC,CAAA;AAEF,KAAK,gBAAgB,GAAG;IACtB,SAAS,EAAE,CACT,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,EAC5C,cAAc,CAAC,EAAE,OAAO,EACxB,QAAQ,CAAC,EAAE,QAAQ,EACnB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,EACtC,OAAO,CAAC,EAAE,MAAM,KACb,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACxB,IAAI,EAAE,aAAa,GAAG,SAAS,CAAA;IAC/B,aAAa,EAAE,OAAO,CAAA;IACtB,gBAAgB,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAA;CACnD,CAAA;AAED,eAAO,MAAM,gBAAgB,mCAA0C,CAAA;AAEvE,eAAO,MAAM,WAAW,GACtB,KAAK,EACL,OAAO,EACP,KAAK,EACL,SAAS,EACT,uBAAuB,EACvB,CAAC,SAAS,KAAK,GAAG,SAAS,EAC3B,IAAI,SAAS,aAAa,GAAG,SAAS,kRAwBrC,aAAa,CACd,KAAK,EACL,OAAO,EACP,KAAK,EACL,SAAS,EACT,uBAAuB,EACvB,CAAC,EACD,IAAI,CACL,KAAG,iBAAiB,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,CA+lB9C,CAAA"}
|
package/dist/runtime/runtime.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Array, Duration, Effect, Exit, Option, Predicate, PubSub, Schema, pipe, } from 'effect';
|
|
2
|
+
import { DialogRuntime } from '../dom/dialogRuntime.js';
|
|
3
|
+
import { releaseDialogResources } from '../dom/dom.js';
|
|
2
4
|
import { __htmlBuilder as htmlBuilderFor, } from '../html/index.js';
|
|
3
5
|
import { RenderCommit, createCommitNotifier } from '../render/commit.js';
|
|
4
6
|
import { fromString as urlFromString } from '../url/index.js';
|
|
@@ -56,6 +58,7 @@ export const makeRuntime = ({ ports, kind, Model, Flags: FlagsCodec, configuredF
|
|
|
56
58
|
// the same signal. A commit in one embedded application must never wake a
|
|
57
59
|
// `Render.afterCommit` awaiting inside another.
|
|
58
60
|
const commitNotifier = createCommitNotifier();
|
|
61
|
+
const dialogIds = new Set();
|
|
59
62
|
return Effect.scoped(Effect.gen(function* () {
|
|
60
63
|
if (runtimeId === '') {
|
|
61
64
|
return yield* Effect.die(new Error('[foldkit] Runtime container must have an `id` for Model preservation across reloads. ' +
|
|
@@ -174,7 +177,12 @@ export const makeRuntime = ({ ports, kind, Model, Flags: FlagsCodec, configuredF
|
|
|
174
177
|
maybeFreezeModel,
|
|
175
178
|
enqueueMessageEffect,
|
|
176
179
|
});
|
|
177
|
-
const { installDevToolsStore, recordInit, recordMessage } = devToolsIntegration;
|
|
180
|
+
const { isRecordingCommands, installDevToolsStore, recordInit, recordMessage, recordCommandResult, } = devToolsIntegration;
|
|
181
|
+
let nextCommandId = 0;
|
|
182
|
+
const assignCommandIds = (commands) => Array.map(commands, command => ({
|
|
183
|
+
id: nextCommandId++,
|
|
184
|
+
command,
|
|
185
|
+
}));
|
|
178
186
|
if (import.meta.hot) {
|
|
179
187
|
yield* Effect.addFinalizer(() => Effect.sync(() => duplicateIdScanner?.cancel()));
|
|
180
188
|
}
|
|
@@ -210,6 +218,7 @@ export const makeRuntime = ({ ports, kind, Model, Flags: FlagsCodec, configuredF
|
|
|
210
218
|
messageQueue,
|
|
211
219
|
devToolsIntegration,
|
|
212
220
|
});
|
|
221
|
+
yield* Effect.addFinalizer(() => Effect.forEach(pipe(dialogIds, Array.fromIterable, Array.reverse), releaseDialogResources, { discard: true }));
|
|
213
222
|
// NOTE: the fork is deferred one microtask so a Command's Effect
|
|
214
223
|
// never begins on the dispatching stack. Commands are facts from
|
|
215
224
|
// outside the update loop; their results always arrive
|
|
@@ -223,7 +232,7 @@ export const makeRuntime = ({ ports, kind, Model, Flags: FlagsCodec, configuredF
|
|
|
223
232
|
// Stream step) would reschedule through clamped `setTimeout`
|
|
224
233
|
// instead of the browser microtask scheduler carried by
|
|
225
234
|
// `runtimeContext`.
|
|
226
|
-
const forkCommand = (command, message) => {
|
|
235
|
+
const forkCommand = (command, message, id) => {
|
|
227
236
|
queueMicrotask(() => {
|
|
228
237
|
// NOTE: `isCrashed` as well as `isRuntimeDisposed`. A crash is
|
|
229
238
|
// terminal but does not dispose the runtime, and a Command forked
|
|
@@ -235,9 +244,13 @@ export const makeRuntime = ({ ports, kind, Model, Flags: FlagsCodec, configuredF
|
|
|
235
244
|
if (status.isRuntimeDisposed || status.isCrashed) {
|
|
236
245
|
return;
|
|
237
246
|
}
|
|
238
|
-
|
|
247
|
+
const providedCommand = command.effect.pipe(Effect.withSpan(command.name, {
|
|
239
248
|
attributes: command.args ?? {},
|
|
240
|
-
}), provideAllResources
|
|
249
|
+
}), provideAllResources);
|
|
250
|
+
const recordedCommand = id !== undefined
|
|
251
|
+
? providedCommand.pipe(Effect.tap(result => Effect.sync(() => recordCommandResult(id, result))))
|
|
252
|
+
: providedCommand;
|
|
253
|
+
Effect.runForkWith(runtimeContext)(Effect.forkIn(runtimeScope)(recordedCommand.pipe(Effect.flatMap(enqueueMessageEffect), Effect.catchCause(cause => crashWith(cause, message)))));
|
|
241
254
|
});
|
|
242
255
|
};
|
|
243
256
|
const processMessagePlain = (message) => {
|
|
@@ -263,14 +276,22 @@ export const makeRuntime = ({ ports, kind, Model, Flags: FlagsCodec, configuredF
|
|
|
263
276
|
}
|
|
264
277
|
scheduleRenderFrame();
|
|
265
278
|
}
|
|
266
|
-
if (
|
|
279
|
+
if (isRecordingCommands) {
|
|
280
|
+
const commandInvocations = assignCommandIds(commands);
|
|
281
|
+
for (const { command, id } of commandInvocations) {
|
|
282
|
+
forkCommand(
|
|
283
|
+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
284
|
+
command, Option.some(message), id);
|
|
285
|
+
}
|
|
286
|
+
recordMessage(message, currentModel, nextModel, commandInvocations);
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
267
289
|
for (const command of commands) {
|
|
268
290
|
forkCommand(
|
|
269
291
|
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
270
292
|
command, Option.some(message));
|
|
271
293
|
}
|
|
272
294
|
}
|
|
273
|
-
recordMessage(message, currentModel, nextModel, commands);
|
|
274
295
|
};
|
|
275
296
|
yield* installDevToolsStore(devToolsRenderBridge);
|
|
276
297
|
const initRenderExit = yield* Effect.exit(render(initModel, Option.none()));
|
|
@@ -285,7 +306,12 @@ export const makeRuntime = ({ ports, kind, Model, Flags: FlagsCodec, configuredF
|
|
|
285
306
|
if (isPreserveScrollActive) {
|
|
286
307
|
yield* restorePreservedScrollPosition(runtimeId);
|
|
287
308
|
}
|
|
288
|
-
|
|
309
|
+
const initCommandInvocations = isRecordingCommands
|
|
310
|
+
? assignCommandIds(initCommands)
|
|
311
|
+
: [];
|
|
312
|
+
if (isRecordingCommands) {
|
|
313
|
+
yield* recordInit(initModel, initCommandInvocations);
|
|
314
|
+
}
|
|
289
315
|
if (subscriptions) {
|
|
290
316
|
yield* forkSubscriptionFibers({
|
|
291
317
|
subscriptions,
|
|
@@ -328,10 +354,19 @@ export const makeRuntime = ({ ports, kind, Model, Flags: FlagsCodec, configuredF
|
|
|
328
354
|
// ManagedResources, ports) is attached. forkCommand also defers each
|
|
329
355
|
// start by a microtask, so a fully synchronous init Command still
|
|
330
356
|
// delivers its result asynchronously.
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
357
|
+
if (isRecordingCommands) {
|
|
358
|
+
for (const { command, id } of initCommandInvocations) {
|
|
359
|
+
forkCommand(
|
|
360
|
+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
361
|
+
command, Option.none(), id);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
for (const command of initCommands) {
|
|
366
|
+
forkCommand(
|
|
367
|
+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
368
|
+
command, Option.none());
|
|
369
|
+
}
|
|
335
370
|
}
|
|
336
371
|
completeBoot();
|
|
337
372
|
// NOTE: suspend forever. Messages are processed synchronously on
|
|
@@ -340,7 +375,14 @@ export const makeRuntime = ({ ports, kind, Model, Flags: FlagsCodec, configuredF
|
|
|
340
375
|
// runtime scope open until interruption (an embedded app's dispose)
|
|
341
376
|
// or the document goes away.
|
|
342
377
|
yield* Effect.never;
|
|
343
|
-
})).pipe(Effect.provideService(RenderCommit, commitNotifier.service)
|
|
378
|
+
})).pipe(Effect.provideService(RenderCommit, commitNotifier.service), Effect.provideService(DialogRuntime, {
|
|
379
|
+
register: id => {
|
|
380
|
+
dialogIds.add(id);
|
|
381
|
+
},
|
|
382
|
+
unregister: id => {
|
|
383
|
+
dialogIds.delete(id);
|
|
384
|
+
},
|
|
385
|
+
}));
|
|
344
386
|
};
|
|
345
387
|
const start = (preservedModel) => startWith(Option.none(), preservedModel, 'Fresh');
|
|
346
388
|
const program = {
|
|
@@ -1,4 +1,46 @@
|
|
|
1
1
|
import { Option, Stream } from 'effect';
|
|
2
|
+
declare const EventMapMarker: unique symbol;
|
|
3
|
+
/**
|
|
4
|
+
* An `EventTarget` that declares the events it dispatches, so the `fromEvent`
|
|
5
|
+
* helpers can resolve an event name to its event type the way they do for
|
|
6
|
+
* `window`, `document`, and the DOM interfaces lib.dom declares event maps
|
|
7
|
+
* for.
|
|
8
|
+
*
|
|
9
|
+
* Annotate a target with this and the mapper's parameter follows from the
|
|
10
|
+
* event name, including a `CustomEvent`'s `detail`. A declared event overrides
|
|
11
|
+
* the corresponding native event and otherwise augments the target's native
|
|
12
|
+
* events, so an element that dispatches custom events can be annotated without
|
|
13
|
+
* losing events such as `click`. Any `EventTarget` is assignable to it, so the
|
|
14
|
+
* annotation is the only change needed.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* const slowWarningTarget: Subscription.TypedEventTarget<{
|
|
19
|
+
* 'foldkit:slow-warning': CustomEvent<SlowWarningReport>
|
|
20
|
+
* }> = new EventTarget()
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export interface TypedEventTarget<EventMap extends {
|
|
24
|
+
readonly [Type in keyof EventMap]: Event;
|
|
25
|
+
}> extends EventTarget {
|
|
26
|
+
readonly [EventMapMarker]?: EventMap;
|
|
27
|
+
}
|
|
28
|
+
type EventMapMarkerKey = typeof EventMapMarker;
|
|
29
|
+
type DeclaredEventMap<Target> = EventMapMarkerKey extends keyof Target ? NonNullable<Target[EventMapMarkerKey & keyof Target]> : never;
|
|
30
|
+
type EventFromHandler<Handler> = NonNullable<Handler> extends (event: infer EventType, ...args: ReadonlyArray<unknown>) => unknown ? EventType extends Event ? EventType : never : never;
|
|
31
|
+
type EventMapFromHandlers<Target> = {
|
|
32
|
+
readonly [Key in keyof Target as Key extends `on${infer Type}` ? [EventFromHandler<Target[Key]>] extends [never] ? never : Type : never]: EventFromHandler<Target[Key]>;
|
|
33
|
+
};
|
|
34
|
+
type OverrideEventMap<Base, Declared> = Omit<Base, keyof Declared> & Declared;
|
|
35
|
+
type ElementEventMapOf<Target> = Target extends HTMLVideoElement ? HTMLVideoElementEventMap : Target extends HTMLMediaElement ? HTMLMediaElementEventMap : Target extends HTMLBodyElement ? HTMLBodyElementEventMap : Target extends HTMLFrameSetElement ? HTMLFrameSetElementEventMap : Target extends HTMLElement ? HTMLElementEventMap : Target extends MathMLElement ? MathMLElementEventMap : Target extends SVGSVGElement ? SVGSVGElementEventMap : Target extends SVGElement ? SVGElementEventMap : Target extends Element ? ElementEventMap & GlobalEventHandlersEventMap : never;
|
|
36
|
+
type NativeEventMapOf<Target> = Target extends Window ? WindowEventMap : Target extends Document ? DocumentEventMap : Target extends XMLHttpRequest ? XMLHttpRequestEventMap : Target extends XMLHttpRequestEventTarget ? XMLHttpRequestEventTargetEventMap : [ElementEventMapOf<Target>] extends [never] ? EventMapFromHandlers<Target> : ElementEventMapOf<Target>;
|
|
37
|
+
type EventMapOfMember<Target> = [DeclaredEventMap<Target>] extends [never] ? [keyof NativeEventMapOf<Target>] extends [never] ? Readonly<Record<string, Event>> : NativeEventMapOf<Target> : [keyof NativeEventMapOf<Target>] extends [never] ? DeclaredEventMap<Target> : OverrideEventMap<NativeEventMapOf<Target>, DeclaredEventMap<Target>>;
|
|
38
|
+
type EventMapOf<Target> = Target extends unknown ? EventMapOfMember<Target> : never;
|
|
39
|
+
type EventTypeOf<Target> = keyof EventMapOf<Target> & string;
|
|
40
|
+
type EventOf<Target, Type extends EventTypeOf<Target>> = Type extends keyof EventMapOf<Target> ? EventMapOf<Target>[Type] extends infer Resolved extends Event ? Resolved : Event : Event;
|
|
41
|
+
type PreventDefaultEventListenerOptions = Omit<AddEventListenerOptions, 'passive'> & Readonly<{
|
|
42
|
+
passive?: false;
|
|
43
|
+
}>;
|
|
2
44
|
/**
|
|
3
45
|
* Configuration for the `fromEvent` Stream helper.
|
|
4
46
|
*
|
|
@@ -8,6 +50,10 @@ import { Option, Stream } from 'effect';
|
|
|
8
50
|
* pass the `EventTarget` directly for always-present globals like `window` or
|
|
9
51
|
* `document`.
|
|
10
52
|
*
|
|
53
|
+
* `type` is constrained to the event names the target declares, and
|
|
54
|
+
* `toMessage`'s parameter is the event those two resolve to. Annotating that
|
|
55
|
+
* parameter is checked against the resolved event rather than replacing it.
|
|
56
|
+
*
|
|
11
57
|
* `toMessage(event)` transforms each dispatched event into a Message. The
|
|
12
58
|
* mapper runs synchronously in the same call stack as the browser's event
|
|
13
59
|
* dispatch, so calling `event.preventDefault()` inside it takes effect,
|
|
@@ -17,10 +63,10 @@ import { Option, Stream } from 'effect';
|
|
|
17
63
|
* those events, or reach for `fromEventFilterMapPreventDefault`, which does
|
|
18
64
|
* so for you.
|
|
19
65
|
*/
|
|
20
|
-
export type FromEventConfig<
|
|
21
|
-
target:
|
|
22
|
-
type:
|
|
23
|
-
toMessage: (event:
|
|
66
|
+
export type FromEventConfig<Target extends EventTarget, Type extends EventTypeOf<Target>, Message> = Readonly<{
|
|
67
|
+
target: Target | (() => Target);
|
|
68
|
+
type: Type;
|
|
69
|
+
toMessage: (event: EventOf<Target, Type>) => Message;
|
|
24
70
|
options?: AddEventListenerOptions;
|
|
25
71
|
}>;
|
|
26
72
|
/**
|
|
@@ -32,6 +78,10 @@ export type FromEventConfig<EventType extends Event, Message> = Readonly<{
|
|
|
32
78
|
* pass the `EventTarget` directly for always-present globals like `window` or
|
|
33
79
|
* `document`.
|
|
34
80
|
*
|
|
81
|
+
* `type` is constrained to the event names the target declares, and
|
|
82
|
+
* `toMessage`'s parameter is the event those two resolve to. Annotating that
|
|
83
|
+
* parameter is checked against the resolved event rather than replacing it.
|
|
84
|
+
*
|
|
35
85
|
* `toMessage(event)` returns `Option.some(message)` to emit a Message for the
|
|
36
86
|
* event, or `Option.none()` to ignore it. The mapper runs synchronously in the
|
|
37
87
|
* same call stack as the browser's event dispatch, so calling
|
|
@@ -41,10 +91,10 @@ export type FromEventConfig<EventType extends Event, Message> = Readonly<{
|
|
|
41
91
|
* `options: { passive: false }` explicitly when cancelling those events, or
|
|
42
92
|
* reach for `fromEventFilterMapPreventDefault`, which does so for you.
|
|
43
93
|
*/
|
|
44
|
-
export type FromEventFilterMapConfig<
|
|
45
|
-
target:
|
|
46
|
-
type:
|
|
47
|
-
toMessage: (event:
|
|
94
|
+
export type FromEventFilterMapConfig<Target extends EventTarget, Type extends EventTypeOf<Target>, Message> = Readonly<{
|
|
95
|
+
target: Target | (() => Target);
|
|
96
|
+
type: Type;
|
|
97
|
+
toMessage: (event: EventOf<Target, Type>) => Option.Option<Message>;
|
|
48
98
|
options?: AddEventListenerOptions;
|
|
49
99
|
}>;
|
|
50
100
|
/**
|
|
@@ -56,6 +106,10 @@ export type FromEventFilterMapConfig<EventType extends Event, Message> = Readonl
|
|
|
56
106
|
* pass the `EventTarget` directly for always-present globals like `window` or
|
|
57
107
|
* `document`.
|
|
58
108
|
*
|
|
109
|
+
* `type` is constrained to the event names the target declares, and
|
|
110
|
+
* `toMessage`'s parameter is the event those two resolve to. Annotating that
|
|
111
|
+
* parameter is checked against the resolved event rather than replacing it.
|
|
112
|
+
*
|
|
59
113
|
* `toMessage(event)` returns `Option.some(message)` to mark the dispatch
|
|
60
114
|
* handled, or `Option.none()` to leave the default behavior intact. For a
|
|
61
115
|
* handled dispatch the helper calls `event.preventDefault()` and queues the
|
|
@@ -63,14 +117,15 @@ export type FromEventFilterMapConfig<EventType extends Event, Message> = Readonl
|
|
|
63
117
|
* `preventDefault()`.
|
|
64
118
|
*
|
|
65
119
|
* `options.passive` defaults to `false` so `preventDefault()` keeps working
|
|
66
|
-
* for the events browsers would otherwise register as passive.
|
|
67
|
-
* `passive: true
|
|
120
|
+
* for the events browsers would otherwise register as passive. The config
|
|
121
|
+
* rejects `passive: true`; the runtime guard also throws for unchecked
|
|
122
|
+
* JavaScript inputs.
|
|
68
123
|
*/
|
|
69
|
-
export type FromEventFilterMapPreventDefaultConfig<
|
|
70
|
-
target:
|
|
71
|
-
type:
|
|
72
|
-
toMessage: (event:
|
|
73
|
-
options?:
|
|
124
|
+
export type FromEventFilterMapPreventDefaultConfig<Target extends EventTarget, Type extends EventTypeOf<Target>, Message> = Readonly<{
|
|
125
|
+
target: Target | (() => Target);
|
|
126
|
+
type: Type;
|
|
127
|
+
toMessage: (event: EventOf<Target, Type>) => Option.Option<Message>;
|
|
128
|
+
options?: PreventDefaultEventListenerOptions;
|
|
74
129
|
}>;
|
|
75
130
|
/**
|
|
76
131
|
* Build a Stream that emits a Message for the dispatches of a DOM event the
|
|
@@ -79,7 +134,9 @@ export type FromEventFilterMapPreventDefaultConfig<EventType extends Event, Mess
|
|
|
79
134
|
*
|
|
80
135
|
* This is the filtered variant of `fromEvent`. Its `toMessage` returns
|
|
81
136
|
* `Option.some(message)` to emit and `Option.none()` to ignore the event, so a
|
|
82
|
-
* single listener can react to some dispatches while passing on the rest.
|
|
137
|
+
* single listener can react to some dispatches while passing on the rest. A
|
|
138
|
+
* mapper that never emits produces a `Stream<never>`, which composes wherever
|
|
139
|
+
* a Message-producing Stream is expected.
|
|
83
140
|
*
|
|
84
141
|
* Reach for this over a downstream `Stream.filterMap` whenever the decision to
|
|
85
142
|
* keep an event is paired with `event.preventDefault()`. The mapper runs
|
|
@@ -91,6 +148,13 @@ export type FromEventFilterMapPreventDefaultConfig<EventType extends Event, Mess
|
|
|
91
148
|
* `options: { passive: false }` explicitly when cancelling those events, or
|
|
92
149
|
* reach for `fromEventFilterMapPreventDefault`, which does so for you.
|
|
93
150
|
*
|
|
151
|
+
* The target, the event name, and the event the mapper receives are one fact:
|
|
152
|
+
* `type` is constrained to the names the target declares, and the mapper's
|
|
153
|
+
* parameter is what those two resolve to, so annotating it narrows nothing and
|
|
154
|
+
* cannot contradict the name. A target that is neither annotated nor one
|
|
155
|
+
* lib.dom declares a map for accepts any name and reports `Event`; annotate it
|
|
156
|
+
* with {@link TypedEventTarget} to resolve its own events.
|
|
157
|
+
*
|
|
94
158
|
* The listener lifecycle uses `Effect.acquireRelease`. The `addEventListener`
|
|
95
159
|
* call happens inside the acquire Effect, and the matching
|
|
96
160
|
* `removeEventListener` is registered only after acquire completes, so the
|
|
@@ -111,8 +175,8 @@ export type FromEventFilterMapPreventDefaultConfig<EventType extends Event, Mess
|
|
|
111
175
|
* modelToDependencies: model => ({ isListening: model.isListening }),
|
|
112
176
|
* dependenciesToStream: ({ isListening }) =>
|
|
113
177
|
* Stream.when(
|
|
114
|
-
* Subscription.fromEventFilterMap
|
|
115
|
-
* target:
|
|
178
|
+
* Subscription.fromEventFilterMap({
|
|
179
|
+
* target: window,
|
|
116
180
|
* type: 'keydown',
|
|
117
181
|
* toMessage: event =>
|
|
118
182
|
* event.key === 'Escape'
|
|
@@ -126,12 +190,19 @@ export type FromEventFilterMapPreventDefaultConfig<EventType extends Event, Mess
|
|
|
126
190
|
* }))
|
|
127
191
|
* ```
|
|
128
192
|
*/
|
|
129
|
-
export declare const fromEventFilterMap: <
|
|
193
|
+
export declare const fromEventFilterMap: <Target extends EventTarget, Type extends EventTypeOf<Target>, Message>(config: FromEventFilterMapConfig<Target, Type, Message>) => Stream.Stream<Message>;
|
|
130
194
|
/**
|
|
131
195
|
* Build a Stream that emits a Message for every dispatch of a DOM event,
|
|
132
196
|
* registering the listener when the Stream's scope opens and removing it when
|
|
133
197
|
* the scope closes.
|
|
134
198
|
*
|
|
199
|
+
* The target, the event name, and the event the mapper receives are one fact:
|
|
200
|
+
* `type` is constrained to the names the target declares, and the mapper's
|
|
201
|
+
* parameter is what those two resolve to, so annotating it narrows nothing and
|
|
202
|
+
* cannot contradict the name. A target that is neither annotated nor one
|
|
203
|
+
* lib.dom declares a map for accepts any name and reports `Event`; annotate it
|
|
204
|
+
* with {@link TypedEventTarget} to resolve its own events.
|
|
205
|
+
*
|
|
135
206
|
* The listener lifecycle uses `Effect.acquireRelease`. The `addEventListener`
|
|
136
207
|
* call happens inside the acquire Effect, and the matching
|
|
137
208
|
* `removeEventListener` is registered only after acquire completes, so the
|
|
@@ -157,7 +228,7 @@ export declare const fromEventFilterMap: <EventType extends Event, Message>(conf
|
|
|
157
228
|
* modelToDependencies: model => ({ isListening: model.isListening }),
|
|
158
229
|
* dependenciesToStream: ({ isListening }) =>
|
|
159
230
|
* Stream.when(
|
|
160
|
-
* Subscription.fromEvent
|
|
231
|
+
* Subscription.fromEvent({
|
|
161
232
|
* target: window,
|
|
162
233
|
* type: 'keydown',
|
|
163
234
|
* toMessage: event => Message.PressedKey({ key: event.key }),
|
|
@@ -169,7 +240,7 @@ export declare const fromEventFilterMap: <EventType extends Event, Message>(conf
|
|
|
169
240
|
* }))
|
|
170
241
|
* ```
|
|
171
242
|
*/
|
|
172
|
-
export declare const fromEvent: <
|
|
243
|
+
export declare const fromEvent: <Target extends EventTarget, Type extends EventTypeOf<Target>, Message>(config: FromEventConfig<Target, Type, Message>) => Stream.Stream<Message>;
|
|
173
244
|
/**
|
|
174
245
|
* Build a Stream that emits a Message for the dispatches of a DOM event the
|
|
175
246
|
* mapper marks handled, calling `event.preventDefault()` on each of them,
|
|
@@ -186,8 +257,14 @@ export declare const fromEvent: <EventType extends Event, Message>(config: FromE
|
|
|
186
257
|
* Because cancelling is the point, the listener registers with
|
|
187
258
|
* `passive: false` when the config does not say otherwise. This keeps wheel
|
|
188
259
|
* and touch events cancelable when a browser would otherwise make listeners
|
|
189
|
-
* on a global target passive.
|
|
190
|
-
*
|
|
260
|
+
* on a global target passive. The config rejects `passive: true`; the runtime
|
|
261
|
+
* guard also throws for unchecked JavaScript inputs.
|
|
262
|
+
*
|
|
263
|
+
* The target, event name, and mapper parameter are one fact: `type` is
|
|
264
|
+
* constrained to the names the target declares, and the mapper receives the
|
|
265
|
+
* event those two resolve to. A target with no declared event map accepts any
|
|
266
|
+
* name and reports `Event`; annotate it with {@link TypedEventTarget} to
|
|
267
|
+
* resolve its own events.
|
|
191
268
|
*
|
|
192
269
|
* The listener lifecycle uses `Effect.acquireRelease`. The `addEventListener`
|
|
193
270
|
* call happens inside the acquire Effect, and the matching
|
|
@@ -209,10 +286,7 @@ export declare const fromEvent: <EventType extends Event, Message>(config: FromE
|
|
|
209
286
|
* modelToDependencies: model => ({ isModalOpen: model.isModalOpen }),
|
|
210
287
|
* dependenciesToStream: ({ isModalOpen }) =>
|
|
211
288
|
* Stream.when(
|
|
212
|
-
* Subscription.fromEventFilterMapPreventDefault
|
|
213
|
-
* WheelEvent,
|
|
214
|
-
* Message
|
|
215
|
-
* >({
|
|
289
|
+
* Subscription.fromEventFilterMapPreventDefault({
|
|
216
290
|
* target: window,
|
|
217
291
|
* type: 'wheel',
|
|
218
292
|
* toMessage: () => Option.some(Message.SuppressedWheelScroll()),
|
|
@@ -224,5 +298,6 @@ export declare const fromEvent: <EventType extends Event, Message>(config: FromE
|
|
|
224
298
|
* }))
|
|
225
299
|
* ```
|
|
226
300
|
*/
|
|
227
|
-
export declare const fromEventFilterMapPreventDefault: <
|
|
301
|
+
export declare const fromEventFilterMapPreventDefault: <Target extends EventTarget, Type extends EventTypeOf<Target>, Message>(config: FromEventFilterMapPreventDefaultConfig<Target, Type, Message>) => Stream.Stream<Message>;
|
|
302
|
+
export {};
|
|
228
303
|
//# sourceMappingURL=fromEvent.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fromEvent.d.ts","sourceRoot":"","sources":["../../src/subscription/fromEvent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,MAAM,EAAS,MAAM,EAAE,MAAM,QAAQ,CAAA;AAEtD
|
|
1
|
+
{"version":3,"file":"fromEvent.d.ts","sourceRoot":"","sources":["../../src/subscription/fromEvent.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import { Effect, Option, Queue, Stream } from 'effect';
|
|
2
2
|
const resolveTarget = (target) => (typeof target === 'function' ? target() : target);
|
|
3
|
+
const listen = (config) => Stream.callback(queue => Effect.acquireRelease(Effect.sync(() => {
|
|
4
|
+
const target = resolveTarget(config.target);
|
|
5
|
+
const handleEvent = (event) => {
|
|
6
|
+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
7
|
+
const maybeMessage = config.toMessage(event);
|
|
8
|
+
if (Option.isSome(maybeMessage)) {
|
|
9
|
+
Queue.offerUnsafe(queue, maybeMessage.value);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
target.addEventListener(config.type, handleEvent, config.options);
|
|
13
|
+
return { target, handleEvent };
|
|
14
|
+
}), ({ target, handleEvent }) => Effect.sync(() => {
|
|
15
|
+
target.removeEventListener(config.type, handleEvent, config.options);
|
|
16
|
+
})).pipe(Effect.flatMap(() => Effect.never)));
|
|
3
17
|
/**
|
|
4
18
|
* Build a Stream that emits a Message for the dispatches of a DOM event the
|
|
5
19
|
* mapper chooses to keep, registering the listener when the Stream's scope
|
|
@@ -7,7 +21,9 @@ const resolveTarget = (target) => (typeof target === 'function' ? target() : tar
|
|
|
7
21
|
*
|
|
8
22
|
* This is the filtered variant of `fromEvent`. Its `toMessage` returns
|
|
9
23
|
* `Option.some(message)` to emit and `Option.none()` to ignore the event, so a
|
|
10
|
-
* single listener can react to some dispatches while passing on the rest.
|
|
24
|
+
* single listener can react to some dispatches while passing on the rest. A
|
|
25
|
+
* mapper that never emits produces a `Stream<never>`, which composes wherever
|
|
26
|
+
* a Message-producing Stream is expected.
|
|
11
27
|
*
|
|
12
28
|
* Reach for this over a downstream `Stream.filterMap` whenever the decision to
|
|
13
29
|
* keep an event is paired with `event.preventDefault()`. The mapper runs
|
|
@@ -19,6 +35,13 @@ const resolveTarget = (target) => (typeof target === 'function' ? target() : tar
|
|
|
19
35
|
* `options: { passive: false }` explicitly when cancelling those events, or
|
|
20
36
|
* reach for `fromEventFilterMapPreventDefault`, which does so for you.
|
|
21
37
|
*
|
|
38
|
+
* The target, the event name, and the event the mapper receives are one fact:
|
|
39
|
+
* `type` is constrained to the names the target declares, and the mapper's
|
|
40
|
+
* parameter is what those two resolve to, so annotating it narrows nothing and
|
|
41
|
+
* cannot contradict the name. A target that is neither annotated nor one
|
|
42
|
+
* lib.dom declares a map for accepts any name and reports `Event`; annotate it
|
|
43
|
+
* with {@link TypedEventTarget} to resolve its own events.
|
|
44
|
+
*
|
|
22
45
|
* The listener lifecycle uses `Effect.acquireRelease`. The `addEventListener`
|
|
23
46
|
* call happens inside the acquire Effect, and the matching
|
|
24
47
|
* `removeEventListener` is registered only after acquire completes, so the
|
|
@@ -39,8 +62,8 @@ const resolveTarget = (target) => (typeof target === 'function' ? target() : tar
|
|
|
39
62
|
* modelToDependencies: model => ({ isListening: model.isListening }),
|
|
40
63
|
* dependenciesToStream: ({ isListening }) =>
|
|
41
64
|
* Stream.when(
|
|
42
|
-
* Subscription.fromEventFilterMap
|
|
43
|
-
* target:
|
|
65
|
+
* Subscription.fromEventFilterMap({
|
|
66
|
+
* target: window,
|
|
44
67
|
* type: 'keydown',
|
|
45
68
|
* toMessage: event =>
|
|
46
69
|
* event.key === 'Escape'
|
|
@@ -54,25 +77,19 @@ const resolveTarget = (target) => (typeof target === 'function' ? target() : tar
|
|
|
54
77
|
* }))
|
|
55
78
|
* ```
|
|
56
79
|
*/
|
|
57
|
-
export const fromEventFilterMap = (config) =>
|
|
58
|
-
const target = resolveTarget(config.target);
|
|
59
|
-
const handleEvent = (event) => {
|
|
60
|
-
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
61
|
-
const maybeMessage = config.toMessage(event);
|
|
62
|
-
if (Option.isSome(maybeMessage)) {
|
|
63
|
-
Queue.offerUnsafe(queue, maybeMessage.value);
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
target.addEventListener(config.type, handleEvent, config.options);
|
|
67
|
-
return { target, handleEvent };
|
|
68
|
-
}), ({ target, handleEvent }) => Effect.sync(() => {
|
|
69
|
-
target.removeEventListener(config.type, handleEvent, config.options);
|
|
70
|
-
})).pipe(Effect.flatMap(() => Effect.never)));
|
|
80
|
+
export const fromEventFilterMap = (config) => listen(config);
|
|
71
81
|
/**
|
|
72
82
|
* Build a Stream that emits a Message for every dispatch of a DOM event,
|
|
73
83
|
* registering the listener when the Stream's scope opens and removing it when
|
|
74
84
|
* the scope closes.
|
|
75
85
|
*
|
|
86
|
+
* The target, the event name, and the event the mapper receives are one fact:
|
|
87
|
+
* `type` is constrained to the names the target declares, and the mapper's
|
|
88
|
+
* parameter is what those two resolve to, so annotating it narrows nothing and
|
|
89
|
+
* cannot contradict the name. A target that is neither annotated nor one
|
|
90
|
+
* lib.dom declares a map for accepts any name and reports `Event`; annotate it
|
|
91
|
+
* with {@link TypedEventTarget} to resolve its own events.
|
|
92
|
+
*
|
|
76
93
|
* The listener lifecycle uses `Effect.acquireRelease`. The `addEventListener`
|
|
77
94
|
* call happens inside the acquire Effect, and the matching
|
|
78
95
|
* `removeEventListener` is registered only after acquire completes, so the
|
|
@@ -98,7 +115,7 @@ export const fromEventFilterMap = (config) => Stream.callback(queue => Effect.ac
|
|
|
98
115
|
* modelToDependencies: model => ({ isListening: model.isListening }),
|
|
99
116
|
* dependenciesToStream: ({ isListening }) =>
|
|
100
117
|
* Stream.when(
|
|
101
|
-
* Subscription.fromEvent
|
|
118
|
+
* Subscription.fromEvent({
|
|
102
119
|
* target: window,
|
|
103
120
|
* type: 'keydown',
|
|
104
121
|
* toMessage: event => Message.PressedKey({ key: event.key }),
|
|
@@ -110,7 +127,7 @@ export const fromEventFilterMap = (config) => Stream.callback(queue => Effect.ac
|
|
|
110
127
|
* }))
|
|
111
128
|
* ```
|
|
112
129
|
*/
|
|
113
|
-
export const fromEvent = (config) =>
|
|
130
|
+
export const fromEvent = (config) => listen({
|
|
114
131
|
...config,
|
|
115
132
|
toMessage: event => Option.some(config.toMessage(event)),
|
|
116
133
|
});
|
|
@@ -130,8 +147,14 @@ export const fromEvent = (config) => fromEventFilterMap({
|
|
|
130
147
|
* Because cancelling is the point, the listener registers with
|
|
131
148
|
* `passive: false` when the config does not say otherwise. This keeps wheel
|
|
132
149
|
* and touch events cancelable when a browser would otherwise make listeners
|
|
133
|
-
* on a global target passive.
|
|
134
|
-
*
|
|
150
|
+
* on a global target passive. The config rejects `passive: true`; the runtime
|
|
151
|
+
* guard also throws for unchecked JavaScript inputs.
|
|
152
|
+
*
|
|
153
|
+
* The target, event name, and mapper parameter are one fact: `type` is
|
|
154
|
+
* constrained to the names the target declares, and the mapper receives the
|
|
155
|
+
* event those two resolve to. A target with no declared event map accepts any
|
|
156
|
+
* name and reports `Event`; annotate it with {@link TypedEventTarget} to
|
|
157
|
+
* resolve its own events.
|
|
135
158
|
*
|
|
136
159
|
* The listener lifecycle uses `Effect.acquireRelease`. The `addEventListener`
|
|
137
160
|
* call happens inside the acquire Effect, and the matching
|
|
@@ -153,10 +176,7 @@ export const fromEvent = (config) => fromEventFilterMap({
|
|
|
153
176
|
* modelToDependencies: model => ({ isModalOpen: model.isModalOpen }),
|
|
154
177
|
* dependenciesToStream: ({ isModalOpen }) =>
|
|
155
178
|
* Stream.when(
|
|
156
|
-
* Subscription.fromEventFilterMapPreventDefault
|
|
157
|
-
* WheelEvent,
|
|
158
|
-
* Message
|
|
159
|
-
* >({
|
|
179
|
+
* Subscription.fromEventFilterMapPreventDefault({
|
|
160
180
|
* target: window,
|
|
161
181
|
* type: 'wheel',
|
|
162
182
|
* toMessage: () => Option.some(Message.SuppressedWheelScroll()),
|
|
@@ -169,7 +189,8 @@ export const fromEvent = (config) => fromEventFilterMap({
|
|
|
169
189
|
* ```
|
|
170
190
|
*/
|
|
171
191
|
export const fromEventFilterMapPreventDefault = (config) => {
|
|
172
|
-
|
|
192
|
+
const options = config.options;
|
|
193
|
+
if (options?.passive === true) {
|
|
173
194
|
throw new Error(`Foldkit: \`Subscription.fromEventFilterMapPreventDefault\` was passed ` +
|
|
174
195
|
`\`options: { passive: true }\` for a "${config.type}" listener. ` +
|
|
175
196
|
`The helper exists to call \`event.preventDefault()\` on every ` +
|
|
@@ -182,7 +203,7 @@ export const fromEventFilterMapPreventDefault = (config) => {
|
|
|
182
203
|
}
|
|
183
204
|
return fromEventFilterMap({
|
|
184
205
|
...config,
|
|
185
|
-
options: { ...
|
|
206
|
+
options: { ...options, passive: false },
|
|
186
207
|
toMessage: event => {
|
|
187
208
|
const maybeMessage = config.toMessage(event);
|
|
188
209
|
if (Option.isSome(maybeMessage)) {
|