tam_select 1.2.0 → 1.2.4
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.
- checksums.yaml +4 -4
- data/README.md +107 -16
- data/Rakefile +5 -2
- data/lib/generators/tam_select/install_generator.rb +33 -1
- data/lib/generators/tam_select/templates/tam_select_controller.js +1 -1
- data/lib/generators/tam_select/templates/tam_select_helper.rb +1 -1
- data/lib/generators/tam_select/templates/tam_select_input.rb +1 -1
- data/lib/tam_select/version.rb +1 -1
- data/package.json +48 -0
- data/rails/app/controllers/concerns/tam_select_paginatable.rb +32 -0
- data/rails/app/helpers/tam_select_helper.rb +17 -0
- data/rails/app/inputs/tam_select_input.rb +30 -0
- data/rails/app/javascript/controllers/tam_select_controller.js +31 -0
- data/src/tam-select.js +550 -175
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d83036c60f51e481343b204bc7e6333c92cc36d6c2d1190656cd956b0e3c4301
|
|
4
|
+
data.tar.gz: 0576a3df23baad11bc818b4a2d9c05ccc035e7c7ae369b67606ef1c03c398aea
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0bd5a3e7eb8d90cb9d18b801a55943abc9da582a33a0616ceadbf8e1a27fcff5ea5df795bae552b4e64d97c3c6e7cb298069285afddae4499c64af1b1cd0579e
|
|
7
|
+
data.tar.gz: 8aed36fcd88674f2cb3d91794ca224f75f909f79d805ba6d67cc613dd9bcf624ee7e2358a39ddcefaf5ff2cd24b27e9583e21ca1bd63e3eaa4b1289c04b87eac
|
data/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Tam Select
|
|
2
2
|
|
|
3
3
|
[](LICENSE)
|
|
4
|
-
[](https://github.com/tamiru/tam_select)
|
|
5
5
|
|
|
6
6
|
**Tam Select** is an accessible, searchable select component for Ruby on Rails, built for Simple Form, Stimulus, Turbo, and Tailwind CSS. It keeps the native `<select>` as the source of truth, so Rails form submission, validation, selected values, and browser autofill continue to work.
|
|
7
7
|
|
|
@@ -29,20 +29,53 @@ bundle install
|
|
|
29
29
|
bin/rails generate tam_select:install
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
The generator installs:
|
|
32
|
+
The generator installs the core module, Stimulus controller, Rails helpers, and remote-search concerns:
|
|
33
33
|
|
|
34
34
|
```text
|
|
35
35
|
app/javascript/tam_select/tam_select.js
|
|
36
36
|
app/javascript/controllers/tam_select_controller.js
|
|
37
|
-
app/inputs/tam_select_input.rb
|
|
38
37
|
app/controllers/concerns/tam_select_paginatable.rb
|
|
39
38
|
app/controllers/concerns/tam_select_remote.rb
|
|
40
39
|
app/controllers/tam_select_remote_controller.rb
|
|
41
40
|
app/helpers/tam_select_helper.rb
|
|
42
41
|
```
|
|
43
42
|
|
|
43
|
+
When Simple Form is present, it also installs `app/inputs/tam_select_input.rb`. Simple Form is optional; applications without it receive a short skip message and continue with standard Rails form helpers.
|
|
44
|
+
|
|
44
45
|
Commit these generated files with the Rails application. This makes customization straightforward and allows Tailwind CSS 4 to scan the component without resolving a Ruby gem directory at build time.
|
|
45
46
|
|
|
47
|
+
### Rails 8 importmap
|
|
48
|
+
|
|
49
|
+
The default Rails 8 importmap setup needs no manual JavaScript package installation. The generator adds this pin to `config/importmap.rb`:
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
pin "tam_select", to: "tam_select/tam_select.js"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The generated Stimulus controller imports `TamSelect` from `"tam_select"`. Running the generator again updates an old pin when necessary and never adds a duplicate. Rails' normal `pin_all_from "app/javascript/controllers", under: "controllers"` line discovers the generated controller.
|
|
56
|
+
|
|
57
|
+
### jsbundling-rails and esbuild
|
|
58
|
+
|
|
59
|
+
The same generated controller works with esbuild when the bare `tam_select` import is aliased to the generated core file. Add the alias flag to the existing `build` script in `package.json`:
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"scripts": {
|
|
64
|
+
"build": "esbuild app/javascript/*.* --bundle --sourcemap --format=esm --outdir=app/assets/builds --public-path=/assets --alias:tam_select=./app/javascript/tam_select/tam_select.js"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
This uses the local file installed by the generator. For non-Rails bundler use, install the npm package from GitHub and import its published package name instead:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npm install github:tamiru/tam_select
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
import TamSelect from "tam-select"
|
|
77
|
+
```
|
|
78
|
+
|
|
46
79
|
Add the generated JavaScript to Tailwind's source detection in `app/assets/tailwind/application.css`:
|
|
47
80
|
|
|
48
81
|
```css
|
|
@@ -50,7 +83,7 @@ Add the generated JavaScript to Tailwind's source detection in `app/assets/tailw
|
|
|
50
83
|
@source "../../javascript/tam_select/**/*.js";
|
|
51
84
|
```
|
|
52
85
|
|
|
53
|
-
###
|
|
86
|
+
### Upgrading
|
|
54
87
|
|
|
55
88
|
The Rails integration files are copied into your application, so updating the gem does not update them automatically. Commit local customizations first, then run:
|
|
56
89
|
|
|
@@ -67,13 +100,15 @@ bin/rails generate tam_select:install --force
|
|
|
67
100
|
|
|
68
101
|
Review `git diff` afterward because `--force` overwrites application-specific customizations.
|
|
69
102
|
|
|
103
|
+
Versions before this release generated a relative controller import. After upgrading, confirm the controller uses `import TamSelect from "tam_select"` and that the importmap pin or esbuild alias above is present.
|
|
104
|
+
|
|
70
105
|
## Features
|
|
71
106
|
|
|
72
107
|
- Single and multiple selection
|
|
73
108
|
- Local search and user-created tags
|
|
74
109
|
- Remote JSON search with debouncing and incremental pagination
|
|
75
110
|
- Loading, empty, and error states
|
|
76
|
-
-
|
|
111
|
+
- Select2-style closed selection and open search states with clear active, selected, disabled, loading, empty, and error feedback
|
|
77
112
|
- Keyboard navigation: arrows, Enter, Escape, Tab, and Backspace
|
|
78
113
|
- Combobox/listbox ARIA semantics
|
|
79
114
|
- Light and dark Tailwind themes
|
|
@@ -81,14 +116,6 @@ Review `git diff` afterward because `--force` overwrites application-specific cu
|
|
|
81
116
|
- Public API and bubbling custom events
|
|
82
117
|
- No jQuery, Tom Select, Select2, Preline, or Floating UI dependency
|
|
83
118
|
|
|
84
|
-
## JavaScript installation
|
|
85
|
-
|
|
86
|
-
For a non-Rails application, install directly from GitHub:
|
|
87
|
-
|
|
88
|
-
```bash
|
|
89
|
-
npm install github:tamiru/tam_select
|
|
90
|
-
```
|
|
91
|
-
|
|
92
119
|
## Tailwind CSS 4
|
|
93
120
|
|
|
94
121
|
Add the package source to `app/assets/tailwind/application.css` so Tailwind generates every class used by the JavaScript templates:
|
|
@@ -117,7 +144,7 @@ For Tailwind CSS 4 class-based theming, define the variant in the application st
|
|
|
117
144
|
@custom-variant dark (&:where(.dark, .dark *));
|
|
118
145
|
```
|
|
119
146
|
|
|
120
|
-
## Rails and Stimulus
|
|
147
|
+
## Standard Rails forms and Stimulus
|
|
121
148
|
|
|
122
149
|
The install generator copies the Stimulus controller into your application, where Stimulus normally discovers it automatically. If controllers are registered manually:
|
|
123
150
|
|
|
@@ -143,9 +170,28 @@ Use a normal Rails select:
|
|
|
143
170
|
|
|
144
171
|
Stimulus destroys generated markup when Turbo removes the select and recreates it on reconnection.
|
|
145
172
|
|
|
173
|
+
For multiple selection, Rails must receive an array field and the native select must include `multiple: true`:
|
|
174
|
+
|
|
175
|
+
```erb
|
|
176
|
+
<%= form.select :skill_ids,
|
|
177
|
+
options_from_collection_for_select(Skill.order(:name), :id, :name, form.object.skill_ids),
|
|
178
|
+
{},
|
|
179
|
+
multiple: true,
|
|
180
|
+
data: {
|
|
181
|
+
controller: "tam-select",
|
|
182
|
+
tam_select_options_value: {
|
|
183
|
+
searchable: true,
|
|
184
|
+
closeAfterSelect: false,
|
|
185
|
+
placeholder: "Add skills…"
|
|
186
|
+
}.to_json
|
|
187
|
+
} %>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
The original `<select>` remains the source of truth. This preserves form names in nested forms, submitted values, prompts, `required`, validation metadata, and native `change` events.
|
|
191
|
+
|
|
146
192
|
## Simple Form
|
|
147
193
|
|
|
148
|
-
|
|
194
|
+
When the `simple_form` gem is installed, the generator creates `app/inputs/tam_select_input.rb`. Use `as: :tam_select` with any Simple Form collection input. The input forwards Simple Form's prompt, selected values, multiple flag, error classes, ARIA attributes, and nested builder context to the native collection select.
|
|
149
195
|
|
|
150
196
|
### Local collection
|
|
151
197
|
|
|
@@ -270,6 +316,8 @@ Point Simple Form to that collection action:
|
|
|
270
316
|
|
|
271
317
|
Typing sends `GET /regions/tam_select_options.json?q=addis&page=1` and receives the standard Tam Select JSON payload.
|
|
272
318
|
|
|
319
|
+
When `pagination.has_more` is true, scrolling near the bottom requests `next_page`. Earlier pages keep their order, duplicate values are collapsed, and a later response can update an existing item's label, metadata, image, or disabled state. A new query aborts the old request and never displays cached results from the previous query.
|
|
320
|
+
|
|
273
321
|
Remote items may include optional `detail`, `meta`, and `image` fields. Tam Select renders the primary label with the detail on a second line and an optional circular image on the left. The selected value keeps the same image, label, and detail, including an initial value rendered before remote search completes. `meta` remains a badge on the right.
|
|
274
322
|
|
|
275
323
|
This works well for program and student searches. For example, return the program name as `label` and admission as `detail`:
|
|
@@ -319,10 +367,42 @@ curl -H "Accept: application/json" \
|
|
|
319
367
|
|
|
320
368
|
A `406 Not Acceptable` response means the route or controller rejected JSON. Keep `defaults: { format: :json }` on the route, use a `.json` URL, and ensure the controller does not restrict responses to HTML only.
|
|
321
369
|
|
|
370
|
+
## Creatable values
|
|
371
|
+
|
|
372
|
+
Set `creatable: true` for tag-style input. The Create action is part of the same keyboard list as normal results, so ArrowUp and ArrowDown can highlight it and Enter creates it.
|
|
373
|
+
|
|
374
|
+
```erb
|
|
375
|
+
<%= form.select :category_ids,
|
|
376
|
+
options_from_collection_for_select(Category.order(:name), :id, :name),
|
|
377
|
+
{},
|
|
378
|
+
multiple: true,
|
|
379
|
+
data: {
|
|
380
|
+
controller: "tam-select",
|
|
381
|
+
tam_select_options_value: {
|
|
382
|
+
creatable: true,
|
|
383
|
+
closeAfterSelect: false,
|
|
384
|
+
placeholder: "Add categories…"
|
|
385
|
+
}.to_json
|
|
386
|
+
} %>
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
Values and labels are compared with normalized, case-insensitive, accent-insensitive text before creation, preventing duplicate entries such as `Café` and `cafe`. A successful creation dispatches `tam-select:create`.
|
|
390
|
+
|
|
391
|
+
## Keyboard and accessibility behavior
|
|
392
|
+
|
|
393
|
+
- ArrowDown and ArrowUp open the list, move through the rendered entries, and skip disabled options.
|
|
394
|
+
- Enter selects the highlighted option or activates the highlighted Create action.
|
|
395
|
+
- Escape closes the list and returns focus to the combobox; Tab closes without trapping focus.
|
|
396
|
+
- Backspace removes the last tag from a multiple select when the search input is empty.
|
|
397
|
+
- Searchable controls use an input combobox; non-searchable controls use a focusable button combobox.
|
|
398
|
+
- Rails labels, descriptions, required state, disabled state, and invalid state are mirrored from the native select.
|
|
399
|
+
|
|
322
400
|
## Core JavaScript
|
|
323
401
|
|
|
402
|
+
For a generated Rails/importmap installation, use the pinned module name:
|
|
403
|
+
|
|
324
404
|
```js
|
|
325
|
-
import TamSelect from "
|
|
405
|
+
import TamSelect from "tam_select"
|
|
326
406
|
|
|
327
407
|
const instance = new TamSelect(document.querySelector("#student_region_id"), {
|
|
328
408
|
searchable: true,
|
|
@@ -332,11 +412,15 @@ const instance = new TamSelect(document.querySelector("#student_region_id"), {
|
|
|
332
412
|
})
|
|
333
413
|
|
|
334
414
|
instance.setValue("2")
|
|
415
|
+
instance.open()
|
|
416
|
+
instance.close()
|
|
335
417
|
instance.clear()
|
|
336
418
|
instance.refresh()
|
|
337
419
|
instance.destroy()
|
|
338
420
|
```
|
|
339
421
|
|
|
422
|
+
When consuming the npm package directly, import from `"tam-select"` instead.
|
|
423
|
+
|
|
340
424
|
## Main options
|
|
341
425
|
|
|
342
426
|
| Option | Default | Purpose |
|
|
@@ -345,6 +429,8 @@ instance.destroy()
|
|
|
345
429
|
| `creatable` | `false` | Allows typed values to become options |
|
|
346
430
|
| `clearable` | `true` | Displays the clear control |
|
|
347
431
|
| `closeAfterSelect` | Single only | Keeps multiple dropdowns open |
|
|
432
|
+
| `placeholder` | Native prompt or `Select…` | Closed-control placeholder |
|
|
433
|
+
| `searchPlaceholder` | `Search…` | Search-input placeholder |
|
|
348
434
|
| `remoteUrl` | `null` | JSON search endpoint |
|
|
349
435
|
| `queryParam` | `q` | Remote search parameter |
|
|
350
436
|
| `pageParam` | `page` | Remote page parameter |
|
|
@@ -353,6 +439,7 @@ instance.destroy()
|
|
|
353
439
|
| `valueField` | `value` | Remote item value key |
|
|
354
440
|
| `labelField` | `label` | Remote item label key |
|
|
355
441
|
| `imageField` | `image` | Remote item image URL key |
|
|
442
|
+
| `matcher` | `null` | Optional `(item, query) => boolean` local matcher |
|
|
356
443
|
| `classes` | `{}` | Overrides any Tailwind class group |
|
|
357
444
|
|
|
358
445
|
## Events
|
|
@@ -361,6 +448,8 @@ Listen on the original select. Every event bubbles:
|
|
|
361
448
|
|
|
362
449
|
```js
|
|
363
450
|
select.addEventListener("tam-select:change", ({ detail }) => console.log(detail.value))
|
|
451
|
+
select.addEventListener("tam-select:open", () => console.log("opened"))
|
|
452
|
+
select.addEventListener("tam-select:close", () => console.log("closed"))
|
|
364
453
|
select.addEventListener("tam-select:load", ({ detail }) => console.log(detail.items))
|
|
365
454
|
select.addEventListener("tam-select:create", ({ detail }) => console.log(detail.item))
|
|
366
455
|
select.addEventListener("tam-select:error", ({ detail }) => console.error(detail.error))
|
|
@@ -368,6 +457,8 @@ select.addEventListener("tam-select:error", ({ detail }) => console.error(detail
|
|
|
368
457
|
|
|
369
458
|
Standard native `change` events are also dispatched for Rails and other controllers.
|
|
370
459
|
|
|
460
|
+
The supported public methods are `open()`, `close()`, `setValue(value)`, `clear()`, `refresh()`, and `destroy()`. Read the current native-compatible selection through `instance.value`, and recover an existing instance with `TamSelect.getInstance(select)`.
|
|
461
|
+
|
|
371
462
|
## Development
|
|
372
463
|
|
|
373
464
|
```bash
|
data/Rakefile
CHANGED
|
@@ -2,7 +2,10 @@ require "bundler/gem_tasks"
|
|
|
2
2
|
|
|
3
3
|
begin
|
|
4
4
|
require "rake/testtask"
|
|
5
|
-
Rake::TestTask.new
|
|
5
|
+
Rake::TestTask.new do |test|
|
|
6
|
+
test.libs << "test"
|
|
7
|
+
test.pattern = "test/**/*_test.rb"
|
|
8
|
+
end
|
|
6
9
|
rescue LoadError
|
|
7
|
-
#
|
|
10
|
+
# Rake's test task is unavailable.
|
|
8
11
|
end
|
|
@@ -12,8 +12,30 @@ module TamSelect
|
|
|
12
12
|
copy_file "lib/generators/tam_select/templates/tam_select_controller.js", "app/javascript/controllers/tam_select_controller.js"
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
+
def configure_importmap
|
|
16
|
+
importmap = "config/importmap.rb"
|
|
17
|
+
unless File.exist?(destination_path(importmap))
|
|
18
|
+
say "Importmap was not detected. For jsbundling/esbuild, alias tam_select to app/javascript/tam_select/tam_select.js.", :yellow
|
|
19
|
+
return
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
pin = 'pin "tam_select", to: "tam_select/tam_select.js"'
|
|
23
|
+
contents = File.read(destination_path(importmap))
|
|
24
|
+
existing_pin = /^\s*pin\s+["']tam_select["'].*$/
|
|
25
|
+
|
|
26
|
+
if contents.match?(existing_pin)
|
|
27
|
+
gsub_file importmap, existing_pin, pin unless contents.match?(/^#{Regexp.escape(pin)}$/)
|
|
28
|
+
else
|
|
29
|
+
append_to_file importmap, "\n#{pin}\n"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
15
33
|
def copy_simple_form_input
|
|
16
|
-
|
|
34
|
+
if simple_form_available?
|
|
35
|
+
copy_file "lib/generators/tam_select/templates/tam_select_input.rb", "app/inputs/tam_select_input.rb"
|
|
36
|
+
else
|
|
37
|
+
say "Simple Form is not installed; skipped app/inputs/tam_select_input.rb. Standard Rails form helpers remain available.", :yellow
|
|
38
|
+
end
|
|
17
39
|
end
|
|
18
40
|
|
|
19
41
|
def copy_controller_concern
|
|
@@ -35,6 +57,16 @@ module TamSelect
|
|
|
35
57
|
say "If your setup uses explicit sources, add:"
|
|
36
58
|
say '@source "../../javascript/tam_select/**/*.js";'
|
|
37
59
|
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def destination_path(relative_path)
|
|
64
|
+
File.join(destination_root, relative_path)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def simple_form_available?
|
|
68
|
+
Gem.loaded_specs.key?("simple_form")
|
|
69
|
+
end
|
|
38
70
|
end
|
|
39
71
|
end
|
|
40
72
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module TamSelectHelper
|
|
2
2
|
def tam_select_tag(name, option_tags = nil, options: {}, html_options: {}, &block)
|
|
3
3
|
data = (html_options[:data] ||= {})
|
|
4
|
-
data[:controller] =
|
|
4
|
+
data[:controller] = (data[:controller].to_s.split + ["tam-select"]).uniq.join(" ")
|
|
5
5
|
data[:tam_select_options_value] = options.to_json
|
|
6
6
|
select_tag(name, option_tags, html_options, &block)
|
|
7
7
|
end
|
|
@@ -4,7 +4,7 @@ class TamSelectInput < SimpleForm::Inputs::CollectionSelectInput
|
|
|
4
4
|
label_method, value_method = detect_collection_methods
|
|
5
5
|
tam_options = attributes.delete(:tam_options) || {}
|
|
6
6
|
data = attributes[:data] ||= {}
|
|
7
|
-
data[:controller] =
|
|
7
|
+
data[:controller] = (data[:controller].to_s.split + ["tam-select"]).uniq.join(" ")
|
|
8
8
|
data[:tam_select_options_value] = defaults.merge(tam_options).to_json
|
|
9
9
|
|
|
10
10
|
@builder.collection_select(
|
data/lib/tam_select/version.rb
CHANGED
data/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tam-select",
|
|
3
|
+
"version": "1.2.3",
|
|
4
|
+
"description": "Accessible, searchable select component for Ruby on Rails, Simple Form, Stimulus, Turbo, and Tailwind CSS",
|
|
5
|
+
"homepage": "https://github.com/tamiru/tam_select",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/tamiru/tam_select.git"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"main": "./src/tam-select.js",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": "./src/tam-select.js",
|
|
14
|
+
"./rails-controller": "./rails/app/javascript/controllers/tam_select_controller.js"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"src",
|
|
18
|
+
"rails",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"test": "node --test test/*.test.js",
|
|
24
|
+
"check": "node --check src/tam-select.js && node --check rails/app/javascript/controllers/tam_select_controller.js"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"select",
|
|
28
|
+
"tailwindcss",
|
|
29
|
+
"stimulus",
|
|
30
|
+
"turbo",
|
|
31
|
+
"rails",
|
|
32
|
+
"combobox"
|
|
33
|
+
],
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@hotwired/stimulus": ">=3.2.0"
|
|
37
|
+
},
|
|
38
|
+
"peerDependenciesMeta": {
|
|
39
|
+
"@hotwired/stimulus": {
|
|
40
|
+
"optional": true
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@hotwired/stimulus": "^3.2.2",
|
|
45
|
+
"esbuild": "^0.28.1",
|
|
46
|
+
"jsdom": "^29.1.1"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module TamSelectPaginatable
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
private
|
|
5
|
+
|
|
6
|
+
def tam_select_payload(scope, label:, value: :id, detail: nil, meta: nil, image: nil, per_page: 20)
|
|
7
|
+
page = [params.fetch(:page, 1).to_i, 1].max
|
|
8
|
+
records = scope.limit(per_page + 1).offset((page - 1) * per_page).to_a
|
|
9
|
+
has_more = records.length > per_page
|
|
10
|
+
|
|
11
|
+
{
|
|
12
|
+
items: records.first(per_page).map do |record|
|
|
13
|
+
{
|
|
14
|
+
value: tam_select_record_value(record, value).to_s,
|
|
15
|
+
label: tam_select_record_value(record, label).to_s,
|
|
16
|
+
detail: detail && tam_select_record_value(record, detail).to_s,
|
|
17
|
+
meta: meta && tam_select_record_value(record, meta).to_s,
|
|
18
|
+
image: image && tam_select_record_value(record, image).to_s
|
|
19
|
+
}.compact
|
|
20
|
+
end,
|
|
21
|
+
pagination: {
|
|
22
|
+
page: page,
|
|
23
|
+
next_page: has_more ? page + 1 : nil,
|
|
24
|
+
has_more: has_more
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def tam_select_record_value(record, resolver)
|
|
30
|
+
resolver.respond_to?(:call) ? resolver.call(record) : record.public_send(resolver)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module TamSelectHelper
|
|
2
|
+
def tam_select_tag(name, option_tags = nil, options: {}, html_options: {}, &block)
|
|
3
|
+
data = (html_options[:data] ||= {})
|
|
4
|
+
data[:controller] = (data[:controller].to_s.split + ["tam-select"]).uniq.join(" ")
|
|
5
|
+
data[:tam_select_options_value] = options.to_json
|
|
6
|
+
select_tag(name, option_tags, html_options, &block)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def tam_select_options(**overrides)
|
|
10
|
+
{
|
|
11
|
+
searchable: true,
|
|
12
|
+
clearable: true,
|
|
13
|
+
creatable: false,
|
|
14
|
+
placeholder: "Select…"
|
|
15
|
+
}.merge(overrides)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
class TamSelectInput < SimpleForm::Inputs::CollectionSelectInput
|
|
2
|
+
def input(wrapper_options = nil)
|
|
3
|
+
attributes = merge_wrapper_options(input_html_options, wrapper_options)
|
|
4
|
+
label_method, value_method = detect_collection_methods
|
|
5
|
+
tam_options = attributes.delete(:tam_options) || {}
|
|
6
|
+
data = attributes[:data] ||= {}
|
|
7
|
+
data[:controller] = (data[:controller].to_s.split + ["tam-select"]).uniq.join(" ")
|
|
8
|
+
data[:tam_select_options_value] = defaults.merge(tam_options).to_json
|
|
9
|
+
|
|
10
|
+
@builder.collection_select(
|
|
11
|
+
attribute_name,
|
|
12
|
+
collection,
|
|
13
|
+
value_method,
|
|
14
|
+
label_method,
|
|
15
|
+
input_options,
|
|
16
|
+
attributes
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def defaults
|
|
23
|
+
{
|
|
24
|
+
searchable: options.fetch(:searchable, true),
|
|
25
|
+
creatable: options.fetch(:creatable, false),
|
|
26
|
+
clearable: options.fetch(:clearable, true),
|
|
27
|
+
placeholder: options[:placeholder] || options[:prompt] || "Select…"
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import TamSelect from "tam-select"
|
|
3
|
+
|
|
4
|
+
export default class extends Controller {
|
|
5
|
+
static values = {
|
|
6
|
+
options: { type: Object, default: {} }
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
connect() {
|
|
10
|
+
this.instance = TamSelect.getInstance(this.element) || new TamSelect(this.element, this.optionsValue)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
disconnect() {
|
|
14
|
+
this.instance?.destroy()
|
|
15
|
+
this.instance = null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
optionsValueChanged() {
|
|
19
|
+
if (!this.instance) return
|
|
20
|
+
this.instance.destroy()
|
|
21
|
+
this.instance = new TamSelect(this.element, this.optionsValue)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
refresh() {
|
|
25
|
+
this.instance?.refresh()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
clear() {
|
|
29
|
+
this.instance?.clear()
|
|
30
|
+
}
|
|
31
|
+
}
|