formstrap 0.1.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 (180) hide show
  1. checksums.yaml +7 -0
  2. data/.DS_Store +0 -0
  3. data/.gitignore +33 -0
  4. data/CHANGELOG.md +1 -0
  5. data/CODE_OF_CONDUCT.md +84 -0
  6. data/Gemfile +28 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +118 -0
  9. data/Rakefile +10 -0
  10. data/app/assets/config/headmin_manifest.js +2 -0
  11. data/app/assets/images/avatar.jpg +0 -0
  12. data/app/assets/images/document.docx +0 -0
  13. data/app/assets/images/document.pdf +0 -0
  14. data/app/assets/images/image.jpg +0 -0
  15. data/app/assets/images/spreadsheet.xls +0 -0
  16. data/app/assets/images/video.mp4 +0 -0
  17. data/app/assets/javascripts/formstrap/config/i18n.js +11 -0
  18. data/app/assets/javascripts/formstrap/controllers/autocomplete_controller.js +318 -0
  19. data/app/assets/javascripts/formstrap/controllers/date_range_controller.js +38 -0
  20. data/app/assets/javascripts/formstrap/controllers/dropzone_controller.js +31 -0
  21. data/app/assets/javascripts/formstrap/controllers/file_preview_controller.js +244 -0
  22. data/app/assets/javascripts/formstrap/controllers/flatpickr_controller.js +35 -0
  23. data/app/assets/javascripts/formstrap/controllers/infinite_scroller_controller.js +28 -0
  24. data/app/assets/javascripts/formstrap/controllers/media_controller.js +252 -0
  25. data/app/assets/javascripts/formstrap/controllers/media_modal_controller.js +147 -0
  26. data/app/assets/javascripts/formstrap/controllers/redactorx_controller.js +40 -0
  27. data/app/assets/javascripts/formstrap/controllers/repeater_controller.js +148 -0
  28. data/app/assets/javascripts/formstrap/controllers/select_controller.js +49 -0
  29. data/app/assets/javascripts/formstrap/controllers/textarea_controller.js +48 -0
  30. data/app/assets/javascripts/formstrap/index.js +32 -0
  31. data/app/assets/javascripts/formstrap.js +11515 -0
  32. data/app/assets/stylesheets/formstrap/forms/autocomplete.scss +27 -0
  33. data/app/assets/stylesheets/formstrap/forms/file.scss +83 -0
  34. data/app/assets/stylesheets/formstrap/forms/media.scss +10 -0
  35. data/app/assets/stylesheets/formstrap/forms/repeater.scss +62 -0
  36. data/app/assets/stylesheets/formstrap/forms/search.scss +12 -0
  37. data/app/assets/stylesheets/formstrap/forms.scss +12 -0
  38. data/app/assets/stylesheets/formstrap/general.scss +18 -0
  39. data/app/assets/stylesheets/formstrap/media/index.scss +9 -0
  40. data/app/assets/stylesheets/formstrap/media.scss +1 -0
  41. data/app/assets/stylesheets/formstrap/utilities/buttons.scss +27 -0
  42. data/app/assets/stylesheets/formstrap/utilities/dropzone.scss +72 -0
  43. data/app/assets/stylesheets/formstrap/utilities.scss +2 -0
  44. data/app/assets/stylesheets/formstrap/vendor/flatpickr.css +903 -0
  45. data/app/assets/stylesheets/formstrap/vendor/tom-select-bootstrap.scss +535 -0
  46. data/app/assets/stylesheets/formstrap.css +1559 -0
  47. data/app/assets/stylesheets/formstrap.scss +11 -0
  48. data/app/controllers/concerns/formstrap/pagination.rb +27 -0
  49. data/app/controllers/formstrap/media_controller.rb +68 -0
  50. data/app/controllers/formstrap_controller.rb +2 -0
  51. data/app/models/concerns/formstrap/autocompletable.rb +36 -0
  52. data/app/models/concerns/formstrap/hintable.rb +22 -0
  53. data/app/models/concerns/formstrap/input_groupable.rb +21 -0
  54. data/app/models/concerns/formstrap/labelable.rb +31 -0
  55. data/app/models/concerns/formstrap/listable.rb +26 -0
  56. data/app/models/concerns/formstrap/placeholderable.rb +11 -0
  57. data/app/models/concerns/formstrap/validatable.rb +38 -0
  58. data/app/models/concerns/formstrap/wrappable.rb +19 -0
  59. data/app/models/formstrap/.DS_Store +0 -0
  60. data/app/models/formstrap/association_view.rb +100 -0
  61. data/app/models/formstrap/blocks_view.rb +43 -0
  62. data/app/models/formstrap/checkbox_view.rb +50 -0
  63. data/app/models/formstrap/color_view.rb +45 -0
  64. data/app/models/formstrap/date_range_view.rb +23 -0
  65. data/app/models/formstrap/date_view.rb +43 -0
  66. data/app/models/formstrap/datetime_range_view.rb +23 -0
  67. data/app/models/formstrap/datetime_view.rb +43 -0
  68. data/app/models/formstrap/email_view.rb +46 -0
  69. data/app/models/formstrap/file_view.rb +106 -0
  70. data/app/models/formstrap/flatpickr_range_view.rb +89 -0
  71. data/app/models/formstrap/flatpickr_view.rb +27 -0
  72. data/app/models/formstrap/hidden_view.rb +8 -0
  73. data/app/models/formstrap/hint_view.rb +4 -0
  74. data/app/models/formstrap/input_group_view.rb +17 -0
  75. data/app/models/formstrap/label_view.rb +22 -0
  76. data/app/models/formstrap/media_item_view.rb +41 -0
  77. data/app/models/formstrap/media_view.rb +143 -0
  78. data/app/models/formstrap/number_view.rb +47 -0
  79. data/app/models/formstrap/password_view.rb +42 -0
  80. data/app/models/formstrap/redactorx_view.rb +57 -0
  81. data/app/models/formstrap/search_view.rb +46 -0
  82. data/app/models/formstrap/select_view.rb +61 -0
  83. data/app/models/formstrap/switch_view.rb +21 -0
  84. data/app/models/formstrap/text_view.rb +46 -0
  85. data/app/models/formstrap/textarea_view.rb +47 -0
  86. data/app/models/formstrap/url_view.rb +46 -0
  87. data/app/models/formstrap/wrapper_view.rb +17 -0
  88. data/app/models/formstrap/wysiwyg_view.rb +15 -0
  89. data/app/models/view_model.rb +62 -0
  90. data/app/views/formstrap/_association.html.erb +30 -0
  91. data/app/views/formstrap/_autocomplete.html.erb +11 -0
  92. data/app/views/formstrap/_blocks.html.erb +45 -0
  93. data/app/views/formstrap/_checkbox.html.erb +34 -0
  94. data/app/views/formstrap/_color.html.erb +32 -0
  95. data/app/views/formstrap/_datalist.html.erb +3 -0
  96. data/app/views/formstrap/_date.html.erb +41 -0
  97. data/app/views/formstrap/_date_range.html.erb +40 -0
  98. data/app/views/formstrap/_datetime.html.erb +41 -0
  99. data/app/views/formstrap/_datetime_range.html.erb +40 -0
  100. data/app/views/formstrap/_email.html.erb +43 -0
  101. data/app/views/formstrap/_errors.html.erb +19 -0
  102. data/app/views/formstrap/_file.html.erb +94 -0
  103. data/app/views/formstrap/_flatpickr.html.erb +33 -0
  104. data/app/views/formstrap/_flatpickr_range.html.erb +40 -0
  105. data/app/views/formstrap/_hidden.html.erb +23 -0
  106. data/app/views/formstrap/_hint.html.erb +21 -0
  107. data/app/views/formstrap/_input_group.html.erb +21 -0
  108. data/app/views/formstrap/_label.html.erb +22 -0
  109. data/app/views/formstrap/_media.html.erb +60 -0
  110. data/app/views/formstrap/_number.html.erb +41 -0
  111. data/app/views/formstrap/_password.html.erb +39 -0
  112. data/app/views/formstrap/_redactorx.html.erb +31 -0
  113. data/app/views/formstrap/_repeater.html.erb +128 -0
  114. data/app/views/formstrap/_search.html.erb +43 -0
  115. data/app/views/formstrap/_select.html.erb +43 -0
  116. data/app/views/formstrap/_switch.html.erb +29 -0
  117. data/app/views/formstrap/_text.html.erb +42 -0
  118. data/app/views/formstrap/_textarea.html.erb +39 -0
  119. data/app/views/formstrap/_to_ary.html.erb +0 -0
  120. data/app/views/formstrap/_url.html.erb +43 -0
  121. data/app/views/formstrap/_validation.html.erb +18 -0
  122. data/app/views/formstrap/_wrapper.html.erb +8 -0
  123. data/app/views/formstrap/_wysiwyg.html.erb +28 -0
  124. data/app/views/formstrap/autocomplete/_item.html.erb +3 -0
  125. data/app/views/formstrap/autocomplete/_list.html.erb +3 -0
  126. data/app/views/formstrap/blocks/_modal.html.erb +20 -0
  127. data/app/views/formstrap/fields/_base.html.erb +25 -0
  128. data/app/views/formstrap/fields/_file.html.erb +17 -0
  129. data/app/views/formstrap/fields/_files.html.erb +17 -0
  130. data/app/views/formstrap/fields/_group.html.erb +52 -0
  131. data/app/views/formstrap/fields/_list.html.erb +31 -0
  132. data/app/views/formstrap/fields/_text.html.erb +17 -0
  133. data/app/views/formstrap/media/_item.html.erb +38 -0
  134. data/app/views/formstrap/media/_media_item_modal.html.erb +77 -0
  135. data/app/views/formstrap/media/_modal.html.erb +40 -0
  136. data/app/views/formstrap/media/_thumbnail.html.erb +20 -0
  137. data/app/views/formstrap/media/_validation.html.erb +10 -0
  138. data/app/views/formstrap/media/create.turbo_stream.erb +5 -0
  139. data/app/views/formstrap/media/index.html.erb +3 -0
  140. data/app/views/formstrap/media/index.turbo_stream.erb +11 -0
  141. data/app/views/formstrap/media/show.html.erb +9 -0
  142. data/app/views/formstrap/media/thumbnail.html.erb +3 -0
  143. data/app/views/formstrap/media/update.turbo_stream.erb +3 -0
  144. data/app/views/formstrap/pagination/_infinite.html.erb +7 -0
  145. data/app/views/formstrap/repeater/_row.html.erb +53 -0
  146. data/app/views/formstrap/shared/_notifications.html.erb +20 -0
  147. data/app/views/formstrap/shared/_popup.html.erb +32 -0
  148. data/app/views/formstrap/shared/_thumbnail.html.erb +35 -0
  149. data/bin/console +14 -0
  150. data/bin/setup +8 -0
  151. data/config/importmap.rb +2 -0
  152. data/config/locales/activerecord/en.yml +12 -0
  153. data/config/locales/activerecord/nl.yml +13 -0
  154. data/config/locales/defaults/en.yml +215 -0
  155. data/config/locales/defaults/nl.yml +213 -0
  156. data/config/locales/devise/en.yml +65 -0
  157. data/config/locales/devise/nl.yml +85 -0
  158. data/config/locales/en.yml +6 -0
  159. data/config/locales/formstrap/forms/en.yml +39 -0
  160. data/config/locales/formstrap/forms/nl.yml +39 -0
  161. data/config/locales/formstrap/media/en.yml +24 -0
  162. data/config/locales/formstrap/media/nl.yml +24 -0
  163. data/config/locales/formstrap/thumbnail/en.yml +4 -0
  164. data/config/locales/formstrap/thumbnail/nl.yml +4 -0
  165. data/config/locales/nl.yml +6 -0
  166. data/config/routes.rb +11 -0
  167. data/esbuild-css.js +25 -0
  168. data/esbuild-js.js +11 -0
  169. data/formstrap.gemspec +37 -0
  170. data/formstrap.iml +34 -0
  171. data/lib/formstrap/engine.rb +27 -0
  172. data/lib/formstrap/form_builder.rb +177 -0
  173. data/lib/formstrap/form_helper.rb +19 -0
  174. data/lib/formstrap/version.rb +3 -0
  175. data/lib/formstrap.rb +6 -0
  176. data/package.json +54 -0
  177. data/src/js/formstrap.js +1 -0
  178. data/src/scss/formstrap.scss +1 -0
  179. data/yarn.lock +1998 -0
  180. metadata +224 -0
