phlex-reactive 0.2.3 → 0.2.4
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 +34 -4
- data/README.md +14 -6
- data/app/javascript/phlex/reactive/reactive_controller.js +20 -0
- 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: cd797b375cedb73b2a6f5695bf38a7eddd0a939aca5f8602c661537f6407ad7b
|
|
4
|
+
data.tar.gz: 994da28bed60c495d87d01fb736e80f19439ca52417f2c70f61a17aa7d15b351
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 11e13357335ec795cc5d0da9b60da2ce059128fed4f355f3864d037fbf5c0dab692844d3b8dd9d10f0b386e7bccd86ae635b2d2bc17a24e4ed1988bd37ce014e
|
|
7
|
+
data.tar.gz: 11c0e74b06f6d55818fc65d83336d9ec77eee65422adfe477a5ebc8a83d6dda4ae6f1dad3c6fdf17a9e215cdef5bcc958bcbaa43c3e6d32b02adb15124c372e1
|
data/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,24 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
8
8
|
|
|
9
9
|
### Fixed
|
|
10
10
|
|
|
11
|
+
- **Reactive save silently dropped rich-text / custom-editor fields.** The
|
|
12
|
+
client's field auto-collection (`#collectFields`) queried only
|
|
13
|
+
`input[name], select[name], textarea[name]`, so a named rich-text editor
|
|
14
|
+
(`lexxy-editor`, `trix-editor`) or any `[contenteditable]` was skipped — a
|
|
15
|
+
reactive `save` posted an empty value and silently wiped the field, with no
|
|
16
|
+
error. `#collectFields` now also reads named custom editors and contenteditable
|
|
17
|
+
elements (by `name` *attribute*, since a plain element has no `name` IDL
|
|
18
|
+
property), reading the serialized `.value` else the contenteditable text. It
|
|
19
|
+
only fills a name the standard controls left absent or empty, so a hidden input
|
|
20
|
+
a rich editor mirrors into (e.g. Trix) still wins when populated. The vendored
|
|
21
|
+
client copy the system suite runs (`spec/dummy/public/vendor/...`) is now kept
|
|
22
|
+
byte-identical to source by a guard spec, so the browser tests never validate
|
|
23
|
+
stale client code again. Closes #8.
|
|
24
|
+
|
|
25
|
+
## [0.2.3] - 2026-06-24
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
11
29
|
- **Record-backed components silently lost `reactive_state` every action.** When
|
|
12
30
|
a component declared BOTH `reactive_record` and `reactive_state`, the state
|
|
13
31
|
branch was dead: `reactive_token` signed only the record GID and
|
|
@@ -23,6 +41,16 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
23
41
|
round trip (only a genuinely absent value falls back to the `initialize`
|
|
24
42
|
default). No API changes. Closes #6.
|
|
25
43
|
|
|
44
|
+
### Documentation
|
|
45
|
+
|
|
46
|
+
- Documented the combined record + state identity (the `{c, gid, s}` token
|
|
47
|
+
shape): the README, `docs/architecture.md`, and `docs/security.md` no longer
|
|
48
|
+
frame `reactive_record` and `reactive_state` as mutually exclusive. (#9)
|
|
49
|
+
|
|
50
|
+
## [0.2.2] - 2026-06-24
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
|
|
26
54
|
- **Record-backed component built with a different init keyword by the action
|
|
27
55
|
endpoint vs the broadcast path.** The click path (`Component.from_identity`)
|
|
28
56
|
builds with `reactive_record_key` (the `reactive_record :name`), but the
|
|
@@ -37,7 +65,7 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
37
65
|
an explicit `def self.model_param_name` override still wins. No API changes.
|
|
38
66
|
Closes #4.
|
|
39
67
|
|
|
40
|
-
## [0.2.1]
|
|
68
|
+
## [0.2.1] - 2026-06-24
|
|
41
69
|
|
|
42
70
|
### Fixed
|
|
43
71
|
|
|
@@ -52,7 +80,7 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
52
80
|
the gem eager-loads cleanly. Added a regression spec that calls
|
|
53
81
|
`eager_load_all`. No API changes.
|
|
54
82
|
|
|
55
|
-
## [0.2.0]
|
|
83
|
+
## [0.2.0] - 2026-06-24
|
|
56
84
|
|
|
57
85
|
### Added
|
|
58
86
|
|
|
@@ -71,7 +99,7 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
71
99
|
helpers. phlex-reactive still works on Action Cable without pgbus; the
|
|
72
100
|
`exclude:` argument is simply ignored there.
|
|
73
101
|
|
|
74
|
-
## [0.1.0]
|
|
102
|
+
## [0.1.0] - 2026-06-20
|
|
75
103
|
|
|
76
104
|
### Added
|
|
77
105
|
|
|
@@ -95,7 +123,9 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
95
123
|
scaffolds a reactive component (and an RSpec spec when the app uses RSpec),
|
|
96
124
|
state-backed by default or record-backed with `--record`.
|
|
97
125
|
|
|
98
|
-
[Unreleased]: https://github.com/mhenrixon/phlex-reactive/compare/v0.2.
|
|
126
|
+
[Unreleased]: https://github.com/mhenrixon/phlex-reactive/compare/v0.2.2...HEAD
|
|
127
|
+
[0.2.3]: https://github.com/mhenrixon/phlex-reactive/compare/v0.2.2...v0.2.3
|
|
128
|
+
[0.2.2]: https://github.com/mhenrixon/phlex-reactive/compare/v0.2.1...v0.2.2
|
|
99
129
|
[0.2.1]: https://github.com/mhenrixon/phlex-reactive/compare/v0.2.0...v0.2.1
|
|
100
130
|
[0.2.0]: https://github.com/mhenrixon/phlex-reactive/compare/v0.1.0...v0.2.0
|
|
101
131
|
[0.1.0]: https://github.com/mhenrixon/phlex-reactive/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -211,15 +211,21 @@ class Todos::Item < ApplicationComponent
|
|
|
211
211
|
end
|
|
212
212
|
```
|
|
213
213
|
|
|
214
|
-
### 2. State-backed (
|
|
214
|
+
### 2. State-backed (signed instance vars)
|
|
215
215
|
|
|
216
|
-
|
|
217
|
-
|
|
216
|
+
Sign small, JSON-serializable instance vars into the token. Use it **alone** for
|
|
217
|
+
a record-less widget (a counter, a wizard step), or **alongside `reactive_record`**
|
|
218
|
+
to carry transient UI state — which field, what mode — next to the row. Both the
|
|
219
|
+
record's GlobalID and the state are signed into one token and rebuilt on each
|
|
220
|
+
action. Keep state small and JSON-serializable.
|
|
218
221
|
|
|
219
222
|
```ruby
|
|
220
223
|
reactive_state :count, :step # signed; rebuilt on each action
|
|
221
224
|
```
|
|
222
225
|
|
|
226
|
+
The [inline edit example](docs/examples/inline_edit.md) combines both: a
|
|
227
|
+
`reactive_record :record` plus `reactive_state :attribute, :editing`.
|
|
228
|
+
|
|
223
229
|
---
|
|
224
230
|
|
|
225
231
|
## Concrete examples
|
|
@@ -257,7 +263,7 @@ Use in controllers: `render turbo_stream: Counter.replace(counter)`.
|
|
|
257
263
|
| Macro / helper | Use |
|
|
258
264
|
|---|---|
|
|
259
265
|
| `reactive_record :name` | Record-backed identity (GlobalID). State = the DB. |
|
|
260
|
-
| `reactive_state :a, :b` |
|
|
266
|
+
| `reactive_state :a, :b` | Signed instance-var identity. Standalone, or combined with `reactive_record` to sign transient UI state alongside the row. |
|
|
261
267
|
| `action :name, params: { x: :integer }` | Declare a client-invokable action + its param schema. **Default-deny.** |
|
|
262
268
|
| `reactive_attrs` | Spread onto the root element: marks it reactive + carries the signed token. |
|
|
263
269
|
| `on(:action, event: "click", **params)` | Spread onto a trigger element. Adds `type=button` for clicks. |
|
|
@@ -314,8 +320,10 @@ phlex-reactive is built so the easy path is the safe path — but the boundary i
|
|
|
314
320
|
real, so read this once.
|
|
315
321
|
|
|
316
322
|
- **State is never trusted from the client.** The DOM holds a `MessageVerifier`-
|
|
317
|
-
signed identity
|
|
318
|
-
|
|
323
|
+
signed identity — `{component, gid}` (record-backed), `{component, state}`
|
|
324
|
+
(state-backed), or `{component, gid, state}` when a component declares both —
|
|
325
|
+
not raw state. A tampered class, record, or state value fails signature
|
|
326
|
+
verification → 400.
|
|
319
327
|
- **Actions are default-deny.** Only methods declared with `action :name` are
|
|
320
328
|
invokable. A public method without `action` is unreachable.
|
|
321
329
|
- **You must authorize.** The signature proves the *token is yours*, not that
|
|
@@ -122,6 +122,7 @@ export default class extends Controller {
|
|
|
122
122
|
|
|
123
123
|
#collectFields() {
|
|
124
124
|
const fields = {}
|
|
125
|
+
// Standard form controls.
|
|
125
126
|
this.element.querySelectorAll("input[name], select[name], textarea[name]").forEach((field) => {
|
|
126
127
|
if (field.type === "checkbox") {
|
|
127
128
|
fields[field.name] = field.checked
|
|
@@ -131,6 +132,25 @@ export default class extends Controller {
|
|
|
131
132
|
fields[field.name] = field.value
|
|
132
133
|
}
|
|
133
134
|
})
|
|
135
|
+
// Named rich-text / custom editors (lexxy-editor, trix-editor) and bare
|
|
136
|
+
// [contenteditable]. These aren't input/select/textarea, so the query above
|
|
137
|
+
// skips them — without this, a reactive save posts an empty value and
|
|
138
|
+
// silently wipes the field (issue #8). Read whatever the element exposes:
|
|
139
|
+
// a custom editor's serialized `.value`, else its contenteditable text.
|
|
140
|
+
// Only fill a name the standard controls left absent or empty, so a synced
|
|
141
|
+
// hidden input (e.g. Trix mirrors into one) still wins when populated.
|
|
142
|
+
this.element
|
|
143
|
+
.querySelectorAll("[name]:is(lexxy-editor, trix-editor, [contenteditable=''], [contenteditable=true], [contenteditable=plaintext-only])")
|
|
144
|
+
.forEach((el) => {
|
|
145
|
+
// A plain element (e.g. a <div contenteditable>) has no `name` IDL
|
|
146
|
+
// property — only the attribute — so read getAttribute, not el.name.
|
|
147
|
+
const name = el.getAttribute("name")
|
|
148
|
+
if (!name) return
|
|
149
|
+
const existing = fields[name]
|
|
150
|
+
if (existing == null || existing === "") {
|
|
151
|
+
fields[name] = el.value ?? el.textContent ?? el.innerHTML ?? ""
|
|
152
|
+
}
|
|
153
|
+
})
|
|
134
154
|
return fields
|
|
135
155
|
}
|
|
136
156
|
|