tam_select 1.1.1 → 1.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3cdee3c229caaeda5d7f572395009c1ffd03e58c1f5376c215ced0e751c35fb9
4
- data.tar.gz: 9896dd22aa1dc9af3727596c1bee1d82ca6b8f1462a3e24a5177b10306853888
3
+ metadata.gz: 77477a755bd92b2c88c2f6909b1cd30fc80f4586cc06b047a60a524e95b08b8f
4
+ data.tar.gz: 55381cbe7a64e822f2f2ea6d48afc933d00f353b4f5563d733606b1520bc09df
5
5
  SHA512:
6
- metadata.gz: 271d3b88383037c504189080665c0c15e19a640e6f8efe0d580683d9f543a0a56ff496a667064b788fbd8a8f1ddc8f48512d755cfc10985ec043d36c042891ce
7
- data.tar.gz: be82081ad386749cb1ff40f1cd48ca80417104712c417de25dccc969082f3f2edfce09a912bce53467c34e170b1a0235245caede8d53717b7fff5d3b21d8f072
6
+ metadata.gz: 749ebc117bccbcb42ada9476629f5897cec512fbd80c669be9bf64150554e2e217907efbd4d4cbb636707bb9650921f630c164ad6fa1cca12141309d1bfcba67
7
+ data.tar.gz: b2b39df96276b3255dce1ab5903ec53ede199a0e741f51b36d243bc129708992a09035150c4f946dfd2da2a065dc43ec9267886d985fcc39a8fff496d95014d4
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
- the # Tam Select
1
+ # Tam Select
2
2
 
