plutonium 0.37.0 → 0.38.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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium-controller/SKILL.md +25 -2
  3. data/.claude/skills/plutonium-definition-fields/SKILL.md +33 -0
  4. data/.claude/skills/plutonium-nested-resources/SKILL.md +79 -19
  5. data/.claude/skills/plutonium-policy/SKILL.md +93 -6
  6. data/CHANGELOG.md +36 -0
  7. data/CLAUDE.md +8 -10
  8. data/CONTRIBUTING.md +6 -8
  9. data/Rakefile +16 -1
  10. data/app/assets/plutonium.css +1 -1
  11. data/app/assets/plutonium.js +9371 -11492
  12. data/app/assets/plutonium.js.map +4 -4
  13. data/app/assets/plutonium.min.js +55 -55
  14. data/app/assets/plutonium.min.js.map +4 -4
  15. data/docs/guides/index.md +5 -0
  16. data/docs/guides/nested-resources.md +132 -29
  17. data/docs/guides/troubleshooting.md +82 -0
  18. data/docs/reference/controller/index.md +1 -1
  19. data/docs/reference/definition/fields.md +33 -0
  20. data/docs/reference/model/index.md +1 -1
  21. data/docs/reference/policy/index.md +77 -6
  22. data/gemfiles/rails_7.gemfile.lock +3 -3
  23. data/gemfiles/rails_8.0.gemfile.lock +3 -3
  24. data/gemfiles/rails_8.1.gemfile.lock +3 -3
  25. data/lib/plutonium/core/controller.rb +144 -19
  26. data/lib/plutonium/core/controllers/association_resolver.rb +86 -0
  27. data/lib/plutonium/helpers/display_helper.rb +12 -0
  28. data/lib/plutonium/query/filters/association.rb +25 -3
  29. data/lib/plutonium/resource/controller.rb +90 -9
  30. data/lib/plutonium/resource/controllers/authorizable.rb +17 -4
  31. data/lib/plutonium/resource/controllers/crud_actions.rb +7 -5
  32. data/lib/plutonium/resource/controllers/interactive_actions.rb +9 -0
  33. data/lib/plutonium/resource/controllers/presentable.rb +13 -11
  34. data/lib/plutonium/resource/policy.rb +85 -2
  35. data/lib/plutonium/resource/record/routes.rb +31 -1
  36. data/lib/plutonium/routing/mapper_extensions.rb +40 -4
  37. data/lib/plutonium/routing/route_set_extensions.rb +3 -0
  38. data/lib/plutonium/ui/breadcrumbs.rb +1 -1
  39. data/lib/plutonium/ui/display/resource.rb +5 -2
  40. data/lib/plutonium/ui/form/components/key_value_store.rb +17 -5
  41. data/lib/plutonium/ui/page/index.rb +1 -1
  42. data/lib/plutonium/version.rb +1 -1
  43. data/lib/tasks/release.rake +1 -1
  44. data/package.json +6 -5
  45. data/plutonium.gemspec +1 -1
  46. data/src/js/controllers/key_value_store_controller.js +6 -0
  47. data/src/js/controllers/resource_drop_down_controller.js +3 -3
  48. data/yarn.lock +1465 -693
  49. metadata +6 -5
  50. data/app/javascript/controllers/key_value_store_controller.js +0 -119
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: plutonium
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.37.0
4
+ version: 0.38.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Froelich
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-01-21 00:00:00.000000000 Z
11
+ date: 2026-01-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: zeitwerk
@@ -184,14 +184,14 @@ dependencies:
184
184
  requirements:
185
185
  - - ">="
186
186
  - !ruby/object:Gem::Version
187
- version: 0.13.0
187
+ version: 0.14.0
188
188
  type: :runtime
189
189
  prerelease: false
190
190
  version_requirements: !ruby/object:Gem::Requirement
191
191
  requirements:
192
192
  - - ">="
193
193
  - !ruby/object:Gem::Version
194
- version: 0.13.0
194
+ version: 0.14.0
195
195
  - !ruby/object:Gem::Dependency
196
196
  name: phlexi-table
197
197
  requirement: !ruby/object:Gem::Requirement
@@ -444,7 +444,6 @@ files:
444
444
  - app/assets/plutonium.min.js
445
445
  - app/assets/plutonium.min.js.map
446
446
  - app/assets/plutonium.png
447
- - app/javascript/controllers/key_value_store_controller.js
448
447
  - app/views/layouts/resource.html.erb
449
448
  - app/views/layouts/rodauth.html.erb
450
449
  - app/views/plutonium/_flash.html.erb
@@ -541,6 +540,7 @@ files:
541
540
  - docs/guides/nested-resources.md
542
541
  - docs/guides/search-filtering.md
543
542
  - docs/guides/theming.md
543
+ - docs/guides/troubleshooting.md
544
544
  - docs/index.md
545
545
  - docs/public/android-chrome-192x192.png
