studio-engine 0.46.0 → 0.47.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +54 -0
- data/README.md +19 -1
- data/app/views/studio/modals/_host.html.erb +39 -0
- data/app/views/studio/modals/_scoped_host.html.erb +16 -0
- data/lib/studio/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d1da7736255e9cf6690f07488e8758227bd6231e08502d123dcbefbdd6f6c685
|
|
4
|
+
data.tar.gz: a8e2e861599996fdb22203bb9ed42feae6f5343232a1c0578c73fd95f28b8b81
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6f1effe6341c220633e267f9382cf5dc2ca68565565489b95feceb744003aceaca151663a0cdba25c7c11753f99ac78369c4c4327232436628f55b30c40afcf1
|
|
7
|
+
data.tar.gz: 33526cb24c80fc6e26ada46d15fbbd5701f5681af7cd4a9feaab2bfcea6c92e8283b7d50d1408cc8f160c2624ed5038968509fbb37ae3783ae003f0381ef1500
|
data/CHANGELOG.md
CHANGED
|
@@ -35,6 +35,60 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
|
|
|
35
35
|
|
|
36
36
|
### Added
|
|
37
37
|
|
|
38
|
+
- **`$store.modals.isLive(id)` — "is this card up?", the question `isOpen` cannot
|
|
39
|
+
answer.** `close()` flips `_closing` immediately and splices the entry only
|
|
40
|
+
after the exit animation, so for that whole ~220ms window `isOpen(id)` reports
|
|
41
|
+
a card that is already leaving the screen as open. Every consumer guard that
|
|
42
|
+
means *don't open this twice* wants the opposite answer, and turf-monster had
|
|
43
|
+
hand-rolled the same `_closing`-aware `.some()` over `.stack` to get it.
|
|
44
|
+
|
|
45
|
+
`isLive(id)` returns true when a card with that id is on the stack **and not on
|
|
46
|
+
its way out**. It also accepts an array — `isLive(['onboarding', 'age-verify',
|
|
47
|
+
'wallet-setup'])` — so a caller can ask "is any card of this flow still up?"
|
|
48
|
+
without reaching into `.stack` and re-deriving the lifecycle rule. Shipped on
|
|
49
|
+
both the shared host and the page-scoped host.
|
|
50
|
+
|
|
51
|
+
**`isOpen(id)` is unchanged.** It still answers "on the stack, in any state",
|
|
52
|
+
which is what a caller asking "is the DOM node still mounted" depends on. This
|
|
53
|
+
is additive; no existing call site changes behavior. The proposal on the task
|
|
54
|
+
was `isOpen(id, { ignoreClosing: true })`, rejected because it reads as a double
|
|
55
|
+
negative at the call site (*is it open, ignoring that it is closing*) and
|
|
56
|
+
because two genuinely different questions are clearer as two names than as one
|
|
57
|
+
name with a mode flag.
|
|
58
|
+
|
|
59
|
+
The asymmetry to know, and the reason this is not simply "not animating":
|
|
60
|
+
|
|
61
|
+
| Transition | Flags on the entry | `isOpen` | `isLive` |
|
|
62
|
+
|---|---|---|---|
|
|
63
|
+
| `open()` | — | true | **true** |
|
|
64
|
+
| `close()` | `_closing` | true | **false** |
|
|
65
|
+
| `swap()`, leaving entry | `_swappingOut` + `_closing` | true | **false** |
|
|
66
|
+
| `advance()` | `_swappingOut` only | true | **true** |
|
|
67
|
+
| spliced / never opened | — | false | **false** |
|
|
68
|
+
|
|
69
|
+
`advance()` slides the SAME card between steps of its own flow, so it sets
|
|
70
|
+
`_swappingOut` and nothing else. `isLive` therefore tests `_closing` alone and
|
|
71
|
+
never consults `_swappingOut`: conflating the two reports a card as gone in the
|
|
72
|
+
middle of a step transition, which is how an in-flow hand-off starts looking to
|
|
73
|
+
a caller like a dismissal. Both halves are pinned by tests that execute the
|
|
74
|
+
store under node (`test/views/modal_host_store_behavior_test.rb`) — dropping the
|
|
75
|
+
`_closing` check and conflating it with `_swappingOut` redden *different*
|
|
76
|
+
assertions.
|
|
77
|
+
|
|
78
|
+
**Consumers that fork the host do not get this from a version bump.**
|
|
79
|
+
`mcritchie-studio` and `turf-monster` both ship their own
|
|
80
|
+
`app/views/studio/modals/_host.html.erb`, and a non-isolated engine lets the app
|
|
81
|
+
copy shadow the gem's. Those apps pick `isLive` up only by porting it into their
|
|
82
|
+
fork or by retiring the fork; `studio/modals/scoped_host` is unforked everywhere
|
|
83
|
+
and carries it today. turf-monster's two hand-rolled scans — the onboarding
|
|
84
|
+
chain driver's `open()` guard (`layouts/application.html.erb`) and
|
|
85
|
+
`selectionBoard#showWalletSetupModal` (`contests/_turf_totals_board.html.erb`)
|
|
86
|
+
— can then collapse onto this primitive, as can its two remaining bare
|
|
87
|
+
`isOpen()` idempotence guards (`app/javascript/solana_stores.js` for
|
|
88
|
+
`wallet-changed`, `cdp/returns/show.html.erb` for `cdp-ramp`), which are the
|
|
89
|
+
exact defect `isLive` exists to prevent. A turf-side follow-up, not part of
|
|
90
|
+
this change.
|
|
91
|
+
|
|
38
92
|
- **The standard user profile columns — and the engine's first migration against
|
|
39
93
|
a host-owned table.** Every other engine migration creates a `studio_*` table
|
|
40
94
|
the engine owns outright; `users` belongs to the host. That boundary is crossed
|
data/README.md
CHANGED
|
@@ -258,6 +258,14 @@ The scoped host takes its animations from `engine-motion.css` rather than an
|
|
|
258
258
|
inline copy, so a consumer bundling that layer gets the same spring as the shared
|
|
259
259
|
host.
|
|
260
260
|
|
|
261
|
+
It carries the store API below with three deliberate differences: there is no
|
|
262
|
+
`advance()`; `swap()` replaces the top entry immediately rather than running the
|
|
263
|
+
shared host's directional slide — so no entry is ever left mid-transition; and it
|
|
264
|
+
does not read the `window.ModalAnimations` registry, so the `enterAnim` /
|
|
265
|
+
`exitAnim` props documented under the table are ignored and the exit always plays
|
|
266
|
+
the 220ms unmount from `engine-motion.css`. `isOpen` and `isLive` behave
|
|
267
|
+
identically on both.
|
|
268
|
+
|
|
261
269
|
Store API (`Alpine.store('modals')`):
|
|
262
270
|
|
|
263
271
|
| Call | Behavior |
|
|
@@ -268,7 +276,17 @@ Store API (`Alpine.store('modals')`):
|
|
|
268
276
|
| `close()` | Animated close of the current modal (no-op if already closing). |
|
|
269
277
|
| `closeAll()` | Instant, unanimated clear (used by navigation cleanup). |
|
|
270
278
|
| `closeAllDismissible()` | Clears all modals except those opened with `dismissible: false`. |
|
|
271
|
-
| `isOpen(id)` / `current()` | Introspection. |
|
|
279
|
+
| `isOpen(id)` / `current()` | Introspection. `isOpen` is true while a card is ON THE STACK — including the whole exit animation, because `close()` flips `_closing` at once and splices only after it. |
|
|
280
|
+
| `isLive(id)` / `isLive([id, …])` | True when a card with that id is up and **not on its way out**. Pass an array to ask about a set of ids ("is any card of this flow still up?"). |
|
|
281
|
+
|
|
282
|
+
**`isOpen` or `isLive`?** A guard that means "don't open this twice" almost
|
|
283
|
+
always wants `isLive`. `isOpen` stays true for the ~220ms exit window, so an
|
|
284
|
+
idempotence check built on it refuses to reopen a card the user just dismissed.
|
|
285
|
+
The one asymmetry worth knowing: `advance()` slides the *same* card between
|
|
286
|
+
steps of its own flow and sets only `_swappingOut`, so a mid-advance card is
|
|
287
|
+
still **live** — only `close()` and the outgoing half of a `swap()` set
|
|
288
|
+
`_closing`. `isLive` tests `_closing` alone, deliberately; conflating it with
|
|
289
|
+
`_swappingOut` makes an in-flow hand-off look like a dismissal.
|
|
272
290
|
|
|
273
291
|
Recognized props: `dismissible: false` disables escape/click-outside dismissal
|
|
274
292
|
(e.g. an in-flight transaction); `enterAnim` / `exitAnim` pick a named
|
|
@@ -390,12 +390,51 @@
|
|
|
390
390
|
});
|
|
391
391
|
this._sync();
|
|
392
392
|
},
|
|
393
|
+
// isOpen(id) — is a card with this id ON THE STACK, in any
|
|
394
|
+
// lifecycle state? A card mid-close still counts: close() flips
|
|
395
|
+
// _closing at once and splices only after the exit animation, so
|
|
396
|
+
// this answers TRUE for that whole exit window. Unchanged — callers
|
|
397
|
+
// asking "is the DOM node still mounted" depend on exactly this.
|
|
398
|
+
// If you mean "is it still up", you want isLive() below.
|
|
393
399
|
isOpen: function(id) {
|
|
394
400
|
for (var i = 0; i < this.stack.length; i++) {
|
|
395
401
|
if (this.stack[i].id === id) return true;
|
|
396
402
|
}
|
|
397
403
|
return false;
|
|
398
404
|
},
|
|
405
|
+
// isLive(id) / isLive([id, ...]) — is a card with this id on the
|
|
406
|
+
// stack and NOT on its way out? This is what a "don't open it
|
|
407
|
+
// twice" guard actually means, and it is the question isOpen
|
|
408
|
+
// cannot answer.
|
|
409
|
+
//
|
|
410
|
+
// The difference is a TIMING one, and the asymmetry is load-bearing:
|
|
411
|
+
//
|
|
412
|
+
// close() entry gets _closing, and is spliced
|
|
413
|
+
// CLOSE_ANIM_MS later. isOpen stays TRUE for
|
|
414
|
+
// that window; isLive is FALSE immediately.
|
|
415
|
+
// swap() / open(..., the LEAVING entry gets _swappingOut AND
|
|
416
|
+
// { replace: true }) _closing — a different card is taking the
|
|
417
|
+
// screen, so it is not live either.
|
|
418
|
+
// advance() the entry gets _swappingOut and NOTHING
|
|
419
|
+
// else. Same card, same id, sliding between
|
|
420
|
+
// steps of its own flow. It stays LIVE.
|
|
421
|
+
//
|
|
422
|
+
// So this tests _closing ALONE and deliberately never consults
|
|
423
|
+
// _swappingOut. Conflating the two reports a card as gone in the
|
|
424
|
+
// middle of a step transition, which is how an in-flow hand-off
|
|
425
|
+
// starts looking to a caller like a dismissal.
|
|
426
|
+
//
|
|
427
|
+
// Accepts an array so a caller can ask about a SET of ids ("is any
|
|
428
|
+
// card belonging to this flow still up?") without reaching into
|
|
429
|
+
// .stack and re-deriving the lifecycle rule for itself.
|
|
430
|
+
isLive: function(id) {
|
|
431
|
+
var ids = Array.isArray(id) ? id : [id];
|
|
432
|
+
for (var i = 0; i < this.stack.length; i++) {
|
|
433
|
+
var entry = this.stack[i];
|
|
434
|
+
if (entry && !entry._closing && ids.indexOf(entry.id) !== -1) return true;
|
|
435
|
+
}
|
|
436
|
+
return false;
|
|
437
|
+
},
|
|
399
438
|
current: function() {
|
|
400
439
|
return this.stack.length ? this.stack[this.stack.length - 1] : null;
|
|
401
440
|
},
|
|
@@ -138,10 +138,26 @@
|
|
|
138
138
|
this._sync();
|
|
139
139
|
},
|
|
140
140
|
|
|
141
|
+
// isOpen(id) — on the stack in ANY lifecycle state, including a card
|
|
142
|
+
// mid-close (close() flips _closing at once and splices only after
|
|
143
|
+
// CLOSE_ANIM_MS). isLive() below is the "still up" question.
|
|
141
144
|
isOpen: function(id) {
|
|
142
145
|
return this.stack.some(function(entry) { return entry.id === id; });
|
|
143
146
|
},
|
|
144
147
|
|
|
148
|
+
// isLive(id) / isLive([id, ...]) — on the stack and NOT on its way
|
|
149
|
+
// out. Mirrors the shared host's primitive so a page-scoped store
|
|
150
|
+
// answers the same question the same way; see studio/modals/_host
|
|
151
|
+
// for the full lifecycle rationale. Tests _closing ALONE: a leaving
|
|
152
|
+
// swap() entry carries _swappingOut AND _closing, so _swappingOut on
|
|
153
|
+
// its own must never count as leaving.
|
|
154
|
+
isLive: function(id) {
|
|
155
|
+
var ids = Array.isArray(id) ? id : [id];
|
|
156
|
+
return this.stack.some(function(entry) {
|
|
157
|
+
return entry && !entry._closing && ids.indexOf(entry.id) !== -1;
|
|
158
|
+
});
|
|
159
|
+
},
|
|
160
|
+
|
|
145
161
|
current: function() {
|
|
146
162
|
return this.stack.length ? this.stack[this.stack.length - 1] : null;
|
|
147
163
|
},
|
data/lib/studio/version.rb
CHANGED