inertia_rails 3.13.0 → 3.15.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -0
  3. data/app/views/inertia.html.erb +1 -1
  4. data/lib/generators/inertia/install/frameworks.yml +3 -18
  5. data/lib/generators/inertia/install/install_generator.rb +51 -6
  6. data/lib/generators/inertia/install/templates/assets/rails.svg +9 -0
  7. data/lib/generators/inertia/install/templates/controller.rb +6 -1
  8. data/lib/generators/inertia/install/templates/initializer.rb +1 -0
  9. data/lib/generators/inertia/install/templates/react/InertiaExample.jsx +42 -46
  10. data/lib/generators/inertia/install/templates/react/InertiaExample.module.css +63 -41
  11. data/lib/generators/inertia/install/templates/react/InertiaExample.tsx +45 -46
  12. data/lib/generators/inertia/install/templates/react/inertia.jsx +2 -1
  13. data/lib/generators/inertia/install/templates/react/inertia.tsx +4 -3
  14. data/lib/generators/inertia/install/templates/svelte/InertiaExample.svelte +103 -69
  15. data/lib/generators/inertia/install/templates/svelte/InertiaExample.ts.svelte +104 -69
  16. data/lib/generators/inertia/install/templates/svelte/inertia.js +1 -1
  17. data/lib/generators/inertia/install/templates/svelte/inertia.ts +2 -1
  18. data/lib/generators/inertia/install/templates/vue/InertiaExample.ts.vue +107 -70
  19. data/lib/generators/inertia/install/templates/vue/InertiaExample.vue +129 -92
  20. data/lib/generators/inertia/install/templates/vue/inertia.js +3 -3
  21. data/lib/generators/inertia/install/templates/vue/inertia.ts +4 -3
  22. data/lib/generators/inertia_templates/scaffold/templates/react/form.tsx.tt +3 -3
  23. data/lib/generators/inertia_templates/scaffold/templates/svelte/form.ts.svelte.tt +3 -3
  24. data/lib/generators/inertia_templates/scaffold/templates/svelte/new.svelte.tt +1 -1
  25. data/lib/generators/inertia_templates/scaffold/templates/svelte/new.ts.svelte.tt +1 -1
  26. data/lib/generators/inertia_templates/scaffold/templates/vue/form.ts.vue.tt +3 -3
  27. data/lib/generators/inertia_tw_templates/scaffold/templates/react/form.tsx.tt +3 -3
  28. data/lib/generators/inertia_tw_templates/scaffold/templates/svelte/form.ts.svelte.tt +3 -3
  29. data/lib/generators/inertia_tw_templates/scaffold/templates/svelte/new.svelte.tt +1 -2
  30. data/lib/generators/inertia_tw_templates/scaffold/templates/svelte/new.ts.svelte.tt +1 -1
  31. data/lib/generators/inertia_tw_templates/scaffold/templates/vue/form.ts.vue.tt +3 -3
  32. data/lib/inertia_rails/base_prop.rb +1 -1
  33. data/lib/inertia_rails/configuration.rb +6 -0
  34. data/lib/inertia_rails/defer_prop.rb +1 -0
  35. data/lib/inertia_rails/helper.rb +14 -0
  36. data/lib/inertia_rails/inertia_rails.rb +8 -2
  37. data/lib/inertia_rails/merge_prop.rb +1 -0
  38. data/lib/inertia_rails/once_prop.rb +12 -0
  39. data/lib/inertia_rails/optional_prop.rb +1 -0
  40. data/lib/inertia_rails/prop_onceable.rb +39 -0
  41. data/lib/inertia_rails/renderer.rb +52 -13
  42. data/lib/inertia_rails/version.rb +1 -1
  43. data/lib/inertia_rails.rb +1 -0
  44. metadata +5 -2
@@ -1,117 +1,154 @@
1
1
  <template>
2
- <Head title="Inertia + Vite Ruby + Vue Example" />
2
+ <Head title="Ruby on Rails + Inertia + Vue" />
3
3
 