546
546
  - docs/public/android-chrome-512x512.png
@@ -779,6 +779,7 @@ files:
779
779
  - lib/plutonium/configuration.rb
780
780
  - lib/plutonium/core/.DS_Store
781
781
  - lib/plutonium/core/controller.rb
782
+ - lib/plutonium/core/controllers/association_resolver.rb
782
783
  - lib/plutonium/core/controllers/authorizable.rb
783
784
  - lib/plutonium/core/controllers/bootable.rb
784
785
  - lib/plutonium/core/controllers/entity_scoping.rb
@@ -1,119 +0,0 @@
1
- import { Controller } from "@hotwired/stimulus"
2
-
3
- // Connects to data-controller="key-value-store"
4
- export default class extends Controller {
5
- static targets = ["container", "pair", "template", "addButton", "keyInput", "valueInput"]
6
- static values = { limit: Number }
7
-
8
- connect() {
9
- this.updateIndices()
10
- this.updateAddButtonState()
11
- }
12
-
13
- addPair(event) {
14
- event.preventDefault()
15
-
16
- if (this.pairTargets.length >= this.limitValue) {
17
- return
18
- }
19
-
20
- const template = this.templateTarget
21
- const newPair = template.content.cloneNode(true)
22
- const index = this.pairTargets.length
23
-
24
- // Update the template placeholders with actual indices
25
- this.updatePairIndices(newPair, index)
26
-
27
- this.containerTarget.appendChild(newPair)
28
- this.updateIndices()
29
- this.updateAddButtonState()
30
-
31
- // Focus on the key input of the new pair
32
- const newKeyInput = this.containerTarget.lastElementChild.querySelector('[data-key-value-store-target="keyInput"]')
33
- if (newKeyInput) {
34
- newKeyInput.focus()
35
- }
36
- }
37
-
38
- removePair(event) {
39
- event.preventDefault()
40
-
41
- const pair = event.target.closest('[data-key-value-store-target="pair"]')
42
- if (pair) {
43
- pair.remove()
44
- this.updateIndices()
45
- this.updateAddButtonState()
46
- }
47
- }
48
-
49
- updateIndices() {
50
- this.pairTargets.forEach((pair, index) => {
51
- const keyInput = pair.querySelector('[data-key-value-store-target="keyInput"]')
52
- const valueInput = pair.querySelector('[data-key-value-store-target="valueInput"]')
53
-
54
- if (keyInput) {
55
- // Update name attribute
56
- keyInput.name = keyInput.name.replace(/\[\d+\]/, `[${index}]`)
57
- // Update id attribute for Turbo compatibility
58
- keyInput.id = keyInput.id.replace(/_\d+_/, `_${index}_`)
59
- }
60
- if (valueInput) {
61
- // Update name attribute
62
- valueInput.name = valueInput.name.replace(/\[\d+\]/, `[${index}]`)
63
- // Update id attribute for Turbo compatibility
64
- valueInput.id = valueInput.id.replace(/_\d+_/, `_${index}_`)
65
- }
66
- })
67
- }
68
-
69
- updatePairIndices(element, index) {
70
- const inputs = element.querySelectorAll('input')
71
- inputs.forEach(input => {
72
- if (input.name) {
73
- input.name = input.name.replace('__INDEX__', index)
74
- }
75
- if (input.id) {
76
- input.id = input.id.replace('___INDEX___', `_${index}_`)
77
- }
78
- })
79
- }
80
-
81
- updateAddButtonState() {
82
- const addButton = this.addButtonTarget
83
- if (this.pairTargets.length >= this.limitValue) {
84
- addButton.disabled = true
85
- addButton.classList.add('opacity-50', 'cursor-not-allowed')
86
- } else {
87
- addButton.disabled = false
88
- addButton.classList.remove('opacity-50', 'cursor-not-allowed')
89
- }
90
- }
91
-
92
- // Serialize the current key-value pairs to JSON
93
- toJSON() {
94
- const pairs = {}
95
- this.pairTargets.forEach(pair => {
96
- const keyInput = pair.querySelector('[data-key-value-store-target="keyInput"]')
97
- const valueInput = pair.querySelector('[data-key-value-store-target="valueInput"]')
98
-
99
- if (keyInput && valueInput && keyInput.value.trim()) {
100
- pairs[keyInput.value.trim()] = valueInput.value
101
- }
102
- })
103
- return JSON.stringify(pairs)
104
- }
105
-
106
- // Get the current key-value pairs as an object
107
- toObject() {
108
- const pairs = {}
109
- this.pairTargets.forEach(pair => {
110
- const keyInput = pair.querySelector('[data-key-value-store-target="keyInput"]')
111
- const valueInput = pair.querySelector('[data-key-value-store-target="valueInput"]')
112
-
113
- if (keyInput && valueInput && keyInput.value.trim()) {
114
- pairs[keyInput.value.trim()] = valueInput.value
115
- }
116
- })
117
- return pairs
118
- }
119
- }