glib-web 5.0.4 → 5.0.5

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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/app/helpers/glib/json_ui/view_builder/fields.rb +6 -2
  3. data/app/models/concerns/glib/enum_symbolization.rb +26 -0
  4. data/app/models/glib/application_record.rb +1 -0
  5. data/app/views/json_ui/garage/test_page/_header.json.jbuilder +31 -28
  6. data/app/views/json_ui/garage/test_page/auth.json.jbuilder +2 -1
  7. data/app/views/json_ui/garage/test_page/auto_validate.json.jbuilder +2 -1
  8. data/app/views/json_ui/garage/test_page/browsers.json.jbuilder +2 -1
  9. data/app/views/json_ui/garage/test_page/calendar.json.jbuilder +2 -1
  10. data/app/views/json_ui/garage/test_page/carousel.json.jbuilder +2 -1
  11. data/app/views/json_ui/garage/test_page/charts.json.jbuilder +2 -1
  12. data/app/views/json_ui/garage/test_page/column.json.jbuilder +2 -1
  13. data/app/views/json_ui/garage/test_page/commands.json.jbuilder +2 -1
  14. data/app/views/json_ui/garage/test_page/components.json.jbuilder +2 -1
  15. data/app/views/json_ui/garage/test_page/cookies.json.jbuilder +2 -1
  16. data/app/views/json_ui/garage/test_page/custom.json.jbuilder +2 -1
  17. data/app/views/json_ui/garage/test_page/dialog.json.jbuilder +2 -1
  18. data/app/views/json_ui/garage/test_page/dirty_state.json.jbuilder +2 -1
  19. data/app/views/json_ui/garage/test_page/fields.json.jbuilder +2 -1
  20. data/app/views/json_ui/garage/test_page/fields_captcha.json.jbuilder +2 -1
  21. data/app/views/json_ui/garage/test_page/fields_creditCard.json.jbuilder +2 -1
  22. data/app/views/json_ui/garage/test_page/fields_date_time.json.jbuilder +2 -1
  23. data/app/views/json_ui/garage/test_page/fields_dynamicSelect.json.jbuilder +2 -1
  24. data/app/views/json_ui/garage/test_page/fields_location.json.jbuilder +2 -1
  25. data/app/views/json_ui/garage/test_page/fields_otp.json.jbuilder +2 -1
  26. data/app/views/json_ui/garage/test_page/fields_phone.json.jbuilder +2 -1
  27. data/app/views/json_ui/garage/test_page/fields_radio.json.jbuilder +293 -0
  28. data/app/views/json_ui/garage/test_page/fields_rating.json.jbuilder +2 -1
  29. data/app/views/json_ui/garage/test_page/fields_richText.json.jbuilder +2 -1
  30. data/app/views/json_ui/garage/test_page/fields_select.json.jbuilder +3 -2
  31. data/app/views/json_ui/garage/test_page/fields_sign.json.jbuilder +2 -1
  32. data/app/views/json_ui/garage/test_page/fields_stripeExternalAccount.json.jbuilder +2 -1
  33. data/app/views/json_ui/garage/test_page/fields_stripeToken.json.jbuilder +2 -1
  34. data/app/views/json_ui/garage/test_page/fields_text.json.jbuilder +312 -0
  35. data/app/views/json_ui/garage/test_page/fields_timer.json.jbuilder +2 -1
  36. data/app/views/json_ui/garage/test_page/fields_upload.json.jbuilder +66 -4
  37. data/app/views/json_ui/garage/test_page/fields_url_fragment.json.jbuilder +2 -1
  38. data/app/views/json_ui/garage/test_page/flow.json.jbuilder +2 -1
  39. data/app/views/json_ui/garage/test_page/form.json.jbuilder +2 -1
  40. data/app/views/json_ui/garage/test_page/form_dynamic.json.jbuilder +2 -1
  41. data/app/views/json_ui/garage/test_page/forms.json.jbuilder +2 -1
  42. data/app/views/json_ui/garage/test_page/grid.json.jbuilder +2 -1
  43. data/app/views/json_ui/garage/test_page/horizontal.json.jbuilder +2 -1
  44. data/app/views/json_ui/garage/test_page/http.json.jbuilder +2 -1
  45. data/app/views/json_ui/garage/test_page/image.json.jbuilder +26 -2
  46. data/app/views/json_ui/garage/test_page/lifecycle.json.jbuilder +2 -1
  47. data/app/views/json_ui/garage/test_page/list.json.jbuilder +2 -1
  48. data/app/views/json_ui/garage/test_page/list_editable.json.jbuilder +2 -1
  49. data/app/views/json_ui/garage/test_page/lists_append.json.jbuilder +2 -1
  50. data/app/views/json_ui/garage/test_page/logics_set.json.jbuilder +0 -1
  51. data/app/views/json_ui/garage/test_page/multimedia_video.json.jbuilder +2 -1
  52. data/app/views/json_ui/garage/test_page/pagination.json.jbuilder +2 -1
  53. data/app/views/json_ui/garage/test_page/panels.json.jbuilder +2 -1
  54. data/app/views/json_ui/garage/test_page/panels_bulkEdit2.json.jbuilder +2 -1
  55. data/app/views/json_ui/garage/test_page/popovers.json.jbuilder +2 -1
  56. data/app/views/json_ui/garage/test_page/progressCircle.json.jbuilder +2 -1
  57. data/app/views/json_ui/garage/test_page/responsive.json.jbuilder +2 -1
  58. data/app/views/json_ui/garage/test_page/scroll.json.jbuilder +2 -1
  59. data/app/views/json_ui/garage/test_page/selectable.json.jbuilder +2 -1
  60. data/app/views/json_ui/garage/test_page/sheets.json.jbuilder +2 -1
  61. data/app/views/json_ui/garage/test_page/snackbars.json.jbuilder +2 -1
  62. data/app/views/json_ui/garage/test_page/split.json.jbuilder +2 -1
  63. data/app/views/json_ui/garage/test_page/storage_items.json.jbuilder +2 -1
  64. data/app/views/json_ui/garage/test_page/switch.json.jbuilder +56 -0
  65. data/app/views/json_ui/garage/test_page/tabBar.json.jbuilder +144 -0
  66. data/app/views/json_ui/garage/test_page/table.json.jbuilder +2 -1
  67. data/app/views/json_ui/garage/test_page/timeline.json.jbuilder +2 -1
  68. data/app/views/json_ui/garage/test_page/timeouts.json.jbuilder +9 -3
  69. data/app/views/json_ui/garage/test_page/ul.json.jbuilder +2 -1
  70. data/app/views/json_ui/garage/test_page/vertical.json.jbuilder +2 -1
  71. data/app/views/json_ui/garage/test_page/web.json.jbuilder +2 -1
  72. data/app/views/json_ui/garage/test_page/window.json.jbuilder +2 -1
  73. data/app/views/json_ui/garage/test_page/windows.json.jbuilder +2 -1
  74. data/lib/glib/json_crawler/router.rb +39 -0
  75. data/lib/glib/test_helpers.rb +68 -2
  76. metadata +6 -1