@@ -0,0 +1,147 @@
1
+ /* global CustomEvent */
2
+ import { Controller } from '@hotwired/stimulus'
3
+
4
+ export default class extends Controller {
5
+ static get targets () {
6
+ return ['idCheckbox', 'item', 'form', 'selectButton', 'placeholder', 'count']
7
+ }
8
+
9
+ static get values () {
10
+ return { ids: Array }
11
+ }
12
+
13
+ connect () {
14
+ this.validate()
15
+ this.updateCount()
16
+ }
17
+
18
+ // Actions
19
+ select () {
20
+ this.dispatchSelectionEvent()
21
+ }
22
+
23
+ submitForm () {
24
+ this.hidePlaceholder()
25
+ this.triggerFormSubmission()
26
+ }
27
+
28
+ inputChange (event) {
29
+ this.handleIdsUpdate(event.target)
30
+ this.updateCount()
31
+ }
32
+
33
+ // Methods
34
+ hidePlaceholder () {
35
+ this.placeholderTarget.classList.add('d-none')
36
+ }
37
+
38
+ handleIdsUpdate (element) {
39
+ if (element.checked) {
40
+ const arr = this.idsValue
41
+ arr.push(element.value)
42
+ this.idsValue = arr
43
+ } else {
44
+ this.idsValue = this.idsValue.filter((value) => {
45
+ return element.value !== value
46
+ })
47
+ }
48
+ }
49
+
50
+ itemTargetConnected (element) {
51
+ this.updateItem(element.querySelector('input'))
52
+ }
53
+
54
+ updateItem (element) {
55
+ const arr = this.idsValue
56
+
57
+ if (arr.includes(element.value)) {
58
+ element.checked = true
59
+ } else {
60
+ element.checked = false
61
+ }
62
+ }
63
+
64
+ idsValueChanged () {
65
+ for (const item of this.itemTargets) {
66
+ this.updateItem(item.querySelector('input'))
67
+ }
68
+ this.validate()
69
+ }
70
+
71
+ dispatchSelectionEvent () {
72
+ document.dispatchEvent(
73
+ new CustomEvent(
74
+ 'mediaSelectionSubmitted',
75
+ {
76
+ detail: {
77
+ name: this.element.dataset.name,
78
+ items: this.renderItemsForEvent()
79
+ }
80
+ }
81
+ )
82
+ )
83
+ }
84
+
85
+ triggerFormSubmission () {
86
+ this.formTarget.requestSubmit()
87
+ }
88
+
89
+ renderItemsForEvent () {
90
+ return this.idsValue.map((item) => this.renderItemForEvent(item)).filter((i) => { return i !== undefined })
91
+ }
92
+
93
+ renderItemForEvent (item) {
94
+ const id = parseInt(item)
95
+ const blobId = `#blob_${id}`
96
+ const element = this.element.querySelector(blobId)
97
+
98
+ return {
99
+ blobId: id,
100
+ thumbnail: element ? element.querySelector('.h-thumbnail') : ''
101
+ }
102
+ }
103
+
104
+ selectedItems () {
105
+ return this.itemTargets.filter((item) => {
106
+ const checkbox = item.querySelector('input[type="checkbox"]')
107
+ return checkbox.checked
108
+ })
109
+ }
110
+
111
+ selectedItemsCount () {
112
+ return this.idsValue.length
113
+ }
114
+
115
+ minSelectedItems () {
116
+ return parseInt(this.element.dataset.min, 10) || 0
117
+ }
118
+
119
+ maxSelectedItems () {
120
+ return parseInt(this.element.dataset.max, 10) || Infinity
121
+ }
122
+
123
+ validate () {
124
+ if (this.isValid()) {
125
+ this.enableSelectButton()
126
+ } else {
127
+ this.disableSelectButton()
128
+ }
129
+ }
130
+
131
+ enableSelectButton () {
132
+ this.selectButtonTarget.removeAttribute('disabled')
133
+ }
134
+
135
+ disableSelectButton () {
136
+ this.selectButtonTarget.setAttribute('disabled', '')
137
+ }
138
+
139
+ isValid () {
140
+ const count = this.selectedItemsCount()
141
+ return count >= this.minSelectedItems() && count <= this.maxSelectedItems()
142
+ }
143
+
144
+ updateCount () {
145
+ this.countTarget.innerHTML = this.selectedItemsCount()
146
+ }
147
+ }
@@ -0,0 +1,40 @@
1
+ /* global RedactorX */
2
+ import { Controller } from '@hotwired/stimulus'
3
+
4
+ export default class extends Controller {
5
+ connect () {
6
+ this.initRedactor()
7
+ }
8
+
9
+ initRedactor () {
10
+ if (typeof RedactorX === 'undefined') {
11
+ console.error('RedactorX is a paid module and is not included in Headmin. Please purchase it and import it as a JS module')
12
+ return false
13
+ }
14
+
15
+ const defaultOptions = {
16
+ editor: {
17
+ minHeight: '57px'
18
+ },
19
+ subscribe: {
20
+ 'app.start': () => {
21
+ this.stylize()
22
+ }
23
+ }
24
+ }
25
+ const options = JSON.parse(this.element.getAttribute('data-redactor-options'))
26
+ RedactorX(this.element, { ...defaultOptions, ...options })
27
+ }
28
+
29
+ stylize () {
30
+ const container = this.element.nextElementSibling
31
+
32
+ // Copy textarea classes
33
+ const inputClasses = this.element.classList
34
+ container.classList.add(...inputClasses)
35
+
36
+ // Add top margin to input group
37
+ // const inputGroup = container.closest('.input-group')
38
+ // inputGroup.classList.add('mt-4')
39
+ }
40
+ }
@@ -0,0 +1,148 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+ import Sortable from 'sortablejs'
3
+
4
+ export default class extends Controller {
5
+ static get values () {
6
+ return {
7
+ id: String
8
+ }
9
+ }
10
+
11
+ static get targets () {
12
+ return ['repeater', 'footer', 'template', 'row', 'list', 'empty', 'addButton']
13
+ }
14
+
15
+ connect () {
16
+ Sortable.create(this.listTarget, {
17
+ animation: 150,
18
+ ghostClass: 'list-group-item-dark',
19
+ draggable: '.repeater-row',
20
+ handle: '.repeater-row-handle',
21
+ onEnd: () => {
22
+ this.resetIndices()
23
+ this.resetPositions()
24
+ }
25
+ })
26
+
27
+ this.toggleEmpty()
28
+ }
29
+
30
+ resetButtonIndices (event) {
31
+ const row = event.target.closest('.repeater-row')
32
+ const index = this.containsRow(row) ? row.dataset.rowIndex : ''
33
+ this.updatePopupButtonIndices(index)
34
+ }
35
+
36
+ containsRow (row) {
37
+ return this.rowTargets.includes(row)
38
+ }
39
+
40
+ updatePopupButtonIndices (index) {
41
+ const popup = document.querySelector(`[data-popup-target="popup"][data-popup-id="repeater-buttons-${this.idValue}"]`)
42
+ const buttons = popup.querySelectorAll('a')
43
+ buttons.forEach((button) => {
44
+ button.dataset.rowIndex = index
45
+ })
46
+ }
47
+
48
+ addRow (event) {
49
+ event.preventDefault()
50
+ const button = event.target
51
+ const templateName = button.dataset.templateName
52
+ const rowIndex = button.dataset.rowIndex
53
+
54
+ // Prepare html from template
55
+ const template = this.getTemplate(templateName)
56
+ const html = this.replaceIdsWithTimestamps(template)
57
+
58
+ // Fallback to last row if no index is set
59
+ if (rowIndex) {
60
+ // Insert new row after defined row
61
+ const row = this.rowTargets[rowIndex]
62
+ row.insertAdjacentHTML('afterend', html)
63
+ } else {
64
+ // Insert before footer
65
+ this.footerTarget.insertAdjacentHTML('beforebegin', html)
66
+ }
67
+
68
+ this.resetIndices()
69
+ this.resetPositions()
70
+ this.toggleEmpty()
71
+ }
72
+
73
+ removeRow (event) {
74
+ event.preventDefault()
75
+
76
+ const row = event.target.closest('.repeater-row')
77
+
78
+ if (row.dataset.newRecord === 'true') {
79
+ // New records are simply removed from the page
80
+ row.remove()
81
+ } else {
82
+ // Existing records are hidden and flagged for deletion
83
+ this.flagRowForDeletion(row)
84
+ row.remove()
85
+ }
86
+
87
+ this.resetIndices()
88
+ this.resetPositions()
89
+ this.toggleEmpty()
90
+ }
91
+
92
+ flagRowForDeletion (row) {
93
+ const destroyInput = row.querySelector('input[name*=\'_destroy\']')
94
+ const idInput = row.querySelector('input[name*=\'[id]\']')
95
+
96
+ // Update _destroy value
97
+ destroyInput.value = 1
98
+
99
+ // Move away from row
100
+ this.listTarget.parentNode.appendChild(destroyInput)
101
+ this.listTarget.parentNode.appendChild(idInput)
102
+ }
103
+
104
+ getTemplate (name) {
105
+ return this.templateTargets.filter((template) => {
106
+ return template.dataset.templateName === name
107
+ })[0]
108
+ }
109
+
110
+ replaceIdsWithTimestamps (template) {
111
+ const regex = new RegExp(template.dataset.templateIdRegex, 'g')
112
+ return template.innerHTML.replace(regex, new Date().getTime())
113
+ }
114
+
115
+ visibleRowsCount () {
116
+ return this.visibleRows().length
117
+ }
118
+
119
+ visibleRows () {
120
+ const rows = this.rowTargets
121
+ return rows.filter((row) => {
122
+ return row.querySelector('input[name*=\'_destroy\']').value !== '1'
123
+ })
124
+ }
125
+
126
+ toggleEmpty () {
127
+ if (this.visibleRowsCount() > 0) {
128
+ this.emptyTarget.classList.add('invisible')
129
+ } else {
130
+ this.emptyTarget.classList.remove('invisible')
131
+ }
132
+ }
133
+
134
+ resetPositions () {
135
+ this.visibleRows().forEach((row, index) => {
136
+ const positionInput = row.querySelector('input[name*=\'position\']')
137
+ if (positionInput) {
138
+ positionInput.value = index
139
+ }
140
+ })
141
+ }
142
+
143
+ resetIndices () {
144
+ this.visibleRows().forEach((row, index) => {
145
+ row.dataset.rowIndex = index
146
+ })
147
+ }
148
+ }
@@ -0,0 +1,49 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+ import TomSelect from 'tom-select'
3
+ import I18n from '../config/i18n'
4
+
5
+ export default class extends Controller {
6
+ connect () {
7
+ if (this.element.hasAttribute('multiple')) {
8
+ this.initTomSelect()
9
+ }
10
+ }
11
+
12
+ defaultOptions (locale) {
13
+ const defaultOptions = {
14
+ en: {
15
+ render: {
16
+ option_create: function (data, escape) {
17
+ return '<div class="create">Add <strong>' + escape(data.input) + '</strong>&hellip;</div>'
18
+ },
19
+ no_results: function (data, escape) {
20
+ return '<div class="no-results">No results found</div>'
21
+ }
22
+ }
23
+ },
24
+ nl: {
25
+ render: {
26
+ option_create: function (data, escape) {
27
+ return '<div class="create">Voeg <strong>' + escape(data.input) + '</strong> toe &hellip;</div>'
28
+ },
29
+ no_results: function (data, escape) {
30
+ return '<div class="no-results">Geen resultaten gevonden</div>'
31
+ }
32
+ }
33
+ }
34
+ }
35
+ return defaultOptions[locale]
36
+ }
37
+
38
+ hasTags () {
39
+ return this.element.dataset.tags === 'true'
40
+ }
41
+
42
+ initTomSelect () {
43
+ const defaultOptions = this.defaultOptions(I18n.locale)
44
+ const options = { create: this.hasTags() }
45
+
46
+ /* eslint-disable no-new */
47
+ new TomSelect(this.element, { ...defaultOptions, ...options })
48
+ }
49
+ }
@@ -0,0 +1,48 @@
1
+ /* global IntersectionObserver */
2
+ import { Controller } from '@hotwired/stimulus'
3
+
4
+ export default class extends Controller {
5
+ static get targets () {
6
+ return ['textarea', 'count']
7
+ }
8
+
9
+ connect () {
10
+ onVisible(this.textareaTarget, () => { this.update() })
11
+ }
12
+
13
+ update () {
14
+ this.resize()
15
+ this.updateCount()
16
+ }
17
+
18
+ resize () {
19
+ this.textareaTarget.style.height = 'auto'
20
+ this.textareaTarget.setAttribute('style', 'height:' + (this.textareaTarget.scrollHeight) + 'px;overflow-y:hidden;')
21
+ }
22
+
23
+ updateCount () {
24
+ if (this.textareaTarget.getAttribute('maxlength')) {
25
+ this.updateCountLength()
26
+ }
27
+ }
28
+
29
+ updateCountLength () {
30
+ const currentLength = this.textareaTarget.value.length
31
+ const maximumLength = this.textareaTarget.getAttribute('maxlength')
32
+
33
+ this.countTarget.textContent = `${currentLength}/${maximumLength}`
34
+ }
35
+ }
36
+
37
+ // Custom callback event that triggers when an element becomes visible.
38
+ // Solves the bug where textarea fields are not properly sized when they (or their parent) or hidden.
39
+ function onVisible (element, callback) {
40
+ new IntersectionObserver((entries, observer) => {
41
+ entries.forEach(entry => {
42
+ if (entry.intersectionRatio > 0) {
43
+ callback(element)
44
+ observer.disconnect()
45
+ }
46
+ })
47
+ }).observe(element)
48
+ }
@@ -0,0 +1,32 @@
1
+ /* global Stimulus */
2
+ import { Application } from '@hotwired/stimulus'
3
+ import AutocompleteController from './controllers/autocomplete_controller'
4
+ import DateRangeController from './controllers/date_range_controller'
5
+ import DropzoneController from './controllers/dropzone_controller'
6
+ import FilePreviewController from './controllers/file_preview_controller'
7
+ import FlatpickrController from './controllers/flatpickr_controller'
8
+ import InfiniteScrollerController from './controllers/infinite_scroller_controller'
9
+ import MediaController from './controllers/media_controller'
10
+ import MediaModalController from './controllers/media_modal_controller'
11
+ import RedactorxController from './controllers/redactorx_controller'
12
+ import RepeaterController from './controllers/repeater_controller'
13
+ import SelectController from './controllers/select_controller'
14
+ import TextareaController from './controllers/textarea_controller'
15
+
16
+ export class Formstrap {
17
+ static start () {
18
+ window.Stimulus = window.Stimulus || Application.start()
19
+ Stimulus.register('autocomplete', AutocompleteController)
20
+ Stimulus.register('date-range', DateRangeController)
21
+ Stimulus.register('dropzone', DropzoneController)
22
+ Stimulus.register('file-preview', FilePreviewController)
23
+ Stimulus.register('flatpickr', FlatpickrController)
24
+ Stimulus.register('infinite-scroller', InfiniteScrollerController)
25
+ Stimulus.register('media', MediaController)
26
+ Stimulus.register('media-modal', MediaModalController)
27
+ Stimulus.register('redactorx', RedactorxController)
28
+ Stimulus.register('repeater', RepeaterController)
29
+ Stimulus.register('select', SelectController)
30
+ Stimulus.register('textarea', TextareaController)
31
+ }
32
+ }