phlex-reactive 0.4.2 → 0.4.3
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 +22 -0
- data/app/javascript/phlex/reactive/reactive_controller.js +46 -2
- data/lib/phlex/reactive/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: 63629dadef998637b9b65f885a00d54297821f405599a64c103e3b482e991fdc
|
|
4
|
+
data.tar.gz: 4826a5cf276f46cdf99575cb624169bacbbc3bf85115ca1256542e4c7aeb6f6b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7561f6119d33b19334078c64570bb2fc7e6e9064b0ee90c4bcbac21dbbe2e07d5ccdb0854d146848efee66bb084c895eb89a853c3931de4c7fd115a0594163d9
|
|
7
|
+
data.tar.gz: 5220bfda32b47381507fed0d3a9a9d6620f06e9a10dbd8eb877fc07a0f420a8a1e3ff2dd06805059b0b0b7f550bb76cac81a7899f258bb85508643ef61dc8e35
|
data/CHANGELOG.md
CHANGED
|
@@ -72,6 +72,28 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
72
72
|
|
|
73
73
|
### Fixed
|
|
74
74
|
|
|
75
|
+
- **Client mirror of #44: collections of *reactive* rows were STILL add-once-only in
|
|
76
|
+
the browser — `#extractToken` read the FIRST token in the response, not this
|
|
77
|
+
controller's own (#46).** The server fix in 0.4.2 (#44) made the `add` response
|
|
78
|
+
correct — the container's fresh `reactive:token` stream is present — but the
|
|
79
|
+
client still broke. After each action the reactive controller stores the
|
|
80
|
+
response's fresh token for its NEXT dispatch; `#extractToken` did
|
|
81
|
+
`html.match(/data-reactive-token-value="([^"]+)"/)` — the FIRST match in the whole
|
|
82
|
+
body. On a collection of reactive rows the response is, in body order: the
|
|
83
|
+
appended/prepended ROW (carrying its OWN token, since a reactive row is itself a
|
|
84
|
+
`data-controller="reactive"` root) FIRST, then the container's `reactive:token`
|
|
85
|
+
refresh LAST. So the list controller stored the ROW's token; its second dispatch
|
|
86
|
+
sent a row token → failed verification → the second add silently did nothing. The
|
|
87
|
+
fix reads the token that RE-RENDERS this controller's own element id — preferring
|
|
88
|
+
the dedicated `reactive:token` stream targeting `this.element.id`, then a self
|
|
89
|
+
`replace`/`update` of it — and otherwise keeps the existing token (never adopting
|
|
90
|
+
a child row's or a sibling component's token). This is the exact client analogue
|
|
91
|
+
of the #44 server rule: a stream "carries the token for C" only when it re-renders
|
|
92
|
+
C itself, never when it inserts children into C. A request-level test can't catch
|
|
93
|
+
this (the server response is already correct); covered by a bun unit test on the
|
|
94
|
+
token selection AND a real two-click browser test (a collection of reactive rows →
|
|
95
|
+
three rows after three adds) under Puma + Falcon. Refs cosmos#1939, #44, #30.
|
|
96
|
+
|
|
75
97
|
- **Collections of *reactive* rows were still add-once-only — a prepended/appended
|
|
76
98
|
row's OWN token suppressed the container's token refresh (#44).** When an action
|
|
77
99
|
appended/prepended a *reactive* child row into a container whose `#id` equals the
|
|
@@ -64,6 +64,13 @@ export function registerReactiveActions() {
|
|
|
64
64
|
registerReactiveToken()
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
// Escape a DOM id for safe interpolation into a RegExp (an id can legally contain
|
|
68
|
+
// regex metacharacters like `.`/`:` — e.g. an `escape:`-namespaced or dotted id).
|
|
69
|
+
// Used by #extractToken to match the stream that re-renders THIS element by id.
|
|
70
|
+
export function escapeRegExp(string) {
|
|
71
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
72
|
+
}
|
|
73
|
+
|
|
67
74
|
if (typeof window !== "undefined") {
|
|
68
75
|
if (window.Turbo) registerReactiveActions()
|
|
69
76
|
else document.addEventListener("turbo:load", registerReactiveActions, { once: true })
|
|
@@ -286,9 +293,46 @@ export default class extends Controller {
|
|
|
286
293
|
this.#tokenCache = value
|
|
287
294
|
}
|
|
288
295
|
|
|
296
|
+
// Read the next token for THIS controller — the one that re-renders THIS
|
|
297
|
+
// element's id, never just the first token in the body (issue #46). On a
|
|
298
|
+
// collection of REACTIVE rows the prepended/appended ROW carries its OWN
|
|
299
|
+
// data-reactive-token-value and it sorts FIRST in the response; the list's own
|
|
300
|
+
// fresh token rides a trailing `reactive:token` stream targeting the container.
|
|
301
|
+
// Grabbing the first match stored the ROW's token, so the list's SECOND
|
|
302
|
+
// dispatch sent a row token → failed verification → add-once-only. This mirrors
|
|
303
|
+
// the server's carries_token_for? (#44): a stream carries OUR token only when it
|
|
304
|
+
// RE-RENDERS our id (reactive:token / replace / update of `this.element.id`) —
|
|
305
|
+
// append/prepend insert children and never count. Returns undefined when no
|
|
306
|
+
// stream re-renders our id, so #currentToken keeps its existing value.
|
|
289
307
|
#extractToken(html) {
|
|
290
|
-
const
|
|
291
|
-
|
|
308
|
+
const id = this.element.id
|
|
309
|
+
if (!id) {
|
|
310
|
+
// No id to self-match (shouldn't happen for a reactive root). Fall back to
|
|
311
|
+
// the legacy first-token behavior so a single-component response still works.
|
|
312
|
+
return html.match(/data-reactive-token-value="([^"]+)"/)?.[1]
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// The dedicated token-only refresh for THIS element (partial updates / the
|
|
316
|
+
// collection container) — an attribute on the <turbo-stream> itself.
|
|
317
|
+
const tokenStream = html.match(
|
|
318
|
+
new RegExp(
|
|
319
|
+
`<turbo-stream\\b[^>]*\\baction="reactive:token"[^>]*\\btarget="${escapeRegExp(id)}"[^>]*\\bdata-reactive-token-value="([^"]+)"`,
|
|
320
|
+
),
|
|
321
|
+
)
|
|
322
|
+
if (tokenStream) return tokenStream[1]
|
|
323
|
+
|
|
324
|
+
// A full self re-render: a replace/update of THIS element whose template root
|
|
325
|
+
// carries the fresh token. Scope the token search to that one stream so a
|
|
326
|
+
// sibling/child token elsewhere in the body can't leak in.
|
|
327
|
+
const selfStream = html.match(
|
|
328
|
+
new RegExp(
|
|
329
|
+
`<turbo-stream\\b[^>]*\\baction="(?:replace|update)"[^>]*\\btarget="${escapeRegExp(id)}"[^>]*>([\\s\\S]*?)</turbo-stream>`,
|
|
330
|
+
),
|
|
331
|
+
)
|
|
332
|
+
if (selfStream) return selfStream[1].match(/data-reactive-token-value="([^"]+)"/)?.[1]
|
|
333
|
+
|
|
334
|
+
// Nothing re-rendered our id — keep the current token.
|
|
335
|
+
return undefined
|
|
292
336
|
}
|
|
293
337
|
|
|
294
338
|
// True when `el` is collected by THIS reactive root and not by a nested one.
|