country_state_select 3.2.0 → 4.0.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/.github/workflows/ci.yml +41 -0
  3. data/.gitignore +2 -0
  4. data/.rspec +1 -0
  5. data/CHANGELOG.md +56 -0
  6. data/README.md +196 -111
  7. data/Rakefile +26 -0
  8. data/app/controllers/country_state_select/cscs_controller.rb +31 -8
  9. data/app/javascript/country_state_select/controller.js +66 -0
  10. data/app/javascript/country_state_select/country_state_select.js +245 -0
  11. data/config/importmap.rb +4 -0
  12. data/config/routes.rb +14 -4
  13. data/country_state_select.gemspec +14 -4
  14. data/docs/cities.md +35 -0
  15. data/docs/images/cascading-select-demo.png +0 -0
  16. data/docs/json-api.md +41 -0
  17. data/docs/migration-v4.md +32 -0
  18. data/gemfiles/rails_7.0.gemfile +10 -0
  19. data/gemfiles/rails_7.1.gemfile +7 -0
  20. data/gemfiles/rails_7.2.gemfile +7 -0
  21. data/gemfiles/rails_8.0.gemfile +7 -0
  22. data/lib/country_state_select/configuration.rb +89 -0
  23. data/lib/country_state_select/country_metadata.rb +29 -0
  24. data/lib/country_state_select/data/dial_codes.yml +250 -0
  25. data/lib/country_state_select/data_sources/base.rb +27 -0
  26. data/lib/country_state_select/data_sources/city_state.rb +28 -0
  27. data/lib/country_state_select/engine.rb +31 -0
  28. data/lib/country_state_select/form_builder.rb +48 -0
  29. data/lib/country_state_select/localization.rb +41 -0
  30. data/lib/country_state_select/version.rb +1 -1
  31. data/lib/country_state_select.rb +83 -23
  32. data/lib/generators/country_state_select/install/install_generator.rb +22 -0
  33. data/lib/generators/country_state_select/install/templates/README +34 -0
  34. data/lib/generators/country_state_select/install/templates/initializer.rb +29 -0
  35. data/package.json +40 -0
  36. data/spec/country_state_select_spec.rb +2 -2
  37. data/spec/lib/country_state_select/configuration_spec.rb +63 -0
  38. data/spec/lib/country_state_select/country_metadata_spec.rb +42 -0
  39. data/spec/lib/country_state_select/filtering_spec.rb +68 -0
  40. data/spec/lib/country_state_select/localization_spec.rb +30 -0
  41. data/spec/requests/business_form_spec.rb +38 -0
  42. data/spec/requests/lookups_spec.rb +72 -0
  43. data/spec/spec_helper.rb +12 -4
  44. data/vendor/assets/javascript/country_state_select.js.erb +35 -21
  45. data/vendor/assets/javascript/country_state_select_vanilla.js +252 -0
  46. metadata +147 -17
  47. data/.circleci/config.yml +0 -15
  48. data/.travis.yml +0 -14
  49. data/lib/country_state_select/engine3.rb +0 -10
  50. data/lib/country_state_select/railtie.rb +0 -10