3
3
  [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
4
- [![Gem Version](https://img.shields.io/badge/version-1.1.0-blue.svg)](https://github.com/tamiru/tam_select)
4
+ [![Gem Version](https://img.shields.io/badge/version-1.2.0-blue.svg)](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
 
@@ -73,6 +73,7 @@ Review `git diff` afterward because `--force` overwrites application-specific cu
73
73
  - Local search and user-created tags
74
74
  - Remote JSON search with debouncing and incremental pagination
75
75
  - Loading, empty, and error states
76
+ - Polished search control with clear, search, selected, and open-state affordances
76
77
  - Keyboard navigation: arrows, Enter, Escape, Tab, and Backspace
77
78
  - Combobox/listbox ARIA semantics
78
79
  - Light and dark Tailwind themes
@@ -104,6 +105,18 @@ When the Rails generator is used, scan the generated component source:
104
105
  @source "../../javascript/tam_select/**/*.js";
105
106
  ```
106
107
 
108
+ Tam Select includes adaptive light and dark styles for every state. It follows a `dark` class on the document or any ancestor and also sets the appropriate native `color-scheme`:
109
+
110
+ ```js
111
+ document.documentElement.classList.toggle("dark")
112
+ ```
113
+
114
+ For Tailwind CSS 4 class-based theming, define the variant in the application stylesheet if it is not already present:
115
+
116
+ ```css
117
+ @custom-variant dark (&:where(.dark, .dark *));
118
+ ```
119
+
107
120
  ## Rails and Stimulus
108
121
 
109
122
  The install generator copies the Stimulus controller into your application, where Stimulus normally discovers it automatically. If controllers are registered manually:
@@ -193,8 +206,8 @@ Options are passed under `input_html[:tam_options]`. Common options include `sea
193
206
  ```json
194
207
  {
195
208
  "items": [
196
- { "value": "1", "label": "Addis Ababa" },
197
- { "value": "2", "label": "Afar" }
209
+ { "value": "1", "label": "Hana Bekele", "detail": "Admission no. UG/1024/26", "meta": "Active", "image": "/avatars/hana.jpg" },
210
+ { "value": "2", "label": "Afar", "detail": "Semera", "meta": "AF" }
198
211
  ],
199
212
  "pagination": {
200
213
  "page": 1,
@@ -221,6 +234,8 @@ class RegionsController < ApplicationController
221
234
  model: Region,
222
235
  label: :name,
223
236
  value: :id,
237
+ detail: :description,
238
+ meta: :code,
224
239
  search_by: %i[name code],
225
240
  scope: -> { Region.order(:name) },
226
241
  per_page: 20
@@ -255,51 +270,43 @@ Point Simple Form to that collection action:
255
270
 
256
271
  Typing sends `GET /regions/tam_select_options.json?q=addis&page=1` and receives the standard Tam Select JSON payload.
257
272
 
258
- ### Generic remote controller
273
+ 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.
259
274
 
260
- The installer generates `TamSelectRemoteController`. Create a small subclass for each allowed remote source:
275
+ This works well for program and student searches. For example, return the program name as `label` and admission as `detail`:
261
276
 
262
277
  ```ruby
263
- # app/controllers/region_options_controller.rb
264
- class RegionOptionsController < TamSelectRemoteController
265
- tam_select model: Region, label: :name, search_by: %i[name code]
266
-
267
- private
268
-
269
- # Override this method when records require authorization or tenant scoping.
270
- def tam_select_scope(config)
271
- current_account.regions.order(:name)
272
- end
273
- end
278
+ tam_select_remote(
279
+ model: Estudent::Program,
280
+ label: :name,
281
+ detail: ->(program) { program.admission.to_s },
282
+ search_by: %i[name],
283
+ scope: -> { Estudent::Program.includes(admission: %i[admission_type enrollment_type enrollment_mode]).order(:name) }
284
+ )
274
285
  ```
275
286
 
276
- Expose the JSON endpoint and pass it to the input:
287
+ For student search, add the admission number and photo URL:
277
288
 
278
289
  ```ruby
279
- # config/routes.rb
280
- get "region_options", to: "region_options#index", defaults: { format: :json }
290
+ tam_select_remote(
291
+ model: Estudent::Student,
292
+ label: ->(student) { student.full_name },
293
+ detail: ->(student) { "Admission no. #{student.applicant.registration_number}" },
294
+ image: ->(student) { url_for(student.applicant.person.avatar) if student.applicant.person.avatar.attached? },
295
+ search_by: %i[id_number],
296
+ scope: -> { Estudent::Student.includes(applicant: { person: { avatar_attachment: :blob } }) }
297
+ )
281
298
  ```
282
299
 
300
+ For a local native select, provide the same values as option data attributes:
301
+
283
302
  ```erb
284
- <%= form.input :region_id,
285
- as: :tam_select,
286
- collection: [form.object.region].compact,
287
- label_method: :name,
288
- value_method: :id,
289
- input_html: {
290
- tam_options: {
291
- remoteUrl: region_options_path(format: :json),
292
- minQueryLength: 1
293
- }
294
- } %>
303
+ <option value="1" data-detail="Admission no. UG/1024/26" data-meta="Active" data-image="/avatars/hana.jpg">Hana Bekele</option>
295
304
  ```
296
305
 
297
- Do not accept a model name from request parameters. Declaring each source in a controller subclass prevents clients from querying arbitrary application models.
298
-
299
306
  The browser sends requests such as:
300
307
 
301
308
  ```text
302
- GET /region_options.json?q=addis&page=1
309
+ GET /regions/tam_select_options.json?q=addis&page=1
303
310
  Accept: application/json
304
311
  ```
305
312
 
@@ -307,7 +314,7 @@ Test an endpoint independently with:
307
314
 
308
315
  ```bash
309
316
  curl -H "Accept: application/json" \
310
- "http://localhost:3000/region_options.json?q=addis&page=1"
317
+ "http://localhost:3000/regions/tam_select_options.json?q=addis&page=1"
311
318
  ```
312
319
 
313
320
  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.
@@ -345,6 +352,7 @@ instance.destroy()
345
352
  | `minQueryLength` | `0` | Characters required before requesting |
346
353
  | `valueField` | `value` | Remote item value key |
347
354
  | `labelField` | `label` | Remote item label key |
355
+ | `imageField` | `image` | Remote item image URL key |
348
356
  | `classes` | `{}` | Overrides any Tailwind class group |
349
357
 
350
358
  ## Events
@@ -3,7 +3,7 @@ module TamSelectPaginatable
3
3
 
4
4
  private
5
5
 
6
- def tam_select_payload(scope, label:, value: :id, per_page: 20)
6
+ def tam_select_payload(scope, label:, value: :id, detail: nil, meta: nil, image: nil, per_page: 20)
7
7
  page = [params.fetch(:page, 1).to_i, 1].max
8
8
  records = scope.limit(per_page + 1).offset((page - 1) * per_page).to_a
9
9
  has_more = records.length > per_page
@@ -11,9 +11,12 @@ module TamSelectPaginatable
11
11
  {
12
12
  items: records.first(per_page).map do |record|
13
13
  {
14
- value: record.public_send(value).to_s,
15
- label: label.respond_to?(:call) ? label.call(record) : record.public_send(label).to_s
16
- }
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
17
20
  end,
18
21
  pagination: {
19
22
  page: page,
@@ -22,4 +25,8 @@ module TamSelectPaginatable
22
25
  }
23
26
  }
24
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
25
32
  end
@@ -7,11 +7,14 @@ module TamSelectRemote
7
7
  end
8
8
 
9
9
  class_methods do
10
- def tam_select_remote(model:, label:, value: :id, search_by: nil, scope: nil, per_page: 20)
10
+ def tam_select_remote(model:, label:, value: :id, detail: nil, meta: nil, image: nil, search_by: nil, scope: nil, per_page: 20)
11
11
  self.tam_select_remote_config = {
12
12
  model: model,
13
13
  label: label,
14
14
  value: value,
15
+ detail: detail,
16
+ meta: meta,
17
+ image: image,
15
18
  search_by: Array(search_by || label),
16
19
  scope: scope,
17
20
  per_page: per_page
@@ -27,6 +30,9 @@ module TamSelectRemote
27
30
  records,
28
31
  label: config[:label],
29
32
  value: config[:value],
33
+ detail: config[:detail],
34
+ meta: config[:meta],
35
+ image: config[:image],
30
36
  per_page: config[:per_page]
31
37
  )
32
38
  end
@@ -1,3 +1,3 @@
1
1
  module TamSelect
2
- VERSION = "1.1.1"
2
+ VERSION = "1.2.0"
3
3
  end
data/src/tam-select.js CHANGED
@@ -1,18 +1,25 @@
1
1
  const DEFAULT_CLASSES = {
2
- wrapper: "tam-select relative w-full",
3
- control: "flex min-h-10 w-full cursor-text flex-wrap items-center gap-1.5 rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm text-zinc-900 shadow-sm transition focus-within:border-blue-500 focus-within:ring-2 focus-within:ring-blue-500/20 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100",
4
- controlDisabled: "cursor-not-allowed bg-zinc-100 opacity-60 dark:bg-zinc-800",
5
- input: "min-w-16 flex-1 border-0 bg-transparent p-0 text-sm text-zinc-900 outline-none placeholder:text-zinc-400 focus:ring-0 dark:text-zinc-100 dark:placeholder:text-zinc-500",
2
+ wrapper: "tam-select relative w-full text-zinc-900 [color-scheme:light] dark:text-zinc-100 dark:[color-scheme:dark]",
3
+ control: "flex min-h-11 w-full cursor-text flex-wrap items-center gap-2 rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm text-zinc-900 shadow-sm transition-colors hover:border-zinc-400 focus-within:border-blue-500 focus-within:ring-2 focus-within:ring-blue-500/20 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100 dark:shadow-black/20 dark:hover:border-zinc-600 dark:focus-within:border-blue-400 dark:focus-within:ring-blue-400/20",
4
+ controlDisabled: "cursor-not-allowed bg-zinc-100 opacity-60 dark:bg-zinc-800 dark:text-zinc-400",
5
+ input: "min-w-16 flex-1 border-0 bg-transparent p-0 text-left text-sm text-zinc-900 outline-none placeholder:text-zinc-400 focus:ring-0 dark:text-zinc-100 dark:placeholder:text-zinc-500",
6
+ searchIcon: "size-4 shrink-0 text-zinc-400 dark:text-zinc-500",
6
7
  placeholder: "pointer-events-none text-zinc-400 dark:text-zinc-500",
7
8
  tag: "inline-flex max-w-full items-center gap-1 rounded-md bg-blue-50 px-2 py-1 text-xs font-medium text-blue-700 ring-1 ring-inset ring-blue-200 dark:bg-blue-950/50 dark:text-blue-300 dark:ring-blue-800",
8
9
  tagRemove: "rounded p-0.5 hover:bg-blue-100 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:hover:bg-blue-900",
9
- clear: "ml-auto rounded p-1 text-zinc-400 hover:bg-zinc-100 hover:text-zinc-700 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:hover:bg-zinc-800 dark:hover:text-zinc-200",
10
+ clear: "ml-auto rounded p-1 text-zinc-400 transition-colors hover:bg-zinc-100 hover:text-zinc-700 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:text-zinc-500 dark:hover:bg-zinc-800 dark:hover:text-zinc-200 dark:focus:ring-blue-400",
10
11
  chevron: "ml-1 size-4 shrink-0 text-zinc-400 transition-transform",
11
12
  chevronOpen: "rotate-180",
12
- dropdown: "absolute z-50 mt-1 max-h-72 w-full overflow-auto rounded-lg border border-zinc-200 bg-white p-1 shadow-xl ring-1 ring-black/5 dark:border-zinc-700 dark:bg-zinc-900",
13
- option: "flex cursor-pointer items-center justify-between gap-3 rounded-md px-3 py-2 text-sm text-zinc-700 outline-none dark:text-zinc-200",
14
- optionActive: "bg-zinc-100 dark:bg-zinc-800",
15
- optionSelected: "bg-blue-50 font-medium text-blue-700 dark:bg-blue-950/50 dark:text-blue-300",
13
+ dropdown: "absolute z-50 mt-1.5 max-h-72 w-full overflow-auto rounded-lg border border-zinc-200 bg-white p-1.5 shadow-xl ring-1 ring-black/5 dark:border-zinc-700 dark:bg-zinc-900 dark:shadow-black/40 dark:ring-white/10",
14
+ option: "flex min-h-11 cursor-pointer items-center justify-between gap-3 rounded-md px-3 py-2 text-sm text-zinc-700 outline-none transition-colors dark:text-zinc-200",
15
+ optionContent: "flex min-w-0 flex-1 items-center gap-3",
16
+ optionText: "flex min-w-0 flex-1 flex-col",
17
+ optionLabel: "font-normal",
18
+ optionDetail: "text-xs font-normal text-zinc-500 dark:text-zinc-400",
19
+ optionImage: "size-9 shrink-0 rounded-full bg-zinc-100 object-cover dark:bg-zinc-800",
20
+ optionMeta: "shrink-0 rounded bg-zinc-100 px-1.5 py-0.5 text-xs font-medium text-zinc-600 dark:bg-zinc-800 dark:text-zinc-300",
21
+ optionActive: "bg-zinc-100 text-zinc-950 dark:bg-zinc-800 dark:text-white",
22
+ optionSelected: "bg-blue-50 text-blue-700 dark:bg-blue-950/60 dark:text-blue-300",
16
23
  optionDisabled: "cursor-not-allowed opacity-50",
17
24
  message: "px-3 py-6 text-center text-sm text-zinc-500 dark:text-zinc-400",
18
25
  spinner: "size-4 animate-spin rounded-full border-2 border-zinc-300 border-t-blue-600",
@@ -20,6 +27,7 @@ const DEFAULT_CLASSES = {
20
27
  }
21
28
 
22
29
  const ICONS = {
30
+ search: '<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path stroke-linecap="round" d="m14.5 14.5 3 3m-1.25-8.25a7 7 0 1 1-14 0 7 7 0 0 1 14 0Z"/></svg>',
23
31
  chevron: '<svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M5.22 7.22a.75.75 0 011.06 0L10 10.94l3.72-3.72a.75.75 0 111.06 1.06l-4.25 4.25a.75.75 0 01-1.06 0L5.22 8.28a.75.75 0 010-1.06z" clip-rule="evenodd"/></svg>',
24
32
  close: '<svg viewBox="0 0 20 20" fill="currentColor" class="size-3" aria-hidden="true"><path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"/></svg>',
25
33
  check: '<svg viewBox="0 0 20 20" fill="currentColor" class="size-4" aria-hidden="true"><path fill-rule="evenodd" d="M16.7 5.3a1 1 0 010 1.4l-8 8a1 1 0 01-1.4 0l-4-4a1 1 0 011.4-1.4L8 12.6l7.3-7.3a1 1 0 011.4 0z" clip-rule="evenodd"/></svg>'
@@ -61,6 +69,7 @@ export class TamSelect {
61
69
  minQueryLength: 0,
62
70
  valueField: "value",
63
71
  labelField: "label",
72
+ imageField: "image",
64
73
  itemsPath: "items",
65
74
  paginationPath: "pagination",
66
75
  classes: {},
@@ -107,6 +116,9 @@ export class TamSelect {
107
116
 
108
117
  this.values = document.createElement("div")
109
118
  this.values.className = "contents"
119
+ this.searchIcon = document.createElement("span")
120
+ this.searchIcon.className = this.classes.searchIcon
121
+ this.searchIcon.innerHTML = ICONS.search
110
122
  this.input = document.createElement("input")
111
123
  this.input.type = "text"
112
124
  this.input.className = this.classes.input
@@ -132,7 +144,7 @@ export class TamSelect {
132
144
  this.dropdown.setAttribute("role", "listbox")
133
145
  if (this.multiple) this.dropdown.setAttribute("aria-multiselectable", "true")
134
146
 
135
- this.control.append(this.values, this.input, this.clearButton, this.chevron)
147
+ this.control.append(this.values, this.searchIcon, this.input, this.clearButton, this.chevron)
136
148
  this.wrapper.append(this.control, this.dropdown)
137
149
  this.select.after(this.wrapper)
138
150
  this.applyDisabled()
@@ -171,7 +183,16 @@ export class TamSelect {
171
183
  readNativeOptions() {
172
184
  this.items = Array.from(this.select.options)
173
185
  .filter(option => option.value !== "" || option.selected)
174
- .map(option => ({ value: option.value, label: option.textContent.trim(), disabled: option.disabled, selected: option.selected, option }))
186
+ .map(option => ({
187
+ value: option.value,
188
+ label: option.textContent.trim(),
189
+ detail: option.dataset.detail || null,
190
+ meta: option.dataset.meta || null,
191
+ image: option.dataset.image || null,
192
+ disabled: option.disabled,
193
+ selected: option.selected,
194
+ option
195
+ }))
175
196
  this.filterLocal(false)
176
197
  }
177
198
 
@@ -179,7 +200,7 @@ export class TamSelect {
179
200
 
180
201
  filterLocal(render = true) {
181
202
  const needle = normalize(this.query)
182
- this.filtered = this.items.filter(item => !needle || normalize(item.label).includes(needle))
203
+ this.filtered = this.items.filter(item => !needle || [item.label, item.detail, item.meta].some(value => normalize(value).includes(needle)))
183
204
  this.activeIndex = this.filtered.findIndex(item => !item.disabled)
184
205
  if (render) this.renderDropdown()
185
206
  }
@@ -189,12 +210,10 @@ export class TamSelect {
189
210
  this.values.replaceChildren()
190
211
  if (this.multiple) selected.forEach(item => this.values.append(this.makeTag(item)))
191
212
  else if (selected.length) {
192
- const label = document.createElement("span")
193
- label.className = "min-w-0 flex-1 truncate"
194
- label.textContent = selected[0].label
195
- this.values.append(label)
213
+ this.values.append(this.makeItemContent(selected[0], true))
196
214
  }
197
215
  const hasValue = selected.length > 0
216
+ this.searchIcon.classList.toggle("hidden", !this.options.searchable || (hasValue && !this.opened))
198
217
  this.input.classList.toggle("hidden", !this.multiple && hasValue && !this.opened)
199
218
  this.input.placeholder = hasValue ? "" : this.options.placeholder
200
219
  this.clearButton.classList.toggle("hidden", !this.options.clearable || !hasValue || this.select.disabled)
@@ -247,10 +266,14 @@ export class TamSelect {
247
266
  option.setAttribute("role", "option")
248
267
  option.setAttribute("aria-selected", String(Boolean(item.selected)))
249
268
  option.setAttribute("aria-disabled", String(Boolean(item.disabled)))
250
- const label = document.createElement("span")
251
- label.className = "min-w-0 flex-1 truncate"
252
- label.textContent = item.label
253
- option.append(label)
269
+ const content = this.makeItemContent(item)
270
+ option.append(content)
271
+ if (item.meta) {
272
+ const meta = document.createElement("span")
273
+ meta.className = this.classes.optionMeta
274
+ meta.textContent = item.meta
275
+ option.append(meta)
276
+ }
254
277
  if (item.selected) {
255
278
  const check = document.createElement("span")
256
279
  check.innerHTML = ICONS.check
@@ -261,6 +284,34 @@ export class TamSelect {
261
284
  return option
262
285
  }
263
286
 
287
+ makeItemContent(item, selected = false) {
288
+ const content = document.createElement("span")
289
+ content.className = this.classes.optionContent
290
+ if (selected) content.classList.add("min-w-0", "flex-1")
291
+ if (item.image) {
292
+ const image = document.createElement("img")
293
+ image.className = this.classes.optionImage
294
+ image.src = item.image
295
+ image.alt = ""
296
+ image.loading = "lazy"
297
+ content.append(image)
298
+ }
299
+ const text = document.createElement("span")
300
+ text.className = this.classes.optionText
301
+ const label = document.createElement("span")
302
+ label.className = `${this.classes.optionLabel} truncate`
303
+ label.textContent = item.label
304
+ text.append(label)
305
+ if (item.detail) {
306
+ const detail = document.createElement("span")
307
+ detail.className = `${this.classes.optionDetail} truncate`
308
+ detail.textContent = item.detail
309
+ text.append(detail)
310
+ }
311
+ content.append(text)
312
+ return content
313
+ }
314
+
264
315
  makeCreateOption() {
265
316
  const option = document.createElement("div")
266
317
  option.className = this.classes.option
@@ -361,12 +412,29 @@ export class TamSelect {
361
412
  addItem(raw) {
362
413
  const value = String(raw.value ?? raw[this.options.valueField])
363
414
  const existing = this.items.find(item => String(item.value) === value)
364
- if (existing) return existing
415
+ if (existing) {
416
+ const image = raw.image ?? raw[this.options.imageField]
417
+ Object.assign(existing, raw, {
418
+ detail: raw.detail ?? existing.detail,
419
+ meta: raw.meta ?? existing.meta,
420
+ image: image ?? existing.image
421
+ })
422
+ if (existing.option) {
423
+ if (existing.detail != null) existing.option.dataset.detail = String(existing.detail)
424
+ if (existing.meta != null) existing.option.dataset.meta = String(existing.meta)
425
+ if (existing.image != null) existing.option.dataset.image = String(existing.image)
426
+ }
427
+ return existing
428
+ }
365
429
  const label = String(raw.label ?? raw[this.options.labelField] ?? value)
366
430
  const option = new Option(label, value, Boolean(raw.selected), Boolean(raw.selected))
367
431
  option.dataset.tamSelectGenerated = ""
432
+ if (raw.detail != null) option.dataset.detail = String(raw.detail)
433
+ if (raw.meta != null) option.dataset.meta = String(raw.meta)
434
+ const image = raw.image ?? raw[this.options.imageField]
435
+ if (image != null) option.dataset.image = String(image)
368
436
  this.select.add(option)
369
- const item = { ...raw, value, label, option, selected: option.selected, disabled: Boolean(raw.disabled) }
437
+ const item = { ...raw, value, label, image: image == null ? null : String(image), option, selected: option.selected, disabled: Boolean(raw.disabled) }
370
438
  option.disabled = item.disabled
371
439
  this.items.push(item)
372
440
  this.filterLocal(false)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tam_select
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tamiru Hailu