4
4
  <div class="root">
5
- <h1 class="h1">Hello {{ name }}!</h1>
6
-
7
- <div>
8
- <a href="https://inertia-rails.dev" target="_blank">
9
- <img class="logo" :src="inertiaSvg" alt="Inertia logo" />
5
+ <nav class="subNav">
6
+ <a href="https://rubyonrails.org" target="_blank">
7
+ <img class="logo rails" :src="railsSvg" alt="Ruby on Rails Logo" />
10
8
  </a>
11
- <a href="https://vite-ruby.netlify.app" target="_blank">
12
- <img class="logo vite" :src="viteRubySvg" alt="Vite Ruby logo" />
9
+ <a href="https://inertia-rails.dev" target="_blank">
10
+ <img class="logo inertia" :src="inertiaSvg" alt="Inertia logo" />
13
11
  </a>
14
12
  <a href="https://vuejs.org" target="_blank">
15
- <img class="logo vue" :src="vueSvg" alt="Vue logo" />
13
+ <img class="logo vue" :src="vueSvg" alt="Vue logo"/>
16
14
  </a>
17
- </div>
15
+ </nav>
18
16
 
19
- <h2 class="h2">Inertia + Vite Ruby + Vue</h2>
17
+ <div class="footer">
18
+ <div class="card">
19
+ <p>
20
+ Edit <code><%= js_destination_path %>/pages/inertia_example/index.vue</code> and save to test <abbr title="Hot Module Replacement">HMR</abbr>.
21
+ </p>
22
+ </div>
20
23
 
21
- <div class="card">
22
- <button class="button" type="button" @click="count++">
23
- count is {{ count }}
24
- </button>
25
- <p>
26
- Edit <code>app/frontend/pages/inertia_example/index.vue</code> and save to test
27
- HMR
28
- </p>
24
+ <ul>
25
+ <li>
26
+ <ul>
27
+ <li><strong>Rails version:</strong> {{ rails_version }}</li>
28
+ <li><strong>Rack version:</strong> {{ rack_version }}</li>
29
+ </ul>
30
+ </li>
31
+ <li><strong>Ruby version:</strong> {{ ruby_version }}</li>
32
+ <li>
33
+ <ul>
34
+ <li><strong>Inertia Rails version:</strong> {{ inertia_rails_version }}</li>
35
+ <li><strong>Vue version:</strong> {{ vue_version }}</li>
36
+ </ul>
37
+ </li>
38
+ </ul>
29
39
  </div>
30
- <p class="readTheDocs">
31
- Click on the Inertia, Vite Ruby, and Vue logos to learn more
32
- </p>
33
40
  </div>
34
41
  </template>
35
42
 
36
43
  <script setup>
37
44
  import { Head } from '@inertiajs/vue3'
38
- import { ref } from 'vue'
45
+ import { version as vue_version } from 'vue';
39
46
 
47
+ import railsSvg from '/assets/rails.svg'
40
48
  import inertiaSvg from '/assets/inertia.svg'
41
- import viteRubySvg from '/assets/vite_ruby.svg'
42
49
  import vueSvg from '/assets/vue.svg'
43
50
 
44
51
  defineProps({
45
- name: String,
52
+ rails_version: String,
53
+ rack_version: String,
54
+ ruby_version: String,
55
+ inertia_rails_version: String,
46
56
  })
47
-
48
- const count = ref(0)
49
57
  </script>
50
58
 
59
+ <style global>
60
+ body {
61
+ margin: 0;
62
+ padding: 0;
63
+ }
64
+ </style>
65
+
51
66
  <style scoped>
52
- .root {
53
- font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
54
- line-height: 1.5;
55
- font-weight: 400;
56
- color: #213547;
57
- background-color: #ffffff;
58
- max-width: 1280px;
59
- margin: 0 auto;
60
- padding: 2rem;
61
- text-align: center;
62
- }
63
-
64
- .h1 {
65
- font-size: 3.2em;
66
- line-height: 1.1;
67
- }
68
-
69
- .h2 {
70
- font-size: 2.6em;
71
- line-height: 1.1;
72
- }
73
-
74
- .button {
75
- border-radius: 8px;
76
- border: 1px solid transparent;
77
- padding: 0.6em 1.2em;
78
- font-size: 1em;
79
- font-weight: 500;
80
- font-family: inherit;
81
- background-color: #f9f9f9;
82
- cursor: pointer;
83
- transition: border-color 0.25s;
84
- }
85
- .button:hover {
86
- border-color: #646cff;
87
- }
88
- .button:focus,
89
- .button:focus-visible {
90
- outline: 4px auto -webkit-focus-ring-color;
91
- }
92
-
93
- .logo {
94
- display: inline-block;
95
- height: 6em;
96
- padding: 1.5em;
97
- will-change: filter;
98
- transition: filter 300ms;
99
- }
100
- .logo:hover {
101
- filter: drop-shadow(0 0 2em #646cffaa);
102
- }
103
- .logo.vite:hover {
104
- filter: drop-shadow(0 0 2em #e4023baa);
105
- }
106
- .logo.vue:hover {
107
- filter: drop-shadow(0 0 2em #41b883aa);
108
- }
109
-
110
- .card {
111
- padding: 2em;
112
- }
113
-
114
- .readTheDocs {
115
- color: #888;
116
- }
67
+ .root {
68
+ box-sizing: border-box;
69
+ margin: 0;
70
+ padding: 0;
71
+ align-items: center;
72
+ background-color: #F0E7E9;
73
+ background-image: url(data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjEwMjQiIHZpZXdCb3g9IjAgMCAxNDQwIDEwMjQiIHdpZHRoPSIxNDQwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Im0xNDQwIDUxMC4wMDA2NDh2LTUxMC4wMDA2NDhoLTE0NDB2Mzg0LjAwMDY0OGM0MTcuMzExOTM5IDEzMS4xNDIxNzkgODkxIDE3MS41MTMgMTQ0MCAxMjZ6IiBmaWxsPSIjZmZmIi8+PC9zdmc+);
74
+ background-position: center center;
75
+ background-repeat: no-repeat;
76
+ background-size: cover;
77
+ color: #261B23;
78
+ display: flex;
79
+ flex-direction: column;
80
+ font-family: Sans-Serif;
81
+ font-size: calc(0.9em + 0.5vw);
82
+ font-style: normal;
83
+ font-weight: 400;
84
+ justify-content: center;
85
+ line-height: 1.25;
86
+ min-height: 100vh;
87
+ text-align: center;
88
+ }
89
+
90
+ @media (prefers-color-scheme: dark) {
91
+ .root {
92
+ background-color: #1a1a1a;
93
+ background-image: url(data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjEwMjQiIHZpZXdCb3g9IjAgMCAxNDQwIDEwMjQiIHdpZHRoPSIxNDQwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Im0xNDQwIDUxMC4wMDA2NDh2LTUxMC4wMDA2NDhoLTE0NDB2Mzg0LjAwMDY0OGM0MTcuMzExOTM5IDEzMS4xNDIxNzkgODkxIDE3MS41MTMgMTQ0MCAxMjZ6IiBmaWxsPSIjMzMzIi8+PC9zdmc+);
94
+ color: #e0e0e0;
95
+ }
96
+ }
97
+
98
+ .logo {
99
+ display: inline-block;
100
+ height: 9.8vw;
101
+ min-height: 130px;
102
+ padding: 1.5em;
103
+ will-change: filter;
104
+ transition: filter 300ms;
105
+ filter: drop-shadow(0 20px 13px rgb(0 0 0 / 0.03)) drop-shadow(0 8px 5px rgb(0 0 0 / 0.08));
106
+ }
107
+ .logo.inertia:hover {
108
+ filter: drop-shadow(0 0 2em #646cffaa);
109
+ }
110
+ .logo.vue:hover {
111
+ filter: drop-shadow(0 0 2em #61dafbaa);
112
+ }
113
+ .logo.rails:hover {
114
+ filter: drop-shadow(0 0 2em rgb(211 0 1 / 0.6));
115
+ }
116
+
117
+ @media (prefers-color-scheme: dark) {
118
+ .logo {
119
+ filter: drop-shadow(0 20px 13px rgb(255 255 255 / 0.03)) drop-shadow(0 8px 5px rgb(255 255 255 / 0.08));
120
+ }
121
+ }
122
+
123
+ .card {
124
+ padding: 2em;
125
+ font-size: 0.7em;
126
+ color: #948e90;
127
+ }
128
+
129
+ .footer {
130
+ bottom: 0;
131
+ left: 0;
132
+ margin: 0 2rem 2rem 2rem;
133
+ position: absolute;
134
+ right: 0;
135
+ }
136
+
137
+ .footer ul {
138
+ list-style: none;
139
+ }
140
+
141
+ .footer ul li {
142
+ display: inline;
143
+ }
144
+
145
+ .footer ul ul li:after {
146
+ content: " | ";
147
+ font-weight: 300;
148
+ color: #948e90;
149
+ }
150
+
151
+ .footer ul ul li:last-child:after {
152
+ content: "";
153
+ }
117
154
  </style>
@@ -37,14 +37,14 @@ createInertiaApp({
37
37
  },
38
38
 
39
39
  defaults: {
40
+ form: {
41
+ forceIndicesArrayFormatInFormData: false,
42
+ },
40
43
  future: {
41
44
  useDataInertiaHeadAttribute: true,
42
45
  useDialogForErrorModal: true,
43
46
  preserveEqualProps: true,
44
47
  },
45
- visitOptions: () => {
46
- return { queryStringArrayFormat: "brackets" }
47
- },
48
48
  },
49
49
  }).catch((error) => {
50
50
  // This ensures this entrypoint is only loaded on Inertia pages
@@ -37,14 +37,15 @@ createInertiaApp({
37
37
  },
38
38
 
39
39
  defaults: {
40
+ form: {
41
+ forceIndicesArrayFormatInFormData: false,
42
+ },
40
43
  future: {
44
+ useScriptElementForInitialPage: true,
41
45
  useDataInertiaHeadAttribute: true,
42
46
  useDialogForErrorModal: true,
43
47
  preserveEqualProps: true,
44
48
  },
45
- visitOptions: () => {
46
- return { queryStringArrayFormat: "brackets" }
47
- },
48
49
  },
49
50
  }).catch((error) => {
50
51
  // This ensures this entrypoint is only loaded on Inertia pages
@@ -28,7 +28,7 @@ export default function Form({ <%= singular_table_name %>, submitText, ...formPr
28
28
  id="password"
29
29
  />
30
30
  {errors.password && (
31
- <div style={{ color: 'red' }}>{errors.password}</div>
31
+ <div style={{ color: 'red' }}>{errors.password.join(', ')}</div>
32
32
  )}
33
33
  </div>
34
34
 
@@ -42,7 +42,7 @@ export default function Form({ <%= singular_table_name %>, submitText, ...formPr
42
42
  id="password_confirmation"
43
43
  />
44
44
  {errors.password_confirmation && (
45
- <div style={{ color: 'red' }}>{errors.password_confirmation}</div>
45
+ <div style={{ color: 'red' }}>{errors.password_confirmation.join(', ')}</div>
46
46
  )}
47
47
  </div>
48
48
  <% else -%>
@@ -92,7 +92,7 @@ export default function Form({ <%= singular_table_name %>, submitText, ...formPr
92
92
  />
93
93
  <% end -%>
94
94
  {errors.<%= attribute.column_name %> && (
95
- <div style={{ color: 'red' }}>{errors.<%= attribute.column_name %>}</div>
95
+ <div style={{ color: 'red' }}>{errors.<%= attribute.column_name %>.join(', ')}</div>
96
96
  )}
97
97
  </div>
98
98
  <% end -%>
@@ -29,7 +29,7 @@
29
29
  id="password"
30
30
  />
31
31
  {#if errors.password}
32
- <div class="error">{errors.password}</div>
32
+ <div class="error">{errors.password.join(', ')}</div>
33
33
  {/if}
34
34
  </div>
35
35
 
@@ -41,7 +41,7 @@
41
41
  id="password_confirmation"
42
42
  />
43
43
  {#if errors.password_confirmation}
44
- <div class="error">{errors.password_confirmation}</div>
44
+ <div class="error">{errors.password_confirmation.join(', ')}</div>
45
45
  {/if}
46
46
  </div>
47
47
  <% else -%>
@@ -82,7 +82,7 @@
82
82
  />
83
83
  <% end -%>
84
84
  {#if errors.<%= attribute.column_name %>}
85
- <div class="error">{errors.<%= attribute.column_name %>}</div>
85
+ <div class="error">{errors.<%= attribute.column_name %>.join(', ')}</div>
86
86
  {/if}
87
87
  </div>
88
88
  <% end -%>
@@ -15,7 +15,7 @@
15
15
  <Form
16
16
  {<%= singular_table_name %>}
17
17
  submitText="Create <%= human_name %>"
18
- action="<%= js_resource_path %>"
18
+ action="<%= js_resources_path %>"
19
19
  method="post"
20
20
  />
21
21
 
@@ -16,7 +16,7 @@
16
16
  <Form
17
17
  {<%= singular_table_name %>}
18
18
  submitText="Create <%= human_name %>"
19
- action="<%= js_resource_path %>"
19
+ action="<%= js_resources_path %>"
20
20
  method="post"
21
21
  />
22
22
 
@@ -14,7 +14,7 @@
14
14
  id="password"
15
15
  />
16
16
  <div v-if="errors.password" class="error">
17
- {{ errors.password }}
17
+ {{ errors.password.join(', ') }}
18
18
  </div>
19
19
  </div>
20
20
 
@@ -28,7 +28,7 @@
28
28
  id="password_confirmation"
29
29
  />
30
30
  <div v-if="errors.password_confirmation" class="error">
31
- {{ errors.password_confirmation }}
31
+ {{ errors.password_confirmation.join(', ') }}
32
32
  </div>
33
33
  </div>
34
34
  <% else -%>
@@ -69,7 +69,7 @@
69
69
  />
70
70
  <% end -%>
71
71
  <div v-if="errors.<%= attribute.column_name %>" class="error">
72
- {{ errors.<%= attribute.column_name %> }}
72
+ {{ errors.<%= attribute.column_name %>.join(', ') }}
73
73
  </div>
74
74
  </div>
75
75
  <% end -%>
@@ -29,7 +29,7 @@ export default function Form({ <%= singular_table_name %>, submitText, ...formPr
29
29
  />
30
30
  {errors.password && (
31
31
  <div className="text-red-500 px-3 py-2 font-medium">
32
- {errors.password}
32
+ {errors.password.join(', ')}
33
33
  </div>
34
34
  )}
35
35
  </div>
@@ -44,7 +44,7 @@ export default function Form({ <%= singular_table_name %>, submitText, ...formPr
44
44
  />
45
45
  {errors.password_confirmation && (
46
46
  <div className="text-red-500 px-3 py-2 font-medium">
47
- {errors.password_confirmation}
47
+ {errors.password_confirmation.join(', ')}
48
48
  </div>
49
49
  )}
50
50
  <% else -%>
@@ -99,7 +99,7 @@ export default function Form({ <%= singular_table_name %>, submitText, ...formPr
99
99
  <% end -%>
100
100
  {errors.<%= attribute.column_name %> && (
101
101
  <div className="text-red-500 px-3 py-2 font-medium">
102
- {errors.<%= attribute.column_name %>}
102
+ {errors.<%= attribute.column_name %>.join(', ')}
103
103
  </div>
104
104
  )}
105
105
  <% end -%>
@@ -31,7 +31,7 @@
31
31
  />
32
32
  {#if errors.password}
33
33
  <div class="text-red-500 px-3 py-2 font-medium">
34
- {errors.password}
34
+ {errors.password.join(', ')}
35
35
  </div>
36
36
  {/if}
37
37
  </div>
@@ -46,7 +46,7 @@
46
46
  />
47
47
  {#if errors.password_confirmation}
48
48
  <div class="text-red-500 px-3 py-2 font-medium">
49
- {errors.password_confirmation}
49
+ {errors.password_confirmation.join(', ')}
50
50
  </div>
51
51
  {/if}
52
52
  </div>
@@ -95,7 +95,7 @@
95
95
  <% end -%>
96
96
  {#if errors.<%= attribute.column_name %>}
97
97
  <div class="text-red-500 px-3 py-2 font-medium">
98
- {errors.<%= attribute.column_name %>}
98
+ {errors.<%= attribute.column_name %>.join(', ')}
99
99
  </div>
100
100
  {/if}
101
101
  </div>
@@ -16,8 +16,7 @@
16
16
  <Form
17
17
  {<%= singular_table_name %>}
18
18
  submitText="Create <%= human_name %>"
19
- onSubmit={handleSubmit}
20
- action="<%= js_resource_path %>"
19
+ action="<%= js_resources_path %>"
21
20
  method="post"
22
21
  />
23
22
 
@@ -17,7 +17,7 @@
17
17
  <Form
18
18
  {<%= singular_table_name %>}
19
19
  submitText="Create <%= human_name %>"
20
- action="<%= js_resource_path %>"
20
+ action="<%= js_resources_path %>"
21
21
  method="post"
22
22
  />
23
23
 
@@ -19,7 +19,7 @@
19
19
  v-if="errors.password"
20
20
  class="text-red-500 px-3 py-2 font-medium"
21
21
  >
22
- {{ errors.password }}
22
+ {{ errors.password.join(', ') }}
23
23
  </div>
24
24
  </div>
25
25
 
@@ -35,7 +35,7 @@
35
35
  v-if="errors.password_confirmation"
36
36
  class="text-red-500 px-3 py-2 font-medium"
37
37
  >
38
- {{ errors.password_confirmation }}
38
+ {{ errors.password_confirmation.join(', ') }}
39
39
  </div>
40
40
  </div>
41
41
  <% else -%>
@@ -82,7 +82,7 @@
82
82
  />
83
83
  <% end -%>
84
84
  <div v-if="errors.<%= attribute.column_name %>" class="text-red-500 px-3 py-2 font-medium">
85
- {{ errors.<%= attribute.column_name %> }}
85
+ {{ errors.<%= attribute.column_name %>.join(', ') }}
86
86
  </div>
87
87
  </div>
88
88
  <% end -%>
@@ -3,7 +3,7 @@
3
3
  module InertiaRails
4
4
  # Base class for all props.
5
5
  class BaseProp
6
- def initialize(&block)
6
+ def initialize(**, &block)
7
7
  @block = block
8
8
  end
9
9
 
@@ -34,6 +34,12 @@ module InertiaRails
34
34
 
35
35
  # Whether to include empty `errors` hash to the props when no errors are present.
36
36
  always_include_errors_hash: nil,
37
+
38
+ # Whether to use `<script>` element for initial page rendering instead of the `data-page` attribute.
39
+ use_script_element_for_initial_page: false,
40
+
41
+ # DOM id to use for the root Inertia.js element.
42
+ root_dom_id: 'app',
37
43
  }.freeze
38
44
 
39
45
  OPTION_NAMES = DEFAULTS.keys.freeze
@@ -2,6 +2,7 @@
2
2
 
3
3
  module InertiaRails
4
4
  class DeferProp < IgnoreOnFirstLoadProp
5
+ prepend PropOnceable
5
6
  prepend PropMergeable
6
7
 
7
8
  DEFAULT_GROUP = 'default'
@@ -32,5 +32,19 @@ module InertiaRails
32
32
 
33
33
  safe_join(meta_tags, "\n")
34
34
  end
35
+
36
+ def inertia_root(id: nil, page: inertia_page)
37
+ config = controller.send(:inertia_configuration)
38
+ id ||= config.root_dom_id
39
+
40
+ if config.use_script_element_for_initial_page
41
+ safe_join([
42
+ tag.script(page.to_json.html_safe, 'data-page': id, type: 'application/json'),
43
+ tag.div(id: id)
44
+ ], "\n")
45
+ else
46
+ tag.div(id: id, 'data-page': page.to_json)
47
+ end
48
+ end
35
49
  end
36
50
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'inertia_rails/prop_onceable'
3
4
  require 'inertia_rails/prop_mergeable'
4
5
  require 'inertia_rails/base_prop'
5
6
  require 'inertia_rails/ignore_on_first_load_prop'
@@ -8,6 +9,7 @@ require 'inertia_rails/lazy_prop'
8
9
  require 'inertia_rails/optional_prop'
9
10
  require 'inertia_rails/defer_prop'
10
11
  require 'inertia_rails/merge_prop'
12
+ require 'inertia_rails/once_prop'
11
13
  require 'inertia_rails/scroll_prop'
12
14
  require 'inertia_rails/configuration'
13
15
  require 'inertia_rails/meta_tag'
@@ -36,6 +38,10 @@ module InertiaRails
36
38
  AlwaysProp.new(&block)
37
39
  end
38
40
 
41
+ def once(...)
42
+ OnceProp.new(...)
43
+ end
44
+
39
45
  def merge(...)
40
46
  MergeProp.new(...)
41
47
  end
@@ -44,8 +50,8 @@ module InertiaRails
44
50
  MergeProp.new(deep_merge: true, match_on: match_on, &block)
45
51
  end
46
52
 
47
- def defer(group: nil, merge: nil, deep_merge: nil, match_on: nil, &block)
48
- DeferProp.new(group: group, merge: merge, deep_merge: deep_merge, match_on: match_on, &block)
53
+ def defer(...)
54
+ DeferProp.new(...)
49
55
  end
50
56
 
51
57
  def scroll(metadata = nil, **options, &block)
@@ -2,6 +2,7 @@
2
2
 
3
3
  module InertiaRails
4
4
  class MergeProp < BaseProp
5
+ prepend PropOnceable
5
6
  prepend PropMergeable
6
7
 
7
8
  def initialize(**_props, &block)
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InertiaRails
4
+ class OnceProp < BaseProp
5
+ prepend PropOnceable
6
+
7
+ def initialize(**, &block)
8
+ @once = true
9
+ super(&block)
10
+ end
11
+ end
12
+ end
@@ -2,5 +2,6 @@
2
2
 
3
3
  module InertiaRails
4
4
  class OptionalProp < IgnoreOnFirstLoadProp
5
+ prepend PropOnceable
5
6
  end
6
7
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InertiaRails
4
+ module PropOnceable
5
+ attr_reader :once_key, :once_expires_in
6
+
7
+ def initialize(**props, &block)
8
+ @once = props.fetch(:once, false)
9
+ @once_key = props[:key]
10
+ @once_expires_in = props[:expires_in]
11
+ @fresh = props.fetch(:fresh, false)
12
+
13
+ super
14
+ end
15
+
16
+ def once?
17
+ @once
18
+ end
19
+
20
+ def fresh?
21
+ @fresh
22
+ end
23
+
24
+ def expires_at
25
+ return nil unless @once_expires_in
26
+
27
+ timestamp = case @once_expires_in
28
+ when ActiveSupport::Duration
29
+ (Time.current + @once_expires_in).to_f
30
+ when Numeric
31
+ Time.current.to_f + @once_expires_in
32
+ else
33
+ raise ArgumentError, "Invalid `expires_in` value: #{@once_expires_in.inspect}"
34
+ end
35
+
36
+ (timestamp * 1000).to_i
37
+ end
38
+ end
39
+ end