@@ -0,0 +1,252 @@
1
+ // GENERATED FILE — do not edit directly.
2
+ // Source: app/javascript/country_state_select/country_state_select.js
3
+ // Regenerate with `rake js:build` after editing the source.
4
+ (function (global) {
5
+ // Dependency-free core: no jQuery required. Works as an ES module (npm,
6
+ // importmap-rails, esbuild/webpack) and is also compiled by `rake js:build`
7
+ // into a plain-script build for Sprockets users — see
8
+ // vendor/assets/javascript/country_state_select_vanilla.js. Keep this file
9
+ // as the single source of truth; edit the compiled build only by re-running
10
+ // `rake js:build`.
11
+ //
12
+ // The legacy jQuery + Chosen implementation
13
+ // (vendor/assets/javascript/country_state_select.js.erb) is kept for
14
+ // backward compatibility but is deprecated — new apps should use this file,
15
+ // optionally through the bundled Stimulus controller (controller.js).
16
+
17
+ function CountryStateSelect(options) {
18
+ var countryId = options.country_id;
19
+ var stateId = options.state_id;
20
+ var cityId = options.city_id;
21
+ var hasCityField = !!cityId;
22
+ var announce = options.announce !== false;
23
+ var offlineData = options.offline_data || null;
24
+ var statesUrl = options.states_url || '/find_states';
25
+ var citiesUrl = options.cities_url || '/find_cities';
26
+ var enhancer = options.enhance; // optional fn(selectEl, tomSelectOptions) e.g. Tom Select hookup
27
+ var enhancerOptions = options.enhance_options || {};
28
+ var liveRegion = announce ? ensureLiveRegion() : null;
29
+
30
+ var enhancedInstances = {};
31
+
32
+ init();
33
+
34
+ return { refresh: init, destroy: destroy };
35
+
36
+ // ====== ***** INITIALIZATION ***** ===================================== //
37
+
38
+ function init() {
39
+ enhance(countryId);
40
+
41
+ var countryEl = document.getElementById(countryId);
42
+ if (!countryEl) return;
43
+
44
+ countryEl.addEventListener('change', function () {
45
+ findStates(countryEl.value, { userInitiated: true });
46
+ });
47
+
48
+ if (hasCityField) {
49
+ document.addEventListener('change', delegatedStateChange);
50
+ }
51
+
52
+ // Edit-form support: a server-rendered form (e.g. via the FormBuilder
53
+ // helpers, which read the model's current country/state to pick the
54
+ // right <select> options) already shows the correct state/city on
55
+ // load — trust it and only enhance it, rather than discarding it for
56
+ // a fresh, unselected fetch. Only fetch here for fields that truly
57
+ // need it: a plain/manual integration where the target is still a
58
+ // blank shell (a text input, or a <select> with no real options),
59
+ // even though the country already has a value. Pre-selection for that
60
+ // fetch comes from an explicit option or a `data-selected-value`/
61
+ // `data-selected-city` attribute.
62
+ var countryValue = countryEl.value;
63
+ var stateEl = document.getElementById(stateId);
64
+ var cityEl = hasCityField ? document.getElementById(cityId) : null;
65
+
66
+ if (isPopulated(stateEl)) {
67
+ enhance(stateId);
68
+ } else if (countryValue) {
69
+ var preselectedState = options.selected_state || (stateEl && stateEl.getAttribute('data-selected-value'));
70
+ findStates(countryValue, { preselect: preselectedState, silent: true });
71
+ return; // findStates cascades into the city fetch itself once it resolves
72
+ }
73
+
74
+ if (isPopulated(cityEl)) {
75
+ enhance(cityId);
76
+ } else if (hasCityField && stateEl && stateEl.value) {
77
+ var preselectedCity = options.selected_city || (cityEl && cityEl.getAttribute('data-selected-city'));
78
+ findCities(stateEl.value, countryValue, { preselect: preselectedCity, silent: true });
79
+ }
80
+ }
81
+
82
+ function isPopulated(el) {
83
+ return !!el && el.tagName === 'SELECT' && el.options.length > 1;
84
+ }
85
+
86
+ function destroy() {
87
+ document.removeEventListener('change', delegatedStateChange);
88
+ }
89
+
90
+ function delegatedStateChange(event) {
91
+ if (event.target && event.target.id === stateId) {
92
+ findCities(event.target.value, document.getElementById(countryId).value, { userInitiated: true });
93
+ }
94
+ }
95
+
96
+ // ====== ***** DATA LOOKUP ***** ========================================= //
97
+
98
+ function findStates(countryCode, ctx) {
99
+ ctx = ctx || {};
100
+ if (ctx.userInitiated) findCities('', '', { silent: true });
101
+
102
+ if (offlineData && offlineData.states) {
103
+ buildStatesDropdown(offlineData.states[countryCode] || [], ctx);
104
+ return;
105
+ }
106
+
107
+ fetchJson(statesUrl, { country_id: countryCode }).then(function (data) {
108
+ buildStatesDropdown(data || [], ctx);
109
+ });
110
+ }
111
+
112
+ function findCities(stateCode, countryCode, ctx) {
113
+ ctx = ctx || {};
114
+ if (!hasCityField) return;
115
+
116
+ if (offlineData && offlineData.cities) {
117
+ var key = countryCode + ':' + stateCode;
118
+ buildCitiesDropdown(offlineData.cities[key] || [], ctx);
119
+ return;
120
+ }
121
+
122
+ fetchJson(citiesUrl, { country_id: countryCode, state_id: stateCode }).then(function (data) {
123
+ buildCitiesDropdown(data || [], ctx);
124
+ });
125
+ }
126
+
127
+ function fetchJson(url, params) {
128
+ var query = Object.keys(params)
129
+ .map(function (k) { return encodeURIComponent(k) + '=' + encodeURIComponent(params[k] == null ? '' : params[k]); })
130
+ .join('&');
131
+
132
+ return fetch(url + '?' + query, {
133
+ headers: { Accept: 'application/json' },
134
+ credentials: 'same-origin'
135
+ }).then(function (response) {
136
+ if (!response.ok) return [];
137
+ return response.json();
138
+ }).catch(function () {
139
+ return [];
140
+ });
141
+ }
142
+
143
+ // ====== ***** DOM BUILDING ***** ======================================== //
144
+
145
+ function buildStatesDropdown(data, ctx) {
146
+ rebuildField(stateId, data, ctx.preselect, options.state_placeholder);
147
+ if (!ctx.silent) announceUpdate('State options updated');
148
+
149
+ var countryHasChanged = ctx.userInitiated;
150
+ if (hasCityField) {
151
+ if (countryHasChanged) {
152
+ findCities('', '', { silent: true });
153
+ } else if (ctx.preselect !== undefined) {
154
+ var stateEl = document.getElementById(stateId);
155
+ var preselectedCity = options.selected_city || (stateEl && stateEl.getAttribute('data-selected-city'));
156
+ findCities(stateEl ? stateEl.value : '', countryId && document.getElementById(countryId).value, {
157
+ preselect: preselectedCity,
158
+ silent: true
159
+ });
160
+ }
161
+ }
162
+ }
163
+
164
+ function buildCitiesDropdown(data, ctx) {
165
+ rebuildField(cityId, data.map(function (name) { return [name, name]; }), ctx.preselect, options.city_placeholder);
166
+ if (!ctx.silent) announceUpdate('City options updated');
167
+ }
168
+
169
+ // Rebuilds `fieldId` as a <select> (data present) or a plain text <input>
170
+ // (no data for the parent selection) while preserving its id/name/class —
171
+ // mutating the existing node in place rather than replaceWith() keeps
172
+ // external references (Tom Select instances, other listeners) valid.
173
+ function rebuildField(fieldId, pairs, preselectValue, placeholder) {
174
+ var el = document.getElementById(fieldId);
175
+ if (!el) return;
176
+
177
+ destroyEnhancement(fieldId);
178
+
179
+ if (!pairs || pairs.length === 0) {
180
+ var input = document.createElement('input');
181
+ input.type = 'text';
182
+ input.id = fieldId;
183
+ input.name = el.name;
184
+ input.className = el.className;
185
+ el.replaceWith(input);
186
+ return;
187
+ }
188
+
189
+ var select = document.createElement('select');
190
+ select.id = fieldId;
191
+ select.name = el.name;
192
+ select.className = el.className;
193
+
194
+ var blank = document.createElement('option');
195
+ blank.textContent = placeholder || '';
196
+ blank.value = '';
197
+ select.appendChild(blank);
198
+
199
+ pairs.forEach(function (pair) {
200
+ var opt = document.createElement('option');
201
+ opt.value = pair[0];
202
+ opt.textContent = pair[1];
203
+ if (preselectValue != null && String(pair[0]) === String(preselectValue)) {
204
+ opt.selected = true;
205
+ }
206
+ select.appendChild(opt);
207
+ });
208
+
209
+ el.replaceWith(select);
210
+ enhance(fieldId);
211
+ }
212
+
213
+ function enhance(fieldId) {
214
+ if (!fieldId || typeof enhancer !== 'function') return;
215
+ var el = document.getElementById(fieldId);
216
+ if (!el || el.tagName !== 'SELECT') return;
217
+
218
+ enhancedInstances[fieldId] = enhancer(el, enhancerOptions);
219
+ }
220
+
221
+ function destroyEnhancement(fieldId) {
222
+ var instance = enhancedInstances[fieldId];
223
+ if (instance && typeof instance.destroy === 'function') instance.destroy();
224
+ delete enhancedInstances[fieldId];
225
+ }
226
+
227
+ // ====== ***** ACCESSIBILITY ***** ======================================= //
228
+
229
+ function ensureLiveRegion() {
230
+ var existing = document.getElementById('country-state-select-live-region');
231
+ if (existing) return existing;
232
+
233
+ var region = document.createElement('div');
234
+ region.id = 'country-state-select-live-region';
235
+ region.setAttribute('aria-live', 'polite');
236
+ region.setAttribute('role', 'status');
237
+ region.style.position = 'absolute';
238
+ region.style.width = '1px';
239
+ region.style.height = '1px';
240
+ region.style.overflow = 'hidden';
241
+ region.style.clip = 'rect(0 0 0 0)';
242
+ document.body.appendChild(region);
243
+ return region;
244
+ }
245
+
246
+ function announceUpdate(message) {
247
+ if (liveRegion) liveRegion.textContent = message;
248
+ }
249
+ }
250
+
251
+ global.CountryStateSelect = CountryStateSelect;
252
+ })(this);
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: country_state_select
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.0
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arvind Vyas
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2023-01-08 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: bundler
@@ -24,22 +23,120 @@ dependencies:
24
23
  - - ">="
25
24
  - !ruby/object:Gem::Version
26
25
  version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: bundler-audit
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: capybara
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: importmap-rails
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: propshaft
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: puma
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
27
96
  - !ruby/object:Gem::Dependency
28
97
  name: rake
29
98
  requirement: !ruby/object:Gem::Requirement
30
99
  requirements:
31
100
  - - "~>"
32
101
  - !ruby/object:Gem::Version
33
- version: 12.3.3
102
+ version: '13.0'
34
103
  type: :development
35
104
  prerelease: false
36
105
  version_requirements: !ruby/object:Gem::Requirement
37
106
  requirements:
38
107
  - - "~>"
39
108
  - !ruby/object:Gem::Version
40
- version: 12.3.3
109
+ version: '13.0'
41
110
  - !ruby/object:Gem::Dependency
42
- name: rspec
111
+ name: rspec-rails
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ type: :development
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ - !ruby/object:Gem::Dependency
125
+ name: sqlite3
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ type: :development
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ - !ruby/object:Gem::Dependency
139
+ name: stimulus-rails
43
140
  requirement: !ruby/object:Gem::Requirement
44
141
  requirements:
45
142
  - - ">="
@@ -72,14 +169,14 @@ dependencies:
72
169
  requirements:
73
170
  - - ">="
74
171
  - !ruby/object:Gem::Version
75
- version: '0'
172
+ version: '7.0'
76
173
  type: :runtime
77
174
  prerelease: false
78
175
  version_requirements: !ruby/object:Gem::Requirement
79
176
  requirements:
80
177
  - - ">="
81
178
  - !ruby/object:Gem::Version
82
- version: '0'
179
+ version: '7.0'
83
180
  description: Country State Select is a Ruby Gem that aims to make Country and State/Province
84
181
  selection a cinch in Ruby on Rails environments.
85
182
  email:
@@ -88,29 +185,57 @@ executables: []
88
185
  extensions: []
89
186
  extra_rdoc_files: []
90
187
  files:
91
- - ".circleci/config.yml"
188
+ - ".github/workflows/ci.yml"
92
189
  - ".gitignore"
93
- - ".travis.yml"
190
+ - ".rspec"
191
+ - CHANGELOG.md
94
192
  - Gemfile
95
193
  - LICENSE.txt
96
194
  - README.md
97
195
  - Rakefile
98
196
  - app/controllers/country_state_select/cscs_controller.rb
197
+ - app/javascript/country_state_select/controller.js
198
+ - app/javascript/country_state_select/country_state_select.js
199
+ - config/importmap.rb
99
200
  - config/routes.rb
100
201
  - country_state_select.gemspec
202
+ - docs/cities.md
203
+ - docs/images/cascading-select-demo.png
204
+ - docs/json-api.md
205
+ - docs/migration-v4.md
206
+ - gemfiles/rails_7.0.gemfile
207
+ - gemfiles/rails_7.1.gemfile
208
+ - gemfiles/rails_7.2.gemfile
209
+ - gemfiles/rails_8.0.gemfile
101
210
  - lib/country_state_select.rb
211
+ - lib/country_state_select/configuration.rb
212
+ - lib/country_state_select/country_metadata.rb
213
+ - lib/country_state_select/data/dial_codes.yml
214
+ - lib/country_state_select/data_sources/base.rb
215
+ - lib/country_state_select/data_sources/city_state.rb
102
216
  - lib/country_state_select/engine.rb
103
- - lib/country_state_select/engine3.rb
104
- - lib/country_state_select/railtie.rb
217
+ - lib/country_state_select/form_builder.rb
218
+ - lib/country_state_select/localization.rb
105
219
  - lib/country_state_select/version.rb
220
+ - lib/generators/country_state_select/install/install_generator.rb
221
+ - lib/generators/country_state_select/install/templates/README
222
+ - lib/generators/country_state_select/install/templates/initializer.rb
223
+ - package.json
106
224
  - spec/country_state_select_spec.rb
225
+ - spec/lib/country_state_select/configuration_spec.rb
226
+ - spec/lib/country_state_select/country_metadata_spec.rb
227
+ - spec/lib/country_state_select/filtering_spec.rb
228
+ - spec/lib/country_state_select/localization_spec.rb
229
+ - spec/requests/business_form_spec.rb
230
+ - spec/requests/lookups_spec.rb
107
231
  - spec/spec_helper.rb
108
232
  - vendor/assets/javascript/country_state_select.js.erb
233
+ - vendor/assets/javascript/country_state_select_vanilla.js
109
234
  homepage: https://github.com/arvindvyas/Country-State-Select
110
235
  licenses:
111
236
  - MIT
112
- metadata: {}
113
- post_install_message:
237
+ metadata:
238
+ changelog_uri: https://github.com/arvindvyas/Country-State-Select/blob/master/CHANGELOG.md
114
239
  rdoc_options: []
115
240
  require_paths:
116
241
  - lib
@@ -118,17 +243,22 @@ required_ruby_version: !ruby/object:Gem::Requirement
118
243
  requirements:
119
244
  - - ">="
120
245
  - !ruby/object:Gem::Version
121
- version: '0'
246
+ version: '3.1'
122
247
  required_rubygems_version: !ruby/object:Gem::Requirement
123
248
  requirements:
124
249
  - - ">="
125
250
  - !ruby/object:Gem::Version
126
251
  version: '0'
127
252
  requirements: []
128
- rubygems_version: 3.0.3
129
- signing_key:
253
+ rubygems_version: 4.0.11
130
254
  specification_version: 4
131
255
  summary: Dynamically select Country and State.
132
256
  test_files:
133
257
  - spec/country_state_select_spec.rb
258
+ - spec/lib/country_state_select/configuration_spec.rb
259
+ - spec/lib/country_state_select/country_metadata_spec.rb
260
+ - spec/lib/country_state_select/filtering_spec.rb
261
+ - spec/lib/country_state_select/localization_spec.rb
262
+ - spec/requests/business_form_spec.rb
263
+ - spec/requests/lookups_spec.rb
134
264
  - spec/spec_helper.rb
data/.circleci/config.yml DELETED
@@ -1,15 +0,0 @@
1
- version: 2.1
2
- orbs:
3
- ruby: circleci/ruby@0.1.2
4
-
5
- jobs:
6
- build:
7
- docker:
8
- - image: circleci/ruby:2.6.3-stretch-node
9
- executor: ruby/default
10
- steps:
11
- - checkout
12
- - run:
13
- name: Which bundler?
14
- command: bundle -v
15
- - ruby/bundle-install
data/.travis.yml DELETED
@@ -1,14 +0,0 @@
1
- language: ruby
2
-
3
- rvm:
4
- - 2.0.0
5
- - 2.2.0
6
- - 2.5.0
7
- - 2.6.5
8
- - 3.0.0
9
-
10
- script:
11
- # ---if you written test cases then write commands here to run
12
-
13
- - bundle install
14
- - rspec
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # @author Sachin Singh
4
-
5
- module CountryStateSelect
6
- module Rails
7
- class Engine3 < ::Rails::Engine
8
- end
9
- end
10
- end
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # @author Sachin Singh
4
-
5
- module CountryStateSelect
6
- module Rails
7
- class Railtie < ::Rails::Railtie
8
- end
9
- end
10
- end