@@ -0,0 +1,293 @@
1
+ json.title 'Test Page (Fields Radio)'
2
+
3
+ page = json_ui_page json
4
+
5
+ render 'json_ui/garage/test_page/header', json: json, page: page
6
+
7
+ page.body(
8
+ childViews: ->(body) do
9
+
10
+ body.panels_responsive(
11
+ padding: glib_json_padding_body,
12
+ childViews: ->(res) do
13
+ res.h2 text: 'Fields Radio'
14
+ res.spacer height: 8
15
+ res.label text: 'Radio button input used inside a fields_radioGroup to select a single value from a set of options.'
16
+ res.spacer height: 12
17
+
18
+ res.panels_form(
19
+ url: json_ui_garage_url(path: 'forms/generic_post'),
20
+ method: 'post',
21
+ childViews: ->(form) do
22
+ form.h2 text: 'Overview'
23
+ form.spacer height: 8
24
+ form.label text: 'fields_radio must always be nested inside fields_radioGroup. The group manages the name, value, and onChange/onChangeAndLoad callbacks. Each fields_radio carries its own value and label, and optionally an icon or image.'
25
+ form.spacer height: 12
26
+ form.hr width: 'matchParent'
27
+ form.spacer height: 12
28
+
29
+ form.h2 text: 'Basic example'
30
+ form.spacer height: 8
31
+ form.label text: 'Minimal vertical radio group.'
32
+ form.spacer height: 8
33
+ form.fields_radioGroup(
34
+ id: 'radio_main',
35
+ name: 'user[gender]',
36
+ value: 'M',
37
+ onChange: ->(action) do
38
+ action.logics_set targetId: 'radio_status', data: { text: 'Status: changed' }
39
+ end,
40
+ childViews: ->(group) do
41
+ group.fields_radio value: 'M', label: 'Male'
42
+ group.fields_radio value: 'F', label: 'Female'
43
+ group.fields_radio value: '', label: 'Prefer not to say'
44
+ end
45
+ )
46
+ form.spacer height: 8
47
+ form.label id: 'radio_status', text: 'Status: idle'
48
+
49
+ form.spacer height: 12
50
+ form.hr width: 'matchParent'
51
+ form.spacer height: 12
52
+
53
+ form.h2 text: 'Variants/props'
54
+ form.spacer height: 8
55
+
56
+ form.h4 text: 'Horizontal layout'
57
+ form.spacer height: 8
58
+ form.label text: 'Wrap options in panels_horizontal to lay them out side by side.'
59
+ form.spacer height: 8
60
+ form.fields_radioGroup(
61
+ name: 'user[plan]',
62
+ value: 'fixed',
63
+ childViews: ->(group) do
64
+ group.panels_horizontal(
65
+ childViews: ->(h) do
66
+ h.fields_radio value: 'hourly', label: 'Hourly rate'
67
+ h.spacer width: 20
68
+ h.fields_radio value: 'fixed', label: 'Fixed amount'
69
+ h.spacer width: 20
70
+ h.fields_radio value: 'equity', label: 'Equity only'
71
+ end
72
+ )
73
+ end
74
+ )
75
+
76
+ form.spacer height: 16
77
+
78
+ form.h4 text: 'With icon (featured template)'
79
+ form.spacer height: 8
80
+ form.fields_radioGroup(
81
+ name: 'user[mode]',
82
+ value: 'edit',
83
+ childViews: ->(group) do
84
+ group.panels_horizontal(
85
+ childViews: ->(h) do
86
+ h.fields_radio value: 'edit', label: 'Edit', icon: { template: 'featured', name: 'edit', size: 40, color: '#d1d1d1' }
87
+ h.spacer width: 16
88
+ h.fields_radio value: 'view', label: 'View', icon: { template: 'featured', name: 'visibility', size: 40, color: '#d1d1d1' }
89
+ h.spacer width: 16
90
+ h.fields_radio value: 'delete', label: 'Delete', icon: { template: 'featured', name: 'delete', size: 40, color: '#d1d1d1' }
91
+ end
92
+ )
93
+ end
94
+ )
95
+
96
+ form.spacer height: 16
97
+
98
+ form.h4 text: 'With image (thumbnail and featured templates)'
99
+ form.spacer height: 8
100
+ form.fields_radioGroup(
101
+ name: 'user[choice]',
102
+ value: 'yes',
103
+ childViews: ->(group) do
104
+ group.panels_horizontal(
105
+ childViews: ->(h) do
106
+ h.fields_radio(
107
+ value: 'yes',
108
+ label: 'Yes',
109
+ image: { template: 'thumbnail', url: 'https://cdn.pixabay.com/photo/2020/08/05/13/12/eco-5465432_1280.png', width: 24, height: 24 }
110
+ )
111
+ h.spacer width: 16
112
+ h.fields_radio(
113
+ value: 'no',
114
+ label: 'No',
115
+ image: { template: 'featured', url: 'https://cdn.pixabay.com/photo/2020/08/05/13/12/eco-5465432_1280.png', width: 80, height: 80 }
116
+ )
117
+ end
118
+ )
119
+ end
120
+ )
121
+
122
+ form.spacer height: 16
123
+
124
+ form.h4 text: 'Responsive column layout'
125
+ form.spacer height: 8
126
+ form.label text: 'Use panels_responsive + panels_column for card-style radio options.'
127
+ form.spacer height: 8
128
+ form.fields_radioGroup(
129
+ name: 'user[tier]',
130
+ value: 'pro',
131
+ width: 'matchParent',
132
+ childViews: ->(group) do
133
+ group.panels_responsive(
134
+ width: 'matchParent',
135
+ childViews: ->(resp) do
136
+ resp.panels_column(
137
+ width: 'matchParent',
138
+ lg: { cols: 4, padding: { x: 6 } },
139
+ childViews: ->(col) do
140
+ col.fields_radio(
141
+ width: 'matchParent',
142
+ value: 'free',
143
+ label: 'Free',
144
+ icon: { template: 'featured', name: 'star_border', size: 36, color: '#9e9e9e' }
145
+ )
146
+ end
147
+ )
148
+ resp.panels_column(
149
+ width: 'matchParent',
150
+ lg: { cols: 4, padding: { x: 6 } },
151
+ childViews: ->(col) do
152
+ col.fields_radio(
153
+ width: 'matchParent',
154
+ value: 'pro',
155
+ label: 'Pro',
156
+ icon: { template: 'featured', name: 'star', size: 36, color: '#fbc02d' }
157
+ )
158
+ end
159
+ )
160
+ resp.panels_column(
161
+ width: 'matchParent',
162
+ lg: { cols: 4, padding: { x: 6 } },
163
+ childViews: ->(col) do
164
+ col.fields_radio(
165
+ width: 'matchParent',
166
+ value: 'enterprise',
167
+ label: 'Enterprise',
168
+ icon: { template: 'featured', name: 'star_rate', size: 36, color: '#1565c0' }
169
+ )
170
+ end
171
+ )
172
+ end
173
+ )
174
+ end
175
+ )
176
+
177
+ form.spacer height: 12
178
+ form.hr width: 'matchParent'
179
+ form.spacer height: 12
180
+
181
+ form.h2 text: 'Actions/events'
182
+ form.spacer height: 8
183
+ form.label text: 'Programmatically set value on the basic example above using components_set.'
184
+ form.spacer height: 8
185
+ form.panels_flow(
186
+ innerPadding: { bottom: 0 },
187
+ width: 'matchParent',
188
+ childViews: ->(flow) do
189
+ flow.button(
190
+ text: 'Select Male',
191
+ onClick: ->(action) do
192
+ action.runMultiple(
193
+ childActions: ->(multiple) do
194
+ multiple.components_set targetId: 'radio_main', data: { value: 'M' }
195
+ multiple.logics_set targetId: 'radio_status', data: { text: 'Status: set to Male' }
196
+ end
197
+ )
198
+ end
199
+ )
200
+ flow.spacer width: 8
201
+ flow.button(
202
+ text: 'Select Female',
203
+ onClick: ->(action) do
204
+ action.runMultiple(
205
+ childActions: ->(multiple) do
206
+ multiple.components_set targetId: 'radio_main', data: { value: 'F' }
207
+ multiple.logics_set targetId: 'radio_status', data: { text: 'Status: set to Female' }
208
+ end
209
+ )
210
+ end
211
+ )
212
+ flow.spacer width: 8
213
+ flow.button(
214
+ text: 'Clear selection',
215
+ onClick: ->(action) do
216
+ action.runMultiple(
217
+ childActions: ->(multiple) do
218
+ multiple.components_set targetId: 'radio_main', data: { value: '' }
219
+ multiple.logics_set targetId: 'radio_status', data: { text: 'Status: cleared' }
220
+ end
221
+ )
222
+ end
223
+ )
224
+ end
225
+ )
226
+
227
+ form.spacer height: 16
228
+
229
+ form.h4 text: 'onChangeAndLoad — conditional show/hide'
230
+ form.spacer height: 8
231
+ form.label text: 'Toggle a field based on the selected radio value.'
232
+ form.spacer height: 8
233
+ form.fields_radioGroup(
234
+ name: 'user[contact]',
235
+ value: 'email',
236
+ onChangeAndLoad: ->(action) do
237
+ action.runMultiple(
238
+ childActions: ->(multiple) do
239
+ multiple.logics_set(
240
+ targetId: 'contact_email_field',
241
+ conditionalData: {
242
+ displayed: { '==': [{ 'var': 'user[contact]' }, 'email'] }
243
+ }
244
+ )
245
+ multiple.logics_set(
246
+ targetId: 'contact_phone_field',
247
+ conditionalData: {
248
+ displayed: { '==': [{ 'var': 'user[contact]' }, 'phone'] }
249
+ }
250
+ )
251
+ end
252
+ )
253
+ end,
254
+ childViews: ->(group) do
255
+ group.panels_horizontal(
256
+ childViews: ->(h) do
257
+ h.fields_radio value: 'email', label: 'Email'
258
+ h.spacer width: 20
259
+ h.fields_radio value: 'phone', label: 'Phone'
260
+ end
261
+ )
262
+ end
263
+ )
264
+ form.spacer height: 8
265
+ form.fields_text id: 'contact_email_field', name: 'user[email]', width: 'matchParent', label: 'Email address', value: ''
266
+ form.fields_text id: 'contact_phone_field', name: 'user[phone]', width: 'matchParent', label: 'Phone number', value: ''
267
+
268
+ form.spacer height: 12
269
+ form.hr width: 'matchParent'
270
+ form.spacer height: 12
271
+
272
+
273
+ form.h4 text: 'Single option'
274
+ form.spacer height: 8
275
+ form.label text: 'A radio group with only one option acts as a toggle.'
276
+ form.spacer height: 8
277
+ form.fields_radioGroup(
278
+ name: 'user[agree]',
279
+ value: nil,
280
+ childViews: ->(group) do
281
+ group.fields_radio value: 'yes', label: 'I agree to the terms and conditions'
282
+ end
283
+ )
284
+
285
+
286
+ form.spacer height: 16
287
+ form.fields_submit text: 'Submit'
288
+ end
289
+ )
290
+ end
291
+ )
292
+ end
293
+ )
@@ -2,9 +2,10 @@ json.title 'Test Page (Fields Rating)'
2
2
 
3
3
  page = json_ui_page json
4
4
 
5
+ render 'json_ui/garage/test_page/header', json: json, page: page
6
+
5
7
  page.body(
6
8
  childViews: ->(body) do
7
- render 'json_ui/garage/test_page/header', view: body
8
9
 
9
10
  body.panels_responsive(
10
11
  padding: glib_json_padding_body,
@@ -2,9 +2,10 @@ json.title 'Test Page (Fields RichText)'
2
2
 
3
3
  page = json_ui_page json
4
4
 
5
+ render 'json_ui/garage/test_page/header', json: json, page: page
6
+
5
7
  page.body(
6
8
  childViews: ->(body) do
7
- render 'json_ui/garage/test_page/header', view: body
8
9
 
9
10
  body.panels_responsive(
10
11
  padding: glib_json_padding_body,
@@ -2,9 +2,10 @@ json.title 'Test Page (Fields Select Options)'
2
2
 
3
3
  page = json_ui_page json
4
4
 
5
+ render 'json_ui/garage/test_page/header', json: json, page: page
6
+
5
7
  page.body(
6
8
  childViews: ->(body) do
7
- render 'json_ui/garage/test_page/header', view: body
8
9
 
9
10
  body.panels_responsive(
10
11
  padding: glib_json_padding_body,
@@ -95,7 +96,7 @@ page.body(
95
96
  onClick: ->(action) do
96
97
  action.components_set(
97
98
  targetId: 'select_media',
98
- data: { options: icon_options, value: %w[option1 option3], multiple: true, useChips: true }
99
+ data: { options: icon_options, value: %w[option1 option3], multiple: true, useChips: true, maxChipLines: 1, width: 250 }
99
100
  )
100
101
  end
101
102
  )
@@ -2,9 +2,10 @@ json.title 'Test Page (Fields Sign)'
2
2
 
3
3
  page = json_ui_page json
4
4
 
5
+ render 'json_ui/garage/test_page/header', json: json, page: page
6
+
5
7
  page.body(
6
8
  childViews: ->(body) do
7
- render 'json_ui/garage/test_page/header', view: body
8
9
 
9
10
  body.panels_responsive(
10
11
  padding: glib_json_padding_body,
@@ -2,9 +2,10 @@ json.title 'Test Page (Fields Stripe External Account)'
2
2
 
3
3
  page = json_ui_page json
4
4
 
5
+ render 'json_ui/garage/test_page/header', json: json, page: page
6
+
5
7
  page.body(
6
8
  childViews: ->(body) do
7
- render 'json_ui/garage/test_page/header', view: body
8
9
 
9
10
  body.panels_responsive(
10
11
  padding: glib_json_padding_body,
@@ -2,9 +2,10 @@ json.title 'Test Page (Fields Stripe Token)'
2
2
 
3
3
  page = json_ui_page json
4
4
 
5
+ render 'json_ui/garage/test_page/header', json: json, page: page
6
+
5
7
  page.body(
6
8
  childViews: ->(body) do
7
- render 'json_ui/garage/test_page/header', view: body
8
9
 
9
10
  body.panels_responsive(
10
11
  padding: glib_json_padding_body,
@@ -0,0 +1,312 @@
1
+ json.title 'Test Page (Fields Text)'
2
+
3
+ page = json_ui_page json
4
+
5
+ render 'json_ui/garage/test_page/header', json: json, page: page
6
+
7
+ page.body(
8
+ childViews: ->(body) do
9
+
10
+ body.panels_responsive(
11
+ padding: glib_json_padding_body,
12
+ childViews: ->(res) do
13
+ res.h2 text: 'Fields Text'
14
+ res.spacer height: 8
15
+ res.label text: 'Family of single-line text inputs: fields_text, fields_email, fields_number, fields_url, and fields_password. All share the same base props; each variant sets a specific input type.'
16
+ res.spacer height: 12
17
+
18
+ res.panels_form(
19
+ url: json_ui_garage_url(path: 'forms/generic_post'),
20
+ method: 'post',
21
+ childViews: ->(form) do
22
+
23
+ # ── Overview ──────────────────────────────────────────────────────
24
+ form.h2 text: 'Overview'
25
+ form.spacer height: 8
26
+ form.label text: 'Choose the variant that matches the expected value type. The input type controls the mobile keyboard, browser autofill hints, and native validation on submit.'
27
+ form.spacer height: 12
28
+ form.hr width: 'matchParent'
29
+ form.spacer height: 12
30
+
31
+ # ── Basic example ─────────────────────────────────────────────────
32
+ form.h2 text: 'Basic example'
33
+ form.spacer height: 8
34
+ form.label text: 'One instance of each variant with a label and placeholder.'
35
+ form.spacer height: 8
36
+
37
+ form.fields_text(
38
+ id: 'text_main',
39
+ name: 'user[name]',
40
+ label: 'Name (fields_text)',
41
+ placeholder: 'Full name',
42
+ width: 'matchParent',
43
+ onChange: ->(action) do
44
+ action.logics_set targetId: 'text_status', data: { text: 'Status: changed' }
45
+ end
46
+ )
47
+ form.spacer height: 8
48
+ form.fields_email(
49
+ name: 'user[email]',
50
+ label: 'Email (fields_email)',
51
+ placeholder: 'you@example.com',
52
+ width: 'matchParent'
53
+ )
54
+ form.spacer height: 8
55
+ form.fields_number(
56
+ name: 'user[age]',
57
+ label: 'Age (fields_number)',
58
+ placeholder: '0',
59
+ width: 'matchParent',
60
+ value: 25
61
+ )
62
+ form.spacer height: 8
63
+ form.fields_url(
64
+ name: 'user[website]',
65
+ label: 'Website (fields_url)',
66
+ placeholder: 'https://example.com',
67
+ width: 'matchParent'
68
+ )
69
+ form.spacer height: 8
70
+ form.fields_password(
71
+ name: 'user[password]',
72
+ label: 'Password (fields_password)',
73
+ placeholder: 'Enter your password',
74
+ width: 'matchParent'
75
+ )
76
+ form.spacer height: 8
77
+ form.label id: 'text_status', text: 'Status: idle'
78
+
79
+ form.spacer height: 12
80
+ form.hr width: 'matchParent'
81
+ form.spacer height: 12
82
+
83
+ # ── Variants/props ────────────────────────────────────────────────
84
+ form.h2 text: 'Variants/props'
85
+ form.spacer height: 8
86
+
87
+ form.h4 text: 'fields_text — styleClasses'
88
+ form.spacer height: 8
89
+ form.label text: 'outlined and outlined + rounded.'
90
+ form.spacer height: 8
91
+ form.fields_text(
92
+ name: 'user[text_outlined]',
93
+ label: 'Outlined',
94
+ placeholder: 'outlined',
95
+ width: 'matchParent',
96
+ styleClasses: ['outlined']
97
+ )
98
+ form.spacer height: 8
99
+ form.fields_text(
100
+ name: 'user[text_rounded]',
101
+ label: 'Outlined + rounded',
102
+ placeholder: 'outlined rounded',
103
+ width: 'matchParent',
104
+ styleClasses: ['outlined', 'rounded']
105
+ )
106
+
107
+ form.spacer height: 16
108
+
109
+ form.h4 text: 'fields_number — min / max / precision'
110
+ form.spacer height: 8
111
+ form.fields_number(
112
+ name: 'user[percentage]',
113
+ label: 'Percentage (0–100)',
114
+ width: 'matchParent',
115
+ value: 50,
116
+ min: 0,
117
+ max: 100
118
+ )
119
+ form.spacer height: 8
120
+ form.fields_number(
121
+ name: 'user[price]',
122
+ label: 'Price (2 decimal places)',
123
+ placeholder: '0.00',
124
+ width: 'matchParent',
125
+ value: 9.99,
126
+ min: 0,
127
+ precision: 2
128
+ )
129
+
130
+ form.spacer height: 16
131
+
132
+ form.h4 text: 'fields_password — outlined + compact'
133
+ form.spacer height: 8
134
+ form.fields_password(
135
+ name: 'user[password_outlined]',
136
+ label: 'Password (outlined compact)',
137
+ placeholder: 'Type your password',
138
+ width: 'matchParent',
139
+ styleClasses: ['outlined', 'compact']
140
+ )
141
+
142
+ form.spacer height: 16
143
+
144
+ form.h4 text: 'Disabled state (fields_text)'
145
+ form.spacer height: 8
146
+ form.fields_text(
147
+ id: 'text_disabled',
148
+ name: 'user[text_disabled]',
149
+ label: 'Disabled field',
150
+ width: 'matchParent',
151
+ value: 'Cannot edit',
152
+ disabled: true
153
+ )
154
+
155
+ form.spacer height: 12
156
+ form.hr width: 'matchParent'
157
+ form.spacer height: 12
158
+
159
+ # ── Actions/events ────────────────────────────────────────────────
160
+ form.h2 text: 'Actions/events'
161
+ form.spacer height: 8
162
+ form.label text: 'Buttons target the basic fields_text field (id: text_main) via components_set and fields_focus.'
163
+ form.spacer height: 8
164
+ form.panels_flow(
165
+ innerPadding: { bottom: 0 },
166
+ width: 'matchParent',
167
+ childViews: ->(flow) do
168
+ flow.button(
169
+ text: 'Set value',
170
+ onClick: ->(action) do
171
+ action.runMultiple(
172
+ childActions: ->(multi) do
173
+ multi.components_set targetId: 'text_main', data: { value: 'Hello world' }
174
+ multi.logics_set targetId: 'text_status', data: { text: 'Status: value set' }
175
+ end
176
+ )
177
+ end
178
+ )
179
+ flow.spacer width: 8
180
+ flow.button(
181
+ text: 'Clear value',
182
+ onClick: ->(action) do
183
+ action.runMultiple(
184
+ childActions: ->(multi) do
185
+ multi.components_set targetId: 'text_main', data: { value: '' }
186
+ multi.logics_set targetId: 'text_status', data: { text: 'Status: cleared' }
187
+ end
188
+ )
189
+ end
190
+ )
191
+ flow.spacer width: 8
192
+ flow.button(
193
+ text: 'Focus',
194
+ onClick: ->(action) do
195
+ action.fields_focus targetId: 'text_main'
196
+ end
197
+ )
198
+ flow.spacer width: 8
199
+ flow.button(
200
+ text: 'Reset',
201
+ onClick: ->(action) do
202
+ action.runMultiple(
203
+ childActions: ->(multi) do
204
+ multi.fields_reset targetId: 'text_main'
205
+ multi.logics_set targetId: 'text_status', data: { text: 'Status: reset' }
206
+ end
207
+ )
208
+ end
209
+ )
210
+ end
211
+ )
212
+
213
+ form.spacer height: 16
214
+ form.hr width: 'matchParent'
215
+ form.spacer height: 16
216
+
217
+ # ── Edge/advanced ─────────────────────────────────────────────────
218
+ form.h2 text: 'Edge/advanced'
219
+ form.spacer height: 8
220
+
221
+ form.h4 text: 'Toggle disabled state'
222
+ form.spacer height: 8
223
+ form.panels_flow(
224
+ innerPadding: { bottom: 0 },
225
+ width: 'matchParent',
226
+ childViews: ->(flow) do
227
+ flow.button(
228
+ text: 'Disable',
229
+ onClick: ->(action) do
230
+ action.components_set targetId: 'text_disabled', data: { disabled: true }
231
+ end
232
+ )
233
+ flow.spacer width: 8
234
+ flow.button(
235
+ text: 'Enable',
236
+ onClick: ->(action) do
237
+ action.components_set targetId: 'text_disabled', data: { disabled: nil }
238
+ end
239
+ )
240
+ end
241
+ )
242
+
243
+ form.spacer height: 16
244
+
245
+ form.h4 text: 'fields_number — onChangeAndLoad derived value'
246
+ form.spacer height: 8
247
+ form.label text: 'The second field shows the first value divided by 3 (1 decimal place).'
248
+ form.spacer height: 8
249
+ form.fields_number(
250
+ name: 'user[base_value]',
251
+ label: 'Base value',
252
+ width: 'matchParent',
253
+ value: 12,
254
+ onChangeAndLoad: ->(action) do
255
+ action.logics_set(
256
+ targetId: 'number_derived',
257
+ conditionalData: { value: { '/': [{ 'var': 'user[base_value]' }, 3] } }
258
+ )
259
+ end
260
+ )
261
+ form.spacer height: 8
262
+ form.fields_number(
263
+ id: 'number_derived',
264
+ name: 'user[derived_value]',
265
+ label: 'Derived value (÷ 3)',
266
+ width: 'matchParent',
267
+ precision: 1,
268
+ disabled: true
269
+ )
270
+
271
+ form.spacer height: 16
272
+
273
+ form.h4 text: 'fields_password — onChangeAndLoad strength hint'
274
+ form.spacer height: 8
275
+ form.label text: 'Show a hint once the password reaches 8 characters.'
276
+ form.spacer height: 8
277
+ form.fields_password(
278
+ name: 'user[password_live]',
279
+ label: 'Password strength check',
280
+ placeholder: 'Type a password...',
281
+ width: 'matchParent',
282
+ onChangeAndLoad: ->(action) do
283
+ action.runMultiple(
284
+ childActions: ->(multi) do
285
+ multi.logics_set(
286
+ targetId: 'password_hint_strong',
287
+ conditionalData: {
288
+ displayed: { '>=': [{ 'strlen': { 'var': 'user[password_live]' } }, 8] }
289
+ }
290
+ )
291
+ multi.logics_set(
292
+ targetId: 'password_hint_weak',
293
+ conditionalData: {
294
+ displayed: { '<': [{ 'strlen': { 'var': 'user[password_live]' } }, 8] }
295
+ }
296
+ )
297
+ end
298
+ )
299
+ end
300
+ )
301
+ form.spacer height: 4
302
+ form.label id: 'password_hint_strong', text: 'Password length looks good', displayed: false
303
+ form.label id: 'password_hint_weak', text: 'Password must be at least 8 characters', displayed: true
304
+
305
+ form.spacer height: 16
306
+ form.fields_submit text: 'Submit'
307
+ end
308
+ )
309
+ end
310
+ )
311
+ end
312
+ )