foldkit 0.82.7 → 0.82.9

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.
@@ -1,8 +1,9 @@
1
1
  import { Schema as S } from 'effect';
2
- /** Browser → plugin: the runtime preserves its current Model under a stable id so it can be restored after a full reload. */
2
+ /** Browser → plugin: the runtime preserves its current Model under a stable id so it can be restored after a full reload. `isHmrReload` is set by the runtime when flushing on `vite:beforeFullReload` to mark the entry as eligible for restoration; absent or `false` for ordinary debounced preserves. */
3
3
  export declare const PreserveModelMessage: S.Struct<{
4
4
  readonly id: S.String;
5
5
  readonly model: S.Unknown;
6
+ readonly isHmrReload: S.optional<S.Boolean>;
6
7
  }>;
7
8
  /** Browser → plugin: the runtime preserves its current Model under a stable id. */
8
9
  export type PreserveModelMessage = typeof PreserveModelMessage.Type;
@@ -1 +1 @@
1
- {"version":3,"file":"hmrProtocol.d.ts","sourceRoot":"","sources":["../../src/runtime/hmrProtocol.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC,EAAE,MAAM,QAAQ,CAAA;AAEpC,6HAA6H;AAC7H,eAAO,MAAM,oBAAoB;;;EAG/B,CAAA;AACF,mFAAmF;AACnF,MAAM,MAAM,oBAAoB,GAAG,OAAO,oBAAoB,CAAC,IAAI,CAAA;AAEnE,sGAAsG;AACtG,eAAO,MAAM,mBAAmB;;EAE9B,CAAA;AACF,wFAAwF;AACxF,MAAM,MAAM,mBAAmB,GAAG,OAAO,mBAAmB,CAAC,IAAI,CAAA;AAEjE,kKAAkK;AAClK,eAAO,MAAM,mBAAmB;;;EAG9B,CAAA;AACF,uFAAuF;AACvF,MAAM,MAAM,mBAAmB,GAAG,OAAO,mBAAmB,CAAC,IAAI,CAAA"}
1
+ {"version":3,"file":"hmrProtocol.d.ts","sourceRoot":"","sources":["../../src/runtime/hmrProtocol.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC,EAAE,MAAM,QAAQ,CAAA;AAEpC,6SAA6S;AAC7S,eAAO,MAAM,oBAAoB;;;;EAI/B,CAAA;AACF,mFAAmF;AACnF,MAAM,MAAM,oBAAoB,GAAG,OAAO,oBAAoB,CAAC,IAAI,CAAA;AAEnE,sGAAsG;AACtG,eAAO,MAAM,mBAAmB;;EAE9B,CAAA;AACF,wFAAwF;AACxF,MAAM,MAAM,mBAAmB,GAAG,OAAO,mBAAmB,CAAC,IAAI,CAAA;AAEjE,kKAAkK;AAClK,eAAO,MAAM,mBAAmB;;;EAG9B,CAAA;AACF,uFAAuF;AACvF,MAAM,MAAM,mBAAmB,GAAG,OAAO,mBAAmB,CAAC,IAAI,CAAA"}
@@ -1,8 +1,9 @@
1
1
  import { Schema as S } from 'effect';
2
- /** Browser → plugin: the runtime preserves its current Model under a stable id so it can be restored after a full reload. */
2
+ /** Browser → plugin: the runtime preserves its current Model under a stable id so it can be restored after a full reload. `isHmrReload` is set by the runtime when flushing on `vite:beforeFullReload` to mark the entry as eligible for restoration; absent or `false` for ordinary debounced preserves. */
3
3
  export const PreserveModelMessage = S.Struct({
4
4
  id: S.String,
5
5
  model: S.Unknown,
6
+ isHmrReload: S.optional(S.Boolean),
6
7
  });
7
8
  /** Browser → plugin: the runtime requests its previously-preserved Model on startup, scoped by id. */
8
9
  export const RequestModelMessage = S.Struct({
@@ -0,0 +1,29 @@
1
+ import { type Duration, Effect, type Scope } from 'effect';
2
+ /**
3
+ * Defers a `preserve(model)` call so that bursts of dispatches do not pay the
4
+ * encoding cost on the hot path. `schedule` stashes the latest model and
5
+ * (re)starts a debounce timer; only one `onDebounce` runs per quiet window.
6
+ *
7
+ * `flush` runs `onFlush` with the latest pending model synchronously: it
8
+ * clears the pending slot atomically and invokes the callback, leaving any
9
+ * in-flight timer fiber to wake up, observe an empty pending slot, and exit
10
+ * as a no-op. Synchronous semantics let the runtime call this from
11
+ * `vite:beforeFullReload` via `Effect.runSync`, with no race against Vite's
12
+ * `location.reload()`. The two-callback split lets the flush path send
13
+ * `isHmrReload: true` while debounced calls send `false`.
14
+ *
15
+ * `cancel` drops the pending model and interrupts the in-flight fiber.
16
+ * Forked fibers are tied to the construction scope via `forkIn`, so scope
17
+ * closure interrupts them too. `cancel` covers manual teardown.
18
+ */
19
+ export type PreserveScheduler<Model> = Readonly<{
20
+ schedule: (model: Model) => Effect.Effect<void>;
21
+ flush: Effect.Effect<void>;
22
+ cancel: Effect.Effect<void>;
23
+ }>;
24
+ export type PreserveSchedulerCallbacks<Model> = Readonly<{
25
+ onDebounce: (model: Model) => Effect.Effect<void>;
26
+ onFlush: (model: Model) => Effect.Effect<void>;
27
+ }>;
28
+ export declare const makePreserveScheduler: <Model>(callbacks: PreserveSchedulerCallbacks<Model>, delay: Duration.Input) => Effect.Effect<PreserveScheduler<Model>, never, Scope.Scope>;
29
+ //# sourceMappingURL=preserveScheduler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preserveScheduler.d.ts","sourceRoot":"","sources":["../../src/runtime/preserveScheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,EAAsB,KAAK,KAAK,EAAE,MAAM,QAAQ,CAAA;AAE9E;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,iBAAiB,CAAC,KAAK,IAAI,QAAQ,CAAC;IAC9C,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAC/C,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;CAC5B,CAAC,CAAA;AAEF,MAAM,MAAM,0BAA0B,CAAC,KAAK,IAAI,QAAQ,CAAC;IACvD,UAAU,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACjD,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;CAC/C,CAAC,CAAA;AAEF,eAAO,MAAM,qBAAqB,GAAI,KAAK,EACzC,WAAW,0BAA0B,CAAC,KAAK,CAAC,EAC5C,OAAO,QAAQ,CAAC,KAAK,KACpB,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAgDzD,CAAA"}
@@ -0,0 +1,36 @@
1
+ import { Effect, Fiber, Option, Ref } from 'effect';
2
+ export const makePreserveScheduler = (callbacks, delay) => Effect.gen(function* () {
3
+ const scope = yield* Effect.scope;
4
+ const pendingRef = yield* Ref.make(Option.none());
5
+ const fiberRef = yield* Ref.make(Option.none());
6
+ const interruptPending = Effect.gen(function* () {
7
+ const maybeFiber = yield* Ref.get(fiberRef);
8
+ yield* Option.match(maybeFiber, {
9
+ onNone: () => Effect.void,
10
+ onSome: Fiber.interrupt,
11
+ });
12
+ yield* Ref.set(fiberRef, Option.none());
13
+ });
14
+ const runPending = (callback) => Effect.gen(function* () {
15
+ const maybePending = yield* Ref.getAndSet(pendingRef, Option.none());
16
+ yield* Option.match(maybePending, {
17
+ onNone: () => Effect.void,
18
+ onSome: callback,
19
+ });
20
+ });
21
+ const schedule = (model) => Effect.gen(function* () {
22
+ yield* Ref.set(pendingRef, Option.some(model));
23
+ yield* interruptPending;
24
+ const fiber = yield* Effect.forkIn(Effect.gen(function* () {
25
+ yield* Effect.sleep(delay);
26
+ yield* runPending(callbacks.onDebounce);
27
+ }), scope);
28
+ yield* Ref.set(fiberRef, Option.some(fiber));
29
+ });
30
+ const flush = runPending(callbacks.onFlush);
31
+ const cancel = Effect.gen(function* () {
32
+ yield* interruptPending;
33
+ yield* Ref.set(pendingRef, Option.none());
34
+ });
35
+ return { schedule, flush, cancel };
36
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/runtime/runtime.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/runtime/runtime.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { BrowserRuntime } from '@effect/platform-browser';
2
- import { Array, Cause, Context, Effect, Exit, Function, Layer, Match, Option, Predicate, PubSub, Queue, Record, Ref, Scheduler, Schema, Stream, SubscriptionRef, pipe, } from 'effect';
2
+ import { Array, Cause, Context, Duration, Effect, Exit, Function, Layer, Match, Option, Predicate, PubSub, Queue, Record, Ref, Scheduler, Schema, Stream, SubscriptionRef, pipe, } from 'effect';
3
3
  import { h } from 'snabbdom';
4
4
  import { createOverlay } from '../devTools/overlay.js';
5
5
  import { createDevToolsStore } from '../devTools/store.js';
@@ -11,6 +11,7 @@ import { defaultCrashView, noOpDispatch } from './crashUI.js';
11
11
  import { deepFreeze } from './deepFreeze.js';
12
12
  import { PreserveModelMessage, RequestModelMessage, RestoreModelMessage, } from './hmrProtocol.js';
13
13
  import { orderByPriority } from './messagePriority.js';
14
+ import { makePreserveScheduler } from './preserveScheduler.js';
14
15
  import { makeRenderLoop } from './renderLoop.js';
15
16
  const DEFAULT_DEV_TOOLS_SHOW = 'Development';
16
17
  const DEFAULT_DEV_TOOLS_POSITION = 'BottomRight';
@@ -98,12 +99,40 @@ const makeRuntime = ({ Model, flags: resolveFlags, init, update, view, subscript
98
99
  });
99
100
  };
100
101
  const flags = yield* resolveFlags;
101
- const modelEquivalence = Schema.toEquivalence(Model);
102
102
  const ModelJsonCodec = Schema.toCodecJson(
103
103
  /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
104
104
  Model);
105
105
  const decodeHmrModel = Schema.decodeUnknownExit(ModelJsonCodec);
106
106
  const encodeHmrModel = Schema.encodeUnknownSync(ModelJsonCodec);
107
+ // NOTE: keep `encodeHmrModel` off the dispatch hot path. It walks
108
+ // the entire Model graph (O(modelSize) per call) and blocks input
109
+ // on large Models. The scheduler defers encoding to a quiet window
110
+ // and the `vite:beforeFullReload` flush covers the HMR boundary.
111
+ const PRESERVE_DEBOUNCE = Duration.millis(200);
112
+ const preserveScheduler = yield* makePreserveScheduler({
113
+ onDebounce: model => Effect.sync(() => preserveModel(runtimeId, encodeHmrModel(model), false)),
114
+ onFlush: model => Effect.sync(() => preserveModel(runtimeId, encodeHmrModel(model), true)),
115
+ }, PRESERVE_DEBOUNCE);
116
+ const hot = import.meta.hot;
117
+ if (hot) {
118
+ yield* Effect.acquireRelease(Effect.sync(() => {
119
+ // NOTE: Effect.runSync requires `flush` to have no async
120
+ // suspensions. The scheduler is built to satisfy that: flush
121
+ // clears pending atomically and runs `onFlush` without
122
+ // interrupting the in-flight timer fiber, which keeps the
123
+ // whole effect synchronous. If a future change adds an async
124
+ // step (interrupt-await, sleep, fork) on this path, Vite may
125
+ // race ahead to location.reload() before the encoded model
126
+ // reaches the plugin.
127
+ const handler = () => {
128
+ Effect.runSync(preserveScheduler.flush);
129
+ };
130
+ hot.on('vite:beforeFullReload', handler);
131
+ return handler;
132
+ }), handler => Effect.sync(() => hot.off('vite:beforeFullReload', handler)));
133
+ yield* Effect.addFinalizer(() => preserveScheduler.cancel);
134
+ }
135
+ const schedulePreserveModel = (model) => hot ? preserveScheduler.schedule(model) : Effect.void;
107
136
  // NOTE: Each enqueued Message carries a priority. Within a single
108
137
  // takeAll batch the drain loop processes all High before any Normal,
109
138
  // so user input (view dispatch, navigation, subscription events,
@@ -160,10 +189,8 @@ const makeRuntime = ({ Model, flags: resolveFlags, init, update, view, subscript
160
189
  yield* Ref.set(modelRef, nextModel);
161
190
  yield* SubscriptionRef.set(isRenderPendingRef, true);
162
191
  yield* Ref.set(lastDirtyMessageRef, Option.some(message));
163
- if (!modelEquivalence(currentModel, nextModel)) {
164
- PubSub.publishUnsafe(modelPubSub, nextModel);
165
- preserveModel(runtimeId, encodeHmrModel(nextModel));
166
- }
192
+ PubSub.publishUnsafe(modelPubSub, nextModel);
193
+ yield* schedulePreserveModel(nextModel);
167
194
  }
168
195
  yield* Effect.forEach(
169
196
  /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
@@ -260,22 +287,26 @@ const makeRuntime = ({ Model, flags: resolveFlags, init, update, view, subscript
260
287
  yield* Effect.forkDetach(renderLoop);
261
288
  addBfcacheRestoreListener();
262
289
  if (subscriptions) {
263
- yield* pipe(subscriptions, Record.toEntries, Effect.forEach(([_key, { schema, modelToDependencies, equivalence: customEquivalence, dependenciesToStream, },]) => {
264
- let latestDependencies = modelToDependencies(initModel);
290
+ yield* pipe(subscriptions, Record.toEntries, Effect.forEach(([_key, { schema, modelToDependencies, equivalence: customEquivalence, dependenciesToStream, },]) => Effect.gen(function* () {
291
+ const latestDependenciesRef = yield* Ref.make(modelToDependencies(initModel));
265
292
  const equivalence = customEquivalence ?? Schema.toEquivalence(schema);
266
293
  const modelStream = Stream.concat(Stream.make(initModel), Stream.fromPubSub(modelPubSub));
267
- return Effect.forkDetach(modelStream.pipe(
268
- // NOTE: updates latestDependencies on every model change so
269
- // readDependencies() returns current values even when the
270
- // stream hasn't restarted (when equivalence filters the change).
271
- Stream.map(model => {
294
+ yield* Effect.forkDetach(modelStream.pipe(
295
+ // NOTE: Ref.set runs upstream of Stream.changesWith on
296
+ // every model change, so readDependencies() returns
297
+ // current values even when the equivalence filter
298
+ // doesn't emit. Moving this into a tap after
299
+ // changesWith would silently break subscribers whose
300
+ // dependencies are equivalence-stable across model
301
+ // changes.
302
+ Stream.mapEffect(model => Effect.gen(function* () {
272
303
  const dependencies = modelToDependencies(model);
273
- latestDependencies = dependencies;
304
+ yield* Ref.set(latestDependenciesRef, dependencies);
274
305
  return dependencies;
275
- }), Stream.changesWith(equivalence), Stream.switchMap(dependencies => dependenciesToStream(dependencies, () => latestDependencies)), Stream.runForEach(message =>
306
+ })), Stream.changesWith(equivalence), Stream.switchMap(dependencies => dependenciesToStream(dependencies, () => Ref.getUnsafe(latestDependenciesRef))), Stream.runForEach(message =>
276
307
  /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
277
308
  enqueueHigh(message)), provideAllResources));
278
- }, {
309
+ }), {
279
310
  concurrency: 'unbounded',
280
311
  discard: true,
281
312
  }));
@@ -346,10 +377,24 @@ const makeRuntime = ({ Model, flags: resolveFlags, init, update, view, subscript
346
377
  yield* processBatch(batch);
347
378
  yield* drainQueue;
348
379
  });
380
+ // NOTE: only reset the burst timer when `Queue.take` actually blocked
381
+ // (queue was empty). With Command-chained dispatches each forever
382
+ // iteration handles a single message, so resetting unconditionally
383
+ // would keep the per-iteration cost under FRAME_BUDGET_MS forever
384
+ // and the runtime would never yield to the browser. Polling first
385
+ // distinguishes "continuing a burst" (poll returns Some) from
386
+ // "waking from idle" (poll returns None, take blocks).
349
387
  yield* pipe(Effect.forever(Effect.gen(function* () {
350
- const first = yield* Queue.take(messageQueue);
388
+ const maybeFirst = yield* Queue.poll(messageQueue);
389
+ const first = yield* Option.match(maybeFirst, {
390
+ onNone: () => Effect.gen(function* () {
391
+ const message = yield* Queue.take(messageQueue);
392
+ yield* Ref.set(burstStartedAtRef, performance.now());
393
+ return message;
394
+ }),
395
+ onSome: Effect.succeed,
396
+ });
351
397
  const rest = yield* pollAvailable;
352
- yield* Ref.set(burstStartedAtRef, performance.now());
353
398
  yield* processBatch(Array.prepend(rest, first));
354
399
  yield* drainQueue;
355
400
  })), Effect.catchCause(cause => Effect.sync(() => {
@@ -497,9 +542,9 @@ export function makeProgram(config) {
497
542
  const encodePreserveModelMessage = Schema.encodeUnknownSync(PreserveModelMessage);
498
543
  const encodeRequestModelMessage = Schema.encodeUnknownSync(RequestModelMessage);
499
544
  const decodeRestoreModelMessage = Schema.decodeUnknownExit(RestoreModelMessage);
500
- const preserveModel = (id, model) => {
545
+ const preserveModel = (id, encodedModel, isHmrReload) => {
501
546
  if (import.meta.hot) {
502
- import.meta.hot.send('foldkit:preserve-model', encodePreserveModelMessage(PreserveModelMessage.make({ id, model })));
547
+ import.meta.hot.send('foldkit:preserve-model', encodePreserveModelMessage(PreserveModelMessage.make({ id, model: encodedModel, isHmrReload })));
503
548
  }
504
549
  };
505
550
  const PLUGIN_RESPONSE_TIMEOUT_MS = 500;
@@ -1,7 +1,17 @@
1
1
  import { Effect } from 'effect';
2
2
  import { ElementNotFound } from './error.js';
3
3
  /**
4
- * Focuses an element matching the given selector.
4
+ * Focuses an element matching the given selector after the next render has
5
+ * committed.
6
+ *
7
+ * For focus that should happen because an element just appeared (an input
8
+ * mounting, a dialog opening), prefer `OnMount` with a `Mount.define`'d
9
+ * action — focus is then a consequence of the element's lifecycle and runs
10
+ * synchronously inside the snabbdom insert hook with no race window.
11
+ * Reserve `Task.focus` for the post-Message case (returning focus to a
12
+ * trigger button after a popover closes, refocusing on blur, keyboard
13
+ * navigation across a stable layout).
14
+ *
5
15
  * Fails with `ElementNotFound` if the selector does not match an `HTMLElement`.
6
16
  *
7
17
  * @example
@@ -1 +1 @@
1
- {"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../../src/task/dom.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,MAAM,EAMP,MAAM,QAAQ,CAAA;AAEf,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAmB5C;;;;;;;;GAQG;AACH,eAAO,MAAM,KAAK,GAAI,UAAU,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAQxE,CAAA;AAEJ;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,SAAS,GACpB,UAAU,MAAM,EAChB,UAAU,QAAQ,CAAC;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,KAC7C,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAgDlC,CAAA;AAuBJ;;;;;;;;;GASG;AACH,eAAO,MAAM,UAAU,GACrB,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAclC,CAAA;AAEJ;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,GACvB,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAQlC,CAAA;AAEJ;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,GACzB,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAQlC,CAAA;AAEJ,0EAA0E;AAC1E,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,UAAU,CAAA;AAEhD;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,GACvB,UAAU,MAAM,EAChB,WAAW,cAAc,KACxB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAqClC,CAAA"}
1
+ {"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../../src/task/dom.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,MAAM,EAMP,MAAM,QAAQ,CAAA;AAEf,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAsC5C;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,KAAK,GAAI,UAAU,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAKxE,CAAA;AAEJ;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,SAAS,GACpB,UAAU,MAAM,EAChB,UAAU,QAAQ,CAAC;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,KAC7C,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAgDlC,CAAA;AAuBJ;;;;;;;;;GASG;AACH,eAAO,MAAM,UAAU,GACrB,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAclC,CAAA;AAEJ;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,GACvB,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAKlC,CAAA;AAEJ;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,GACzB,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAKlC,CAAA;AAEJ,0EAA0E;AAC1E,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,UAAU,CAAA;AAEhD;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,GACvB,UAAU,MAAM,EAChB,WAAW,cAAc,KACxB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAiClC,CAAA"}
package/dist/task/dom.js CHANGED
@@ -11,8 +11,32 @@ const FOCUSABLE_SELECTOR = Array.join([
11
11
  'textarea:not([disabled]):not([tabindex="-1"])',
12
12
  '[tabindex]:not([tabindex="-1"])',
13
13
  ], ', ');
14
+ // NOTE: DOM tasks await one rAF before walking the tree. The runtime defers
15
+ // renders to the next animation frame and forks Commands on the microtask
16
+ // queue, so without this wait a Task that runs immediately after a dirtying
17
+ // Message would query the DOM before the matching VDOM patch has committed.
18
+ const awaitNextFrame = Effect.callback(resume => {
19
+ const handle = requestAnimationFrame(() => resume(Effect.void));
20
+ return Effect.sync(() => cancelAnimationFrame(handle));
21
+ });
22
+ const queryHTMLElement = (selector) => Effect.suspend(() => {
23
+ const element = document.querySelector(selector);
24
+ return element instanceof HTMLElement
25
+ ? Effect.succeed(element)
26
+ : Effect.fail(new ElementNotFound({ selector }));
27
+ });
14
28
  /**
15
- * Focuses an element matching the given selector.
29
+ * Focuses an element matching the given selector after the next render has
30
+ * committed.
31
+ *
32
+ * For focus that should happen because an element just appeared (an input
33
+ * mounting, a dialog opening), prefer `OnMount` with a `Mount.define`'d
34
+ * action — focus is then a consequence of the element's lifecycle and runs
35
+ * synchronously inside the snabbdom insert hook with no race window.
36
+ * Reserve `Task.focus` for the post-Message case (returning focus to a
37
+ * trigger button after a popover closes, refocusing on blur, keyboard
38
+ * navigation across a stable layout).
39
+ *
16
40
  * Fails with `ElementNotFound` if the selector does not match an `HTMLElement`.
17
41
  *
18
42
  * @example
@@ -20,13 +44,10 @@ const FOCUSABLE_SELECTOR = Array.join([
20
44
  * Task.focus('#email-input').pipe(Effect.ignore, Effect.as(CompletedFocusInput()))
21
45
  * ```
22
46
  */
23
- export const focus = (selector) => Effect.suspend(() => {
24
- const element = document.querySelector(selector);
25
- if (element instanceof HTMLElement) {
26
- element.focus();
27
- return Effect.void;
28
- }
29
- return Effect.fail(new ElementNotFound({ selector }));
47
+ export const focus = (selector) => Effect.gen(function* () {
48
+ yield* awaitNextFrame;
49
+ const element = yield* queryHTMLElement(selector);
50
+ element.focus();
30
51
  });
31
52
  /**
32
53
  * Opens a dialog element using `show()` with high z-index, focus trapping,
@@ -43,10 +64,11 @@ export const focus = (selector) => Effect.suspend(() => {
43
64
  * Task.showModal('#my-dialog', { focusSelector: '#search-input' }).pipe(Effect.ignore, Effect.as(CompletedShowDialog()))
44
65
  * ```
45
66
  */
46
- export const showModal = (selector, options) => Effect.suspend(() => {
67
+ export const showModal = (selector, options) => Effect.gen(function* () {
68
+ yield* awaitNextFrame;
47
69
  const element = document.querySelector(selector);
48
70
  if (!(element instanceof HTMLDialogElement)) {
49
- return Effect.fail(new ElementNotFound({ selector }));
71
+ return yield* Effect.fail(new ElementNotFound({ selector }));
50
72
  }
51
73
  element.style.position = 'fixed';
52
74
  element.style.inset = '0';
@@ -75,7 +97,6 @@ export const showModal = (selector, options) => Effect.suspend(() => {
75
97
  focusTarget.focus();
76
98
  }
77
99
  }
78
- return Effect.void;
79
100
  });
80
101
  const trapFocusWithinDialog = (event, dialog) => {
81
102
  const focusable = Array.fromIterable(dialog.querySelectorAll(FOCUSABLE_SELECTOR));
@@ -125,13 +146,10 @@ export const closeModal = (selector) => Effect.suspend(() => {
125
146
  * Task.clickElement('#menu-item-2').pipe(Effect.ignore, Effect.as(CompletedClickItem()))
126
147
  * ```
127
148
  */
128
- export const clickElement = (selector) => Effect.suspend(() => {
129
- const element = document.querySelector(selector);
130
- if (element instanceof HTMLElement) {
131
- element.click();
132
- return Effect.void;
133
- }
134
- return Effect.fail(new ElementNotFound({ selector }));
149
+ export const clickElement = (selector) => Effect.gen(function* () {
150
+ yield* awaitNextFrame;
151
+ const element = yield* queryHTMLElement(selector);
152
+ element.click();
135
153
  });
136
154
  /**
137
155
  * Scrolls an element into view by selector using `{ block: 'nearest' }`.
@@ -142,13 +160,10 @@ export const clickElement = (selector) => Effect.suspend(() => {
142
160
  * Task.scrollIntoView('#active-item').pipe(Effect.ignore, Effect.as(CompletedScrollIntoView()))
143
161
  * ```
144
162
  */
145
- export const scrollIntoView = (selector) => Effect.suspend(() => {
146
- const element = document.querySelector(selector);
147
- if (element instanceof HTMLElement) {
148
- element.scrollIntoView({ block: 'nearest' });
149
- return Effect.void;
150
- }
151
- return Effect.fail(new ElementNotFound({ selector }));
163
+ export const scrollIntoView = (selector) => Effect.gen(function* () {
164
+ yield* awaitNextFrame;
165
+ const element = yield* queryHTMLElement(selector);
166
+ element.scrollIntoView({ block: 'nearest' });
152
167
  });
153
168
  /**
154
169
  * Focuses the next or previous focusable element in the document relative to the element matching the given selector.
@@ -159,20 +174,17 @@ export const scrollIntoView = (selector) => Effect.suspend(() => {
159
174
  * Task.advanceFocus('#menu-button', 'Next').pipe(Effect.ignore, Effect.as(CompletedAdvanceFocus()))
160
175
  * ```
161
176
  */
162
- export const advanceFocus = (selector, direction) => Effect.suspend(() => {
163
- const reference = document.querySelector(selector);
164
- if (!(reference instanceof HTMLElement)) {
165
- return Effect.fail(new ElementNotFound({ selector }));
166
- }
177
+ export const advanceFocus = (selector, direction) => Effect.gen(function* () {
178
+ yield* awaitNextFrame;
179
+ const reference = yield* queryHTMLElement(selector);
167
180
  const focusableElements = Array.fromIterable(document.querySelectorAll(FOCUSABLE_SELECTOR));
168
181
  const referenceElementIndex = Array.findFirstIndex(focusableElements, Equal.equals(reference));
169
182
  if (Option.isNone(referenceElementIndex)) {
170
- return Effect.fail(new ElementNotFound({ selector }));
183
+ return yield* Effect.fail(new ElementNotFound({ selector }));
171
184
  }
172
185
  const offsetReferenceElementIndex = M.value(direction).pipe(M.when('Next', () => Number.increment), M.when('Previous', () => Number.decrement), M.exhaustive)(referenceElementIndex.value);
173
186
  const nextElement = Array.get(focusableElements, offsetReferenceElementIndex);
174
187
  if (Option.isSome(nextElement)) {
175
188
  nextElement.value.focus();
176
189
  }
177
- return Effect.void;
178
190
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foldkit",
3
- "version": "0.82.7",
3
+ "version": "0.82.9",
4
4
  "description": "A TypeScript frontend framework, built on Effect and architected like Elm",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",