tutuca 0.9.93 → 0.9.95

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,5 +1,5 @@
1
1
  // src/storybook.js
2
- import { component, html, injectCss, tutuca } from "tutuca";
2
+ import { component, dispatchPhase, html, injectCss, tutuca } from "tutuca";
3
3
  var Storybook = component({
4
4
  name: "Storybook",
5
5
  fields: {
@@ -80,7 +80,10 @@ var Storybook = component({
80
80
  this,
81
81
  true
82
82
  ]);
83
- return this.selectSectionAtIndex(this.sections.indexOf(section));
83
+ const oldIndex = this.selectedSectionIndex;
84
+ const next = this.selectSectionAtIndex(this.sections.indexOf(section));
85
+ transitionSections(ctx, next, oldIndex, next.selectedSectionIndex);
86
+ return next;
84
87
  },
85
88
  exampleFocusRequested(example, ctx) {
86
89
  ctx.stopPropagation();
@@ -102,11 +105,13 @@ var Storybook = component({
102
105
  }
103
106
  },
104
107
  response: {
105
- loadState(state, err) {
108
+ loadState(state, err, ctx) {
106
109
  if (err || !state)
107
110
  return this;
108
- const next = this.selectSectionWithId(state.section).setFilter(state.sectionFilter ?? "").setSelectedSectionFilter(state.exampleFilter ?? "");
109
- return state.example ? next.focusExampleByIds(state.section, state.example) : next.setSectionId(null).setExampleId(null).setFocusExample(null);
111
+ const selected = this.selectSectionWithId(state.section).setFilter(state.sectionFilter ?? "").setSelectedSectionFilter(state.exampleFilter ?? "");
112
+ const next = state.example ? selected.focusExampleByIds(state.section, state.example) : selected.setSectionId(null).setExampleId(null).setFocusExample(null);
113
+ transitionSections(ctx, next, this.selectedSectionIndex, next.selectedSectionIndex);
114
+ return next;
110
115
  }
111
116
  },
112
117
  view: html`<div>
@@ -150,7 +155,8 @@ var Section = component({
150
155
  description: "",
151
156
  items: [],
152
157
  filter: "",
153
- selected: false
158
+ selected: false,
159
+ initialized: false
154
160
  },
155
161
  statics: {
156
162
  fromData(raw) {
@@ -185,6 +191,20 @@ var Section = component({
185
191
  return this;
186
192
  }
187
193
  },
194
+ receive: {
195
+ init(ctx) {
196
+ fanoutLifecycle(ctx, this.items, "init");
197
+ return this.setInitialized(true);
198
+ },
199
+ resume(ctx) {
200
+ fanoutLifecycle(ctx, this.items, "resume");
201
+ return this;
202
+ },
203
+ suspend(ctx) {
204
+ fanoutLifecycle(ctx, this.items, "suspend");
205
+ return this;
206
+ }
207
+ },
188
208
  view: html`<section class="flex flex-col gap-3">
189
209
  <div class="sticky top-0 z-10 bg-base-100 pt-1 pb-2 shadow-sm">
190
210
  <h2 class="text-lg font-bold" @text=".title"></h2>
@@ -226,7 +246,8 @@ var Example = component({
226
246
  description: "",
227
247
  value: null,
228
248
  view: "main",
229
- requestHandlers: null
249
+ requestHandlers: null,
250
+ on: null
230
251
  },
231
252
  requestOverridesField: "requestHandlers",
232
253
  statics: {
@@ -236,7 +257,8 @@ var Example = component({
236
257
  description = "",
237
258
  value = null,
238
259
  view = "main",
239
- requestHandlers = null
260
+ requestHandlers = null,
261
+ on = null
240
262
  }) {
241
263
  id ??= slugify(title);
242
264
  return this.make({
@@ -245,10 +267,25 @@ var Example = component({
245
267
  description,
246
268
  value,
247
269
  view,
248
- requestHandlers
270
+ requestHandlers,
271
+ on
249
272
  });
250
273
  }
251
274
  },
275
+ receive: {
276
+ init(ctx) {
277
+ dispatchPhase(ctx, ctx.at.field("value").buildPath(), this.on?.init, this.value);
278
+ return this;
279
+ },
280
+ resume(ctx) {
281
+ dispatchPhase(ctx, ctx.at.field("value").buildPath(), this.on?.resume, this.value);
282
+ return this;
283
+ },
284
+ suspend(ctx) {
285
+ dispatchPhase(ctx, ctx.at.field("value").buildPath(), this.on?.suspend, this.value);
286
+ return this;
287
+ }
288
+ },
252
289
  input: {
253
290
  onLogSelected() {
254
291
  console.log(this.value);
@@ -279,6 +316,23 @@ var Example = component({
279
316
  </div>
280
317
  </div>`
281
318
  });
319
+ function transitionSections(ctx, sb, oldIndex, newIndex) {
320
+ const changed = oldIndex !== newIndex;
321
+ if (changed && sb.sections.get(oldIndex)?.initialized)
322
+ ctx.at.index("sections", oldIndex).send("suspend", []);
323
+ const target = sb.sections.get(newIndex);
324
+ if (!target)
325
+ return;
326
+ if (!target.initialized)
327
+ ctx.at.index("sections", newIndex).send("init", []);
328
+ else if (changed)
329
+ ctx.at.index("sections", newIndex).send("resume", []);
330
+ }
331
+ function fanoutLifecycle(ctx, items, name) {
332
+ items.forEach((_item, j) => {
333
+ ctx.at.index("items", j).send(name, []);
334
+ });
335
+ }
282
336
  function fuzzyMatch(query, target) {
283
337
  const q = query.toLowerCase(), t = target.toLowerCase();
284
338
  let qi = 0;
@@ -359,15 +413,16 @@ async function mountStorybook(selector, modules, { compileCss, root, persistUrl
359
413
  const scope = app.registerComponents(built.components);
360
414
  scope.registerMacros(built.macros);
361
415
  scope.registerRequestHandlers(buildExampleRequestHandlers(built));
416
+ scope.registerRequestHandlers({ loadState: persistUrl ? loadState : loadStateBlank });
362
417
  if (persistUrl) {
363
- scope.registerRequestHandlers({ persistState, loadState });
418
+ scope.registerRequestHandlers({ persistState });
364
419
  }
365
420
  if (compileCss) {
366
421
  injectCss("tutuca-storybook", await compileCss(app));
367
422
  }
368
423
  app.start();
424
+ app.sendAtRoot("init", []);
369
425
  if (persistUrl) {
370
- app.sendAtRoot("init", []);
371
426
  window.addEventListener("popstate", () => app.sendAtRoot("init", []));
372
427
  }
373
428
  return app;
@@ -392,6 +447,9 @@ async function mountStorybook(selector, modules, { compileCss, root, persistUrl
392
447
  exampleFilter: p.get("exampleFilter") ?? ""
393
448
  };
394
449
  }
450
+ function loadStateBlank() {
451
+ return { section: null, example: null, sectionFilter: "", exampleFilter: "" };
452
+ }
395
453
  }
396
454
  function getComponents() {
397
455
  return [Storybook, Section, Example];
@@ -2920,36 +2920,81 @@ class Transactor {
2920
2920
  this.transactions = [];
2921
2921
  this.state = new State(rootValue);
2922
2922
  this.onTransactionPushed = () => {};
2923
+ this._inflight = new Set;
2923
2924
  }
2924
2925
  pushTransaction(t) {
2925
2926
  this.transactions.push(t);
2926
2927
  this.onTransactionPushed(t);
2927
2928
  }
2929
+ _link(child, parent) {
2930
+ if (parent) {
2931
+ const release = parent.completion.track();
2932
+ child.completion.whenSubtreeSettled().then(release);
2933
+ }
2934
+ return child;
2935
+ }
2928
2936
  pushSend(path, name, args = [], opts = {}, parent = null) {
2929
- this.pushTransaction(new SendEvent(path, this, name, args, parent, opts));
2937
+ const t = new SendEvent(path, this, name, args, parent, opts);
2938
+ this.pushTransaction(t);
2939
+ return this._link(t, parent);
2940
+ }
2941
+ pushInput(path, name, args = [], opts = {}, parent = null) {
2942
+ const t = new InputDispatchEvent(path, this, name, args, parent, opts);
2943
+ this.pushTransaction(t);
2944
+ return this._link(t, parent);
2930
2945
  }
2931
2946
  pushBubble(path, name, args = [], opts = {}, parent = null, targetPath = null) {
2932
2947
  const newOpts = opts.skipSelf ? { ...opts, skipSelf: false } : opts;
2933
- this.pushTransaction(new BubbleEvent(path, this, name, args, parent, newOpts, targetPath));
2948
+ const t = new BubbleEvent(path, this, name, args, parent, newOpts, targetPath);
2949
+ this.pushTransaction(t);
2950
+ return this._link(t, parent);
2951
+ }
2952
+ pushRequest(path, name, args = [], opts = {}, parent = null) {
2953
+ const release = parent ? parent.completion.track() : null;
2954
+ const p = this._runRequest(path, name, args, opts, parent, release);
2955
+ this._inflight.add(p);
2956
+ p.finally(() => this._inflight.delete(p));
2957
+ return p;
2958
+ }
2959
+ async settle(maxTurns = 1e4) {
2960
+ while ((this.hasPendingTransactions || this._inflight.size) && maxTurns-- > 0) {
2961
+ while (this.hasPendingTransactions)
2962
+ this.transactNext();
2963
+ if (this._inflight.size)
2964
+ await Promise.allSettled([...this._inflight]);
2965
+ }
2934
2966
  }
2935
- async pushRequest(path, name, args = [], opts = {}, parent = null) {
2936
- const curRoot = this.state.val;
2937
- const txnPath = path.toTransactionPath();
2938
- const curLeaf = txnPath.lookup(curRoot);
2939
- const handler = this.comps.getRequestFor(curLeaf, name) ?? mkReq404(name);
2940
- const reqCtx = new RequestContext(path, this, parent, curRoot);
2941
- const resHandlerName = opts?.onResName ?? name;
2942
- const resPath = opts?.livePath ? null : txnPath.pinKeys(curRoot);
2943
- const push = (specificName, baseName, singleArg, result, error) => {
2944
- const resArgs = specificName ? [singleArg] : [result, error];
2945
- const t = new ResponseEvent(path, this, specificName ?? baseName, resArgs, parent, resPath);
2946
- this.pushTransaction(t);
2967
+ async _runRequest(path, name, args = [], opts = {}, parent = null, release = null) {
2968
+ let released = false;
2969
+ const transfer = (t) => {
2970
+ if (release) {
2971
+ released = true;
2972
+ t.completion.whenSubtreeSettled().then(release);
2973
+ }
2947
2974
  };
2948
2975
  try {
2949
- const result = await handler.fn.apply(null, [...args, reqCtx]);
2950
- push(opts?.onOkName, resHandlerName, result, result, null);
2951
- } catch (error) {
2952
- push(opts?.onErrorName, resHandlerName, error, null, error);
2976
+ const curRoot = this.state.val;
2977
+ const txnPath = path.toTransactionPath();
2978
+ const curLeaf = txnPath.lookup(curRoot);
2979
+ const handler = this.comps.getRequestFor(curLeaf, name) ?? mkReq404(name);
2980
+ const reqCtx = new RequestContext(path, this, parent, curRoot);
2981
+ const resHandlerName = opts?.onResName ?? name;
2982
+ const resPath = opts?.livePath ? null : txnPath.pinKeys(curRoot);
2983
+ const push = (specificName, baseName, singleArg, result, error) => {
2984
+ const resArgs = specificName ? [singleArg] : [result, error];
2985
+ const t = new ResponseEvent(path, this, specificName ?? baseName, resArgs, parent, resPath);
2986
+ transfer(t);
2987
+ this.pushTransaction(t);
2988
+ };
2989
+ try {
2990
+ const result = await handler.fn.apply(null, [...args, reqCtx]);
2991
+ push(opts?.onOkName, resHandlerName, result, result, null);
2992
+ } catch (error) {
2993
+ push(opts?.onErrorName, resHandlerName, error, null, error);
2994
+ }
2995
+ } finally {
2996
+ if (release && !released)
2997
+ release();
2953
2998
  }
2954
2999
  }
2955
3000
  get hasPendingTransactions() {
@@ -2960,13 +3005,18 @@ class Transactor {
2960
3005
  this.transact(this.transactions.shift());
2961
3006
  }
2962
3007
  transact(transaction) {
2963
- const curState = this.state.val;
2964
- const newState = transaction.run(curState, this.comps);
2965
- if (newState !== undefined) {
2966
- this.state.set(newState, { transaction });
2967
- transaction.afterTransaction();
2968
- } else
2969
- console.warn("undefined new state", { curState, transaction });
3008
+ try {
3009
+ const curState = this.state.val;
3010
+ const newState = transaction.run(curState, this.comps);
3011
+ if (newState !== undefined) {
3012
+ this.state.set(newState, { transaction });
3013
+ transaction.afterTransaction();
3014
+ } else
3015
+ console.warn("undefined new state", { curState, transaction });
3016
+ } finally {
3017
+ transaction._completion?.ensureSelfSettled();
3018
+ transaction._completion?.releaseSelf();
3019
+ }
2970
3020
  }
2971
3021
  transactInputNow(path, event, eventHandler, dragInfo) {
2972
3022
  this.transact(new InputEvent(path, event, eventHandler, this, dragInfo));
@@ -2987,18 +3037,17 @@ class Transaction {
2987
3037
  this.path = path;
2988
3038
  this.transactor = transactor;
2989
3039
  this.parentTransaction = parentTransaction;
2990
- this._task = null;
3040
+ this._completion = null;
2991
3041
  }
2992
- get task() {
2993
- this._task ??= new Task;
2994
- return this._task;
3042
+ get completion() {
3043
+ this._completion ??= new Completion;
3044
+ return this._completion;
2995
3045
  }
2996
- getCompletionPromise() {
2997
- return this.task.promise;
3046
+ whenSettled() {
3047
+ return this.completion.whenSettled();
2998
3048
  }
2999
- setParent(parentTransaction) {
3000
- this.parentTransaction = parentTransaction;
3001
- parentTransaction.task.addDep(this.task);
3049
+ whenSubtreeSettled() {
3050
+ return this.completion.whenSubtreeSettled();
3002
3051
  }
3003
3052
  run(rootValue, comps) {
3004
3053
  return this.updateRootValue(rootValue, comps);
@@ -3024,7 +3073,7 @@ class Transaction {
3024
3073
  const txnPath = this.getTransactionPath();
3025
3074
  const curLeaf = txnPath.lookup(curRoot);
3026
3075
  const newLeaf = this.callHandler(curRoot, curLeaf, comps);
3027
- this._task?.complete?.({ value: newLeaf, old: curLeaf });
3076
+ this._completion?.markSelfSettled({ value: newLeaf, old: curLeaf });
3028
3077
  return curLeaf !== newLeaf ? txnPath.setValue(curRoot, newLeaf) : curRoot;
3029
3078
  }
3030
3079
  lookupName(_name) {
@@ -3161,29 +3210,69 @@ class BubbleEvent extends SendEvent {
3161
3210
  }
3162
3211
  }
3163
3212
 
3164
- class Task {
3213
+ class InputDispatchEvent extends NameArgsTransaction {
3214
+ handlerProp = "input";
3215
+ }
3216
+
3217
+ class Completion {
3165
3218
  constructor() {
3166
- this.deps = [];
3167
- this.val = this.resolve = this.reject = null;
3168
- this.promise = new Promise((res, rej) => {
3169
- this.resolve = res;
3170
- this.reject = rej;
3219
+ this.val = undefined;
3220
+ this.selfSettled = false;
3221
+ this.subtreeSettled = false;
3222
+ this.pending = 1;
3223
+ this._selfResolve = null;
3224
+ this._selfPromise = null;
3225
+ this._subtreeResolve = null;
3226
+ this._subtreePromise = null;
3227
+ this._selfReleased = false;
3228
+ }
3229
+ whenSettled() {
3230
+ if (this.selfSettled)
3231
+ return Promise.resolve(this.val);
3232
+ this._selfPromise ??= new Promise((res) => {
3233
+ this._selfResolve = res;
3171
3234
  });
3172
- this.isCompleted = false;
3235
+ return this._selfPromise;
3173
3236
  }
3174
- addDep(task) {
3175
- console.assert(!this.isCompleted, "addDep for completed task", this, task);
3176
- this.deps.push(task);
3177
- task.promise.then((_) => this._check());
3237
+ whenSubtreeSettled() {
3238
+ if (this.subtreeSettled)
3239
+ return Promise.resolve(this.val);
3240
+ this._subtreePromise ??= new Promise((res) => {
3241
+ this._subtreeResolve = res;
3242
+ });
3243
+ return this._subtreePromise;
3178
3244
  }
3179
- complete(val) {
3245
+ markSelfSettled(val) {
3246
+ if (this.selfSettled)
3247
+ return;
3248
+ this.selfSettled = true;
3180
3249
  this.val = val;
3181
- this._check();
3250
+ this._selfResolve?.(val);
3251
+ }
3252
+ ensureSelfSettled() {
3253
+ if (!this.selfSettled)
3254
+ this.markSelfSettled(this.val);
3182
3255
  }
3183
- _check() {
3184
- if (this.deps.every((task) => task.isCompleted)) {
3185
- this.isCompleted = true;
3186
- this.resolve(this);
3256
+ track() {
3257
+ this.pending++;
3258
+ let done = false;
3259
+ return () => {
3260
+ if (done)
3261
+ return;
3262
+ done = true;
3263
+ this._release();
3264
+ };
3265
+ }
3266
+ releaseSelf() {
3267
+ if (this._selfReleased)
3268
+ return;
3269
+ this._selfReleased = true;
3270
+ this._release();
3271
+ }
3272
+ _release() {
3273
+ if (--this.pending === 0) {
3274
+ this.subtreeSettled = true;
3275
+ this._subtreeResolve?.(this.val);
3187
3276
  }
3188
3277
  }
3189
3278
  }
@@ -3222,6 +3311,9 @@ class Dispatcher {
3222
3311
  requestAtPath(path, name, args, opts) {
3223
3312
  return this.transactor.pushRequest(path, name, args, opts, this.parent);
3224
3313
  }
3314
+ inputAtPath(path, name, args, opts) {
3315
+ return this.transactor.pushInput(path, name, args, opts, this.parent);
3316
+ }
3225
3317
  lookupTypeFor(name, inst) {
3226
3318
  return this.transactor.comps.getCompFor(inst).scope.lookupComponent(name);
3227
3319
  }
@@ -3576,6 +3668,45 @@ import {
3576
3668
  version
3577
3669
  } from "immutable";
3578
3670
 
3671
+ // src/on.js
3672
+ var OP_KINDS = ["send", "bubble", "request", "input"];
3673
+ function phaseOps(phase) {
3674
+ const ops = [];
3675
+ for (const type of OP_KINDS)
3676
+ for (const a of phase[type] ?? [])
3677
+ ops.push({ type, ...a });
3678
+ for (const a of phase.do ?? [])
3679
+ ops.push(a);
3680
+ return ops;
3681
+ }
3682
+ function resolveArgs(args, self) {
3683
+ return typeof args === "function" ? args(self) ?? [] : args ?? [];
3684
+ }
3685
+ function dispatchPhase(dispatcher, targetPath, phase, self) {
3686
+ if (!phase)
3687
+ return;
3688
+ for (const op of phaseOps(phase)) {
3689
+ const args = resolveArgs(op.args, self);
3690
+ switch (op.type) {
3691
+ case "send":
3692
+ dispatcher.sendAtPath(targetPath, op.name, args, op.opts);
3693
+ break;
3694
+ case "bubble":
3695
+ dispatcher.sendAtPath(targetPath, op.name, args, {
3696
+ skipSelf: true,
3697
+ bubbles: true,
3698
+ ...op.opts
3699
+ });
3700
+ break;
3701
+ case "request":
3702
+ dispatcher.requestAtPath(targetPath, op.name, args, op.opts);
3703
+ break;
3704
+ case "input":
3705
+ dispatcher.inputAtPath(targetPath, op.name, args, op.opts);
3706
+ break;
3707
+ }
3708
+ }
3709
+ }
3579
3710
  // src/oo.js
3580
3711
  import { Map as IMap, Set as ISet, List, OrderedMap, Record } from "immutable";
3581
3712
  var BAD_VALUE = Symbol("BadValue");
@@ -3989,8 +4120,10 @@ export {
3989
4120
  test,
3990
4121
  setIn,
3991
4122
  set,
4123
+ resolveArgs,
3992
4124
  removeIn,
3993
4125
  remove,
4126
+ phaseOps,
3994
4127
  mergeWith,
3995
4128
  mergeDeepWith,
3996
4129
  mergeDeep,
@@ -4023,6 +4156,7 @@ export {
4023
4156
  getIn,
4024
4157
  get,
4025
4158
  fromJS,
4159
+ dispatchPhase,
4026
4160
  css,
4027
4161
  component,
4028
4162
  collectIterBindings,