@unabridged/midwest 0.16.1 → 0.18.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 (34) hide show
  1. package/app/assets/javascript/midwest/index.ts +6 -0
  2. package/app/assets/javascript/midwest.js +970 -39
  3. package/app/assets/javascript/midwest.js.map +1 -1
  4. package/app/assets/stylesheets/midwest.css +1 -1
  5. package/app/assets/stylesheets/midwest.tailwind.css +6 -1
  6. package/dist/css/midwest.css +1 -1
  7. package/dist/javascript/collection/app/assets/javascript/midwest/index.js +6 -0
  8. package/dist/javascript/collection/app/assets/javascript/midwest/index.js.map +1 -1
  9. package/dist/javascript/collection/app/components/midwest/carousel_component/carousel_component_controller.js +17 -5
  10. package/dist/javascript/collection/app/components/midwest/carousel_component/carousel_component_controller.js.map +1 -1
  11. package/dist/javascript/collection/app/components/midwest/color_picker_component/color_picker_component_controller.js +135 -0
  12. package/dist/javascript/collection/app/components/midwest/color_picker_component/color_picker_component_controller.js.map +1 -0
  13. package/dist/javascript/collection/app/components/midwest/countdown_timer_component/countdown_timer_component_controller.js +27 -6
  14. package/dist/javascript/collection/app/components/midwest/countdown_timer_component/countdown_timer_component_controller.js.map +1 -1
  15. package/dist/javascript/collection/app/components/midwest/form/autocomplete_component/autocomplete_component_controller.js +109 -21
  16. package/dist/javascript/collection/app/components/midwest/form/autocomplete_component/autocomplete_component_controller.js.map +1 -1
  17. package/dist/javascript/collection/app/components/midwest/form/color_picker_component/color_picker_component_controller.js +129 -0
  18. package/dist/javascript/collection/app/components/midwest/form/color_picker_component/color_picker_component_controller.js.map +1 -0
  19. package/dist/javascript/collection/app/components/midwest/form/input_component/input_component_controller.js +50 -1
  20. package/dist/javascript/collection/app/components/midwest/form/input_component/input_component_controller.js.map +1 -1
  21. package/dist/javascript/collection/app/components/midwest/form/live_summary_component/live_summary_component_controller.js +38 -5
  22. package/dist/javascript/collection/app/components/midwest/form/live_summary_component/live_summary_component_controller.js.map +1 -1
  23. package/dist/javascript/collection/app/components/midwest/image_component/color_thief.js +490 -0
  24. package/dist/javascript/collection/app/components/midwest/image_component/color_thief.js.map +1 -0
  25. package/dist/javascript/collection/app/components/midwest/image_component/image_component_controller.js +47 -0
  26. package/dist/javascript/collection/app/components/midwest/image_component/image_component_controller.js.map +1 -0
  27. package/dist/javascript/collection/app/components/midwest/notification_component/notification_component_controller.js +60 -0
  28. package/dist/javascript/collection/app/components/midwest/notification_component/notification_component_controller.js.map +1 -0
  29. package/dist/javascript/collection/app/components/midwest/tabs_component/tabs_component_controller.js +15 -1
  30. package/dist/javascript/collection/app/components/midwest/tabs_component/tabs_component_controller.js.map +1 -1
  31. package/dist/javascript/midwest.d.ts +5 -0
  32. package/dist/javascript/midwest.js +970 -39
  33. package/dist/javascript/midwest.js.map +1 -1
  34. package/package.json +1 -1
@@ -1,19 +1,30 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
3
  class Autocomplete extends Controller {
4
- static targets = ["input", "dropdown", "list", "tokens", "multipleTokenTemplate", "singleTokenTemplate", "status", "turboList", "loader"];
4
+ static targets = [
5
+ "input",
6
+ "dropdown",
7
+ "list",
8
+ "tokens",
9
+ "multipleTokenTemplate",
10
+ "singleTokenTemplate",
11
+ "status",
12
+ "turboList",
13
+ "loader",
14
+ "freeFormHintList",
15
+ "freeFormHintValue"
16
+ ];
5
17
  static values = {
6
18
  options: { type: Array, default: [] },
7
19
  url: { type: String, default: "" },
8
20
  multiple: { type: Boolean, default: true },
9
- turboFrameUrl: { type: String, default: "" }
21
+ turboFrameUrl: { type: String, default: "" },
22
+ freeForm: { type: Boolean, default: false }
10
23
  };
11
24
  highlightedIndex = -1;
12
25
  fetchAbort = null;
13
26
  searchTimer;
14
27
  // ── Named handlers ─────────────────────────────────────────────────────────
15
- // Arrow-function class properties preserve `this` and produce a stable
16
- // reference so addEventListener/removeEventListener pairs always match.
17
28
  onFrameLoad = () => {
18
29
  this.syncSelectedState();
19
30
  this.loading = false;
@@ -48,9 +59,6 @@ class Autocomplete extends Controller {
48
59
  clearTimeout(this.searchTimer);
49
60
  this.element.removeEventListener("focusout", this.onFocusOut);
50
61
  }
51
- // Target callbacks — Stimulus calls these automatically when targets connect
52
- // or disconnect, including after the initial connect(). This replaces manual
53
- // hasTurboListTarget guards and addEventListener calls inside connect().
54
62
  dropdownTargetConnected(el) {
55
63
  el.addEventListener("toggle", this.onToggle);
56
64
  }
@@ -75,13 +83,27 @@ class Autocomplete extends Controller {
75
83
  this.open();
76
84
  }
77
85
  open() {
86
+ if (!this.hasDropdownTarget)
87
+ return;
78
88
  if (this.dropdownTarget.matches(":popover-open"))
79
89
  return;
90
+ const query = this.inputTarget.value.trim();
91
+ if (this.freeFormValue && !this.hasListTarget && !this.hasTurboListTarget) {
92
+ if (query) {
93
+ this.updateFreeFormHint(query);
94
+ this.showDropdown();
95
+ }
96
+ return;
97
+ }
80
98
  clearTimeout(this.searchTimer);
81
- this.resolve(this.inputTarget.value.trim());
99
+ this.resolve(query);
82
100
  }
83
101
  search() {
84
102
  const query = this.inputTarget.value.trim();
103
+ if (this.freeFormValue)
104
+ this.updateFreeFormHint(query);
105
+ if (!this.hasListTarget && !this.hasTurboListTarget)
106
+ return;
85
107
  if (!this.hasTurboListTarget && !this.urlValue) {
86
108
  this.renderOptions(this.filteredOptions(query));
87
109
  return;
@@ -96,6 +118,8 @@ class Autocomplete extends Controller {
96
118
  switch (event.key) {
97
119
  case "ArrowDown": {
98
120
  event.preventDefault();
121
+ if (!this.hasDropdownTarget)
122
+ break;
99
123
  if (!this.dropdownTarget.matches(":popover-open"))
100
124
  this.open();
101
125
  const items = this.visibleOptions();
@@ -105,7 +129,7 @@ class Autocomplete extends Controller {
105
129
  }
106
130
  case "ArrowUp": {
107
131
  event.preventDefault();
108
- if (!this.dropdownTarget.matches(":popover-open"))
132
+ if (!this.hasDropdownTarget || !this.dropdownTarget.matches(":popover-open"))
109
133
  break;
110
134
  const items = this.visibleOptions();
111
135
  this.highlightedIndex = Math.max(this.highlightedIndex - 1, 0);
@@ -114,11 +138,22 @@ class Autocomplete extends Controller {
114
138
  }
115
139
  case "Enter": {
116
140
  event.preventDefault();
117
- if (!this.dropdownTarget.matches(":popover-open"))
118
- break;
119
- const items = this.visibleOptions();
120
- if (this.highlightedIndex >= 0 && items[this.highlightedIndex]) {
121
- this.pickItem(items[this.highlightedIndex]);
141
+ if (this.hasDropdownTarget && this.dropdownTarget.matches(":popover-open") && (this.hasListTarget || this.hasTurboListTarget)) {
142
+ const items = this.visibleOptions();
143
+ if (this.highlightedIndex >= 0 && items[this.highlightedIndex]) {
144
+ this.pickItem(items[this.highlightedIndex]);
145
+ break;
146
+ }
147
+ }
148
+ if (this.freeFormValue) {
149
+ this.commitFreeFormTag();
150
+ }
151
+ break;
152
+ }
153
+ case ",": {
154
+ if (this.freeFormValue && this.inputTarget.value.trim()) {
155
+ event.preventDefault();
156
+ this.commitFreeFormTag();
122
157
  }
123
158
  break;
124
159
  }
@@ -135,6 +170,22 @@ class Autocomplete extends Controller {
135
170
  break;
136
171
  }
137
172
  }
173
+ // Splits pasted text on commas and newlines into individual tags (free-form mode only).
174
+ pasteTag(event) {
175
+ if (!this.freeFormValue)
176
+ return;
177
+ const text = event.clipboardData?.getData("text") ?? "";
178
+ const tags = text.split(/[\n,]+/).map((t) => t.trim()).filter(Boolean);
179
+ if (tags.length === 0)
180
+ return;
181
+ event.preventDefault();
182
+ tags.forEach((tag) => this.addFreeFormTag(tag));
183
+ }
184
+ // Mousedown on the "Hit Enter to add 'X'" hint row.
185
+ addFreeFormTagFromHint(event) {
186
+ event.preventDefault();
187
+ this.commitFreeFormTag();
188
+ }
138
189
  removeToken(event) {
139
190
  event.stopPropagation();
140
191
  const btn = event.currentTarget;
@@ -142,9 +193,6 @@ class Autocomplete extends Controller {
142
193
  if (value)
143
194
  this.deselect(value);
144
195
  }
145
- // Public action used by turbo-frame option items via data-action.
146
- // Each server-rendered <li> should have:
147
- // data-action="mousedown->midwest-autocomplete#selectOption"
148
196
  selectOption(event) {
149
197
  event.preventDefault();
150
198
  this.pickItem(event.currentTarget);
@@ -156,7 +204,6 @@ class Autocomplete extends Controller {
156
204
  isSelected(value) {
157
205
  return !!this.tokensTarget.querySelector(`[data-value="${CSS.escape(value)}"]`);
158
206
  }
159
- // Routes to the correct data source. Shared by open() and the debounced search path.
160
207
  resolve(query) {
161
208
  if (this.hasTurboListTarget) {
162
209
  this.navigateFrame(query);
@@ -185,9 +232,6 @@ class Autocomplete extends Controller {
185
232
  this.turboListTarget.setAttribute("src", url.toString());
186
233
  this.showDropdown();
187
234
  }
188
- // After a turbo frame loads, mark already-selected items and update the live region.
189
- // The empty state element is always present in the frame and CSS shows/hides it
190
- // based on whether any non-selected options exist.
191
235
  syncSelectedState() {
192
236
  const options = this.turboListTarget.querySelectorAll(".midwest-autocomplete-option");
193
237
  options.forEach((option) => {
@@ -223,6 +267,7 @@ class Autocomplete extends Controller {
223
267
  select(option) {
224
268
  this.appendToken(option);
225
269
  this.hideDropdown();
270
+ this.hideFreeFormHint();
226
271
  this.inputTarget.value = "";
227
272
  this.inputTarget.focus();
228
273
  this.announce(`${option.label} selected`);
@@ -294,11 +339,19 @@ class Autocomplete extends Controller {
294
339
  this.renderEmpty("No more options");
295
340
  return;
296
341
  }
342
+ this.hideFreeFormHint();
297
343
  this.loading = false;
298
344
  this.announce(`${availableCount} option${availableCount === 1 ? "" : "s"} available`);
299
345
  this.showDropdown();
300
346
  }
301
347
  renderEmpty(message = "No results") {
348
+ if (this.freeFormValue && this.inputTarget.value.trim()) {
349
+ if (this.hasListTarget)
350
+ this.listTarget.replaceChildren();
351
+ this.loading = false;
352
+ this.updateFreeFormHint(this.inputTarget.value.trim());
353
+ return;
354
+ }
302
355
  const li = document.createElement("li");
303
356
  li.className = "midwest-autocomplete-empty";
304
357
  li.setAttribute("aria-hidden", "true");
@@ -308,15 +361,39 @@ class Autocomplete extends Controller {
308
361
  this.announce(message);
309
362
  this.showDropdown();
310
363
  }
364
+ // Shows or hides the "Hit Enter to add 'X'" hint based on the current query.
365
+ updateFreeFormHint(query) {
366
+ if (!this.hasFreeFormHintListTarget)
367
+ return;
368
+ if (query && !this.isSelected(query)) {
369
+ if (this.hasFreeFormHintValueTarget) {
370
+ this.freeFormHintValueTarget.textContent = `"${query}"`;
371
+ }
372
+ this.freeFormHintListTarget.hidden = false;
373
+ this.showDropdown();
374
+ } else {
375
+ this.hideFreeFormHint();
376
+ if (!this.hasListTarget && !this.hasTurboListTarget)
377
+ this.hideDropdown();
378
+ }
379
+ }
380
+ hideFreeFormHint() {
381
+ if (this.hasFreeFormHintListTarget)
382
+ this.freeFormHintListTarget.hidden = true;
383
+ }
311
384
  set loading(on) {
312
385
  if (this.hasLoaderTarget)
313
386
  this.loaderTarget.hidden = !on;
314
387
  }
315
388
  showDropdown() {
389
+ if (!this.hasDropdownTarget)
390
+ return;
316
391
  if (!this.dropdownTarget.matches(":popover-open"))
317
392
  this.dropdownTarget.showPopover();
318
393
  }
319
394
  hideDropdown() {
395
+ if (!this.hasDropdownTarget)
396
+ return;
320
397
  if (this.dropdownTarget.matches(":popover-open"))
321
398
  this.dropdownTarget.hidePopover();
322
399
  }
@@ -340,6 +417,17 @@ class Autocomplete extends Controller {
340
417
  const support = item.querySelector(".midwest-autocomplete-option-support")?.textContent?.trim();
341
418
  this.select({ value, label, support });
342
419
  }
420
+ commitFreeFormTag() {
421
+ const value = this.inputTarget.value.trim();
422
+ this.addFreeFormTag(value);
423
+ }
424
+ addFreeFormTag(value) {
425
+ if (!value)
426
+ return;
427
+ if (this.isSelected(value))
428
+ return;
429
+ this.select({ value, label: value });
430
+ }
343
431
  announce(message) {
344
432
  if (this.hasStatusTarget)
345
433
  this.statusTarget.textContent = message;
@@ -1 +1 @@
1
- {"version":3,"file":"autocomplete_component_controller.js","sources":["../../../../../../../../app/components/midwest/form/autocomplete_component/autocomplete_component_controller.ts"],"sourcesContent":["import { Controller } from '@hotwired/stimulus'\n\ninterface Option {\n value: string\n label: string\n support?: string\n}\n\nexport default class Autocomplete extends Controller<HTMLElement> {\n static targets = ['input', 'dropdown', 'list', 'tokens', 'multipleTokenTemplate', 'singleTokenTemplate', 'status', 'turboList', 'loader']\n static values = {\n options: { type: Array, default: [] },\n url: { type: String, default: '' },\n multiple: { type: Boolean, default: true },\n turboFrameUrl: { type: String, default: '' }\n }\n\n declare readonly inputTarget: HTMLInputElement\n declare readonly dropdownTarget: HTMLElement\n declare readonly listTarget: HTMLElement\n declare readonly hasListTarget: boolean\n declare readonly tokensTarget: HTMLElement\n declare readonly multipleTokenTemplateTarget: HTMLTemplateElement\n declare readonly singleTokenTemplateTarget: HTMLTemplateElement\n declare readonly statusTarget: HTMLElement\n declare readonly hasStatusTarget: boolean\n declare readonly turboListTarget: HTMLElement\n declare readonly hasTurboListTarget: boolean\n declare readonly loaderTarget: HTMLElement\n declare readonly hasLoaderTarget: boolean\n\n declare optionsValue: Option[]\n declare urlValue: string\n declare multipleValue: boolean\n declare turboFrameUrlValue: string\n\n private highlightedIndex = -1\n private fetchAbort: AbortController | null = null\n private searchTimer: number | undefined\n\n // ── Named handlers ─────────────────────────────────────────────────────────\n // Arrow-function class properties preserve `this` and produce a stable\n // reference so addEventListener/removeEventListener pairs always match.\n\n private onFrameLoad = () => {\n // syncSelectedState marks already-selected items as aria-selected=\"true\" (CSS hides them).\n // We hide the loader only after that DOM update so the brief flash of selected items\n // is covered by the overlay and never visible to the user.\n this.syncSelectedState()\n this.loading = false\n this.showDropdown()\n }\n\n private onToggle = (e: Event) => {\n const opened = (e as ToggleEvent).newState === 'open'\n this.inputTarget.setAttribute('aria-expanded', String(opened))\n if (!opened) {\n this.inputTarget.removeAttribute('aria-activedescendant')\n this.highlightedIndex = -1\n }\n }\n\n private onFocusOut = (e: FocusEvent) => {\n if (!this.element.contains(e.relatedTarget as Node)) {\n this.hideDropdown()\n }\n }\n\n private onListMouseDown = (e: MouseEvent) => {\n const option = (e.target as HTMLElement).closest<HTMLElement>('.midwest-autocomplete-option')\n if (option) { e.preventDefault(); this.pickItem(option) }\n }\n\n // ── Lifecycle ───────────────────────────────────────────────────────────────\n\n connect () {\n // focusout is on the controller root — not a target — so it lives here.\n this.element.addEventListener('focusout', this.onFocusOut)\n }\n\n disconnect () {\n this.fetchAbort?.abort()\n clearTimeout(this.searchTimer)\n this.element.removeEventListener('focusout', this.onFocusOut)\n }\n\n // Target callbacks — Stimulus calls these automatically when targets connect\n // or disconnect, including after the initial connect(). This replaces manual\n // hasTurboListTarget guards and addEventListener calls inside connect().\n\n dropdownTargetConnected (el: HTMLElement) {\n el.addEventListener('toggle', this.onToggle)\n }\n\n dropdownTargetDisconnected (el: HTMLElement) {\n el.removeEventListener('toggle', this.onToggle)\n }\n\n listTargetConnected (el: HTMLElement) {\n // Single delegated listener for client-rendered options.\n el.addEventListener('mousedown', this.onListMouseDown)\n }\n\n listTargetDisconnected (el: HTMLElement) {\n el.removeEventListener('mousedown', this.onListMouseDown)\n }\n\n turboListTargetConnected (el: HTMLElement) {\n el.addEventListener('turbo:frame-load', this.onFrameLoad)\n }\n\n turboListTargetDisconnected (el: HTMLElement) {\n el.removeEventListener('turbo:frame-load', this.onFrameLoad)\n }\n\n // ── Public actions ──────────────────────────────────────────────────────────\n\n focusInput () {\n this.inputTarget.focus()\n this.open()\n }\n\n open () {\n if (this.dropdownTarget.matches(':popover-open')) return\n // Cancel any pending debounced search so open() always shows results immediately.\n clearTimeout(this.searchTimer)\n this.resolve(this.inputTarget.value.trim())\n }\n\n search () {\n const query = this.inputTarget.value.trim()\n\n // Static options filter instantly — no debounce needed.\n if (!this.hasTurboListTarget && !this.urlValue) {\n this.renderOptions(this.filteredOptions(query))\n return\n }\n\n // Debounce network requests so only the final keystroke fires.\n clearTimeout(this.searchTimer)\n this.searchTimer = window.setTimeout(() => {\n this.searchTimer = undefined\n this.resolve(query)\n }, 200)\n }\n\n navigate (event: KeyboardEvent) {\n switch (event.key) {\n case 'ArrowDown': {\n event.preventDefault()\n if (!this.dropdownTarget.matches(':popover-open')) this.open()\n const items = this.visibleOptions()\n this.highlightedIndex = Math.min(this.highlightedIndex + 1, items.length - 1)\n this.syncHighlight(items)\n break\n }\n\n case 'ArrowUp': {\n event.preventDefault()\n if (!this.dropdownTarget.matches(':popover-open')) break\n const items = this.visibleOptions()\n this.highlightedIndex = Math.max(this.highlightedIndex - 1, 0)\n this.syncHighlight(items)\n break\n }\n\n case 'Enter': {\n event.preventDefault()\n if (!this.dropdownTarget.matches(':popover-open')) break\n const items = this.visibleOptions()\n if (this.highlightedIndex >= 0 && items[this.highlightedIndex]) {\n this.pickItem(items[this.highlightedIndex])\n }\n break\n }\n\n case 'Escape':\n this.hideDropdown()\n break\n\n case 'Backspace':\n if (this.inputTarget.value === '' && this.multipleValue) {\n const tokens = this.tokensTarget.querySelectorAll<HTMLElement>('[data-value]')\n const last = tokens[tokens.length - 1]\n if (last?.dataset.value) this.deselect(last.dataset.value)\n }\n break\n }\n }\n\n removeToken (event: MouseEvent) {\n event.stopPropagation()\n const btn = event.currentTarget as HTMLElement\n const value = btn.dataset.value\n if (value) this.deselect(value)\n }\n\n // Public action used by turbo-frame option items via data-action.\n // Each server-rendered <li> should have:\n // data-action=\"mousedown->midwest-autocomplete#selectOption\"\n selectOption (event: MouseEvent) {\n event.preventDefault()\n this.pickItem(event.currentTarget as HTMLElement)\n }\n\n // ── Private ─────────────────────────────────────────────────────────────────\n\n private get optionIdPrefix (): string {\n return `ac-opt-${(this.element.dataset.name ?? 'field').replace(/\\W/g, '-')}`\n }\n\n private isSelected (value: string): boolean {\n return !!this.tokensTarget.querySelector(`[data-value=\"${CSS.escape(value)}\"]`)\n }\n\n // Routes to the correct data source. Shared by open() and the debounced search path.\n private resolve (query: string) {\n if (this.hasTurboListTarget) { this.navigateFrame(query); return }\n if (this.urlValue) { this.fetch(query); return }\n this.renderOptions(this.filteredOptions(query))\n }\n\n private filteredOptions (query: string): Option[] {\n const q = query.toLowerCase()\n return (this.optionsValue as Option[]).filter(\n o => q === '' || o.label.toLowerCase().includes(q) || (o.support?.toLowerCase().includes(q) ?? false)\n )\n }\n\n private navigateFrame (query: string) {\n const url = new URL(this.turboFrameUrlValue, window.location.href)\n if (query) url.searchParams.set('q', query)\n // Send already-selected values so the server can exclude duplicates.\n this.tokensTarget.querySelectorAll<HTMLInputElement>('input[type=hidden]').forEach(input => {\n url.searchParams.append('selected[]', input.value)\n })\n this.loading = true\n this.turboListTarget.setAttribute('src', url.toString())\n this.showDropdown()\n }\n\n // After a turbo frame loads, mark already-selected items and update the live region.\n // The empty state element is always present in the frame and CSS shows/hides it\n // based on whether any non-selected options exist.\n private syncSelectedState () {\n const options = this.turboListTarget.querySelectorAll<HTMLElement>('.midwest-autocomplete-option')\n options.forEach(option => {\n const val = option.dataset.value ?? ''\n option.setAttribute('aria-selected', this.isSelected(val) ? 'true' : 'false')\n })\n\n const visibleCount = this.visibleOptions().length\n this.announce(visibleCount > 0 ? `${visibleCount} option${visibleCount === 1 ? '' : 's'} available` : 'No more options')\n }\n\n private async fetch (query: string) {\n this.fetchAbort?.abort()\n this.fetchAbort = new AbortController()\n\n this.listTarget.replaceChildren()\n this.loading = true\n this.announce('Loading')\n this.showDropdown()\n\n try {\n const url = new URL(this.urlValue, window.location.href)\n if (query) url.searchParams.set('q', query)\n\n const res = await globalThis.fetch(url.toString(), {\n signal: this.fetchAbort.signal,\n headers: { Accept: 'application/json' }\n })\n\n const data: Option[] = await res.json()\n this.renderOptions(data)\n } catch (err) {\n if ((err as Error).name !== 'AbortError') {\n this.renderEmpty()\n }\n }\n }\n\n private select (option: Option) {\n this.appendToken(option)\n this.hideDropdown()\n this.inputTarget.value = ''\n this.inputTarget.focus()\n this.announce(`${option.label} selected`)\n\n this.dispatch('change', { detail: { value: option.value, label: option.label } })\n }\n\n private deselect (value: string) {\n const token = this.tokensTarget.querySelector(`[data-value=\"${CSS.escape(value)}\"]`)\n const label = token?.querySelector('.value')?.textContent?.trim()\n token?.remove()\n if (label) this.announce(`${label} removed`)\n this.dispatch('change', { detail: { value: null } })\n }\n\n private appendToken (option: Option) {\n if (!this.multipleValue) {\n this.tokensTarget.querySelectorAll<HTMLElement>('[data-value]').forEach(el => el.remove())\n }\n const template = this.multipleValue ? this.multipleTokenTemplateTarget : this.singleTokenTemplateTarget\n const fragment = template.content.cloneNode(true) as DocumentFragment\n const badge = fragment.querySelector<HTMLElement>('.midwest-badge')\n if (!badge) return\n badge.dataset.value = option.value\n const valueEl = badge.querySelector<HTMLElement>('.value')\n if (valueEl) valueEl.textContent = option.label\n if (this.multipleValue) {\n const removeBtn = badge.querySelector<HTMLElement>('.remove')\n if (removeBtn) {\n removeBtn.dataset.value = option.value\n removeBtn.setAttribute('aria-label', `Remove ${option.label}`)\n }\n }\n const input = badge.querySelector<HTMLInputElement>('input[type=hidden]')\n if (input) {\n input.name = this.element.dataset.name ?? ''\n input.value = option.value\n }\n this.tokensTarget.appendChild(fragment)\n }\n\n private renderOptions (options: Option[]) {\n this.highlightedIndex = -1\n\n if (options.length === 0) {\n this.renderEmpty()\n return\n }\n\n this.listTarget.replaceChildren()\n\n options.forEach((option, index) => {\n const li = document.createElement('li')\n li.id = `${this.optionIdPrefix}-${index}`\n li.className = 'midwest-autocomplete-option'\n li.setAttribute('role', 'option')\n li.setAttribute('aria-selected', this.isSelected(option.value) ? 'true' : 'false')\n li.dataset.value = option.value\n\n const label = document.createElement('span')\n label.className = 'midwest-autocomplete-option-label'\n label.textContent = option.label\n li.appendChild(label)\n\n if (option.support) {\n const support = document.createElement('span')\n support.className = 'midwest-autocomplete-option-support'\n support.textContent = option.support\n li.appendChild(support)\n }\n\n this.listTarget.appendChild(li)\n })\n\n const availableCount = options.filter(o => !this.isSelected(o.value)).length\n if (availableCount === 0) {\n this.renderEmpty('No more options')\n return\n }\n this.loading = false\n this.announce(`${availableCount} option${availableCount === 1 ? '' : 's'} available`)\n this.showDropdown()\n }\n\n private renderEmpty (message = 'No results') {\n const li = document.createElement('li')\n li.className = 'midwest-autocomplete-empty'\n li.setAttribute('aria-hidden', 'true')\n li.textContent = message\n this.listTarget.replaceChildren(li)\n this.loading = false\n this.announce(message)\n this.showDropdown()\n }\n\n private set loading (on: boolean) {\n if (this.hasLoaderTarget) this.loaderTarget.hidden = !on\n }\n\n private showDropdown () {\n if (!this.dropdownTarget.matches(':popover-open')) this.dropdownTarget.showPopover()\n }\n\n private hideDropdown () {\n if (this.dropdownTarget.matches(':popover-open')) this.dropdownTarget.hidePopover()\n }\n\n private visibleOptions (): NodeListOf<HTMLElement> {\n const container = this.hasTurboListTarget ? this.turboListTarget : this.listTarget\n return container.querySelectorAll<HTMLElement>('.midwest-autocomplete-option:not([aria-selected=\"true\"])')\n }\n\n private syncHighlight (items: NodeListOf<HTMLElement>) {\n items.forEach((item, i) => item.classList.toggle('is-highlighted', i === this.highlightedIndex))\n const active = items[this.highlightedIndex]\n active?.scrollIntoView({ block: 'nearest' })\n if (active?.id) {\n this.inputTarget.setAttribute('aria-activedescendant', active.id)\n } else {\n this.inputTarget.removeAttribute('aria-activedescendant')\n }\n }\n\n private pickItem (item: HTMLElement) {\n const value = item.dataset.value ?? ''\n // Prefer data-label (set by AutocompleteOptionComponent) so rich slot content\n // like avatars does not corrupt the badge text via textContent.\n const label = item.dataset.label?.trim() || item.querySelector('.midwest-autocomplete-option-label')?.textContent?.trim() || value\n const support = item.querySelector('.midwest-autocomplete-option-support')?.textContent?.trim()\n this.select({ value, label, support })\n }\n\n private announce (message: string) {\n if (this.hasStatusTarget) this.statusTarget.textContent = message\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"autocomplete_component_controller.js","sources":["../../../../../../../../app/components/midwest/form/autocomplete_component/autocomplete_component_controller.ts"],"sourcesContent":["import { Controller } from '@hotwired/stimulus'\n\ninterface Option {\n value: string\n label: string\n support?: string\n}\n\nexport default class Autocomplete extends Controller<HTMLElement> {\n static targets = [\n 'input', 'dropdown', 'list', 'tokens',\n 'multipleTokenTemplate', 'singleTokenTemplate',\n 'status', 'turboList', 'loader',\n 'freeFormHintList', 'freeFormHintValue'\n ]\n\n static values = {\n options: { type: Array, default: [] },\n url: { type: String, default: '' },\n multiple: { type: Boolean, default: true },\n turboFrameUrl: { type: String, default: '' },\n freeForm: { type: Boolean, default: false }\n }\n\n declare readonly inputTarget: HTMLInputElement\n declare readonly dropdownTarget: HTMLElement\n declare readonly hasDropdownTarget: boolean\n declare readonly listTarget: HTMLElement\n declare readonly hasListTarget: boolean\n declare readonly tokensTarget: HTMLElement\n declare readonly multipleTokenTemplateTarget: HTMLTemplateElement\n declare readonly singleTokenTemplateTarget: HTMLTemplateElement\n declare readonly statusTarget: HTMLElement\n declare readonly hasStatusTarget: boolean\n declare readonly turboListTarget: HTMLElement\n declare readonly hasTurboListTarget: boolean\n declare readonly loaderTarget: HTMLElement\n declare readonly hasLoaderTarget: boolean\n declare readonly freeFormHintListTarget: HTMLElement\n declare readonly hasFreeFormHintListTarget: boolean\n declare readonly freeFormHintValueTarget: HTMLElement\n declare readonly hasFreeFormHintValueTarget: boolean\n\n declare optionsValue: Option[]\n declare urlValue: string\n declare multipleValue: boolean\n declare turboFrameUrlValue: string\n declare freeFormValue: boolean\n\n private highlightedIndex = -1\n private fetchAbort: AbortController | null = null\n private searchTimer: number | undefined\n\n // ── Named handlers ─────────────────────────────────────────────────────────\n\n private onFrameLoad = () => {\n this.syncSelectedState()\n this.loading = false\n this.showDropdown()\n }\n\n private onToggle = (e: Event) => {\n const opened = (e as ToggleEvent).newState === 'open'\n this.inputTarget.setAttribute('aria-expanded', String(opened))\n if (!opened) {\n this.inputTarget.removeAttribute('aria-activedescendant')\n this.highlightedIndex = -1\n }\n }\n\n private onFocusOut = (e: FocusEvent) => {\n if (!this.element.contains(e.relatedTarget as Node)) {\n this.hideDropdown()\n }\n }\n\n private onListMouseDown = (e: MouseEvent) => {\n const option = (e.target as HTMLElement).closest<HTMLElement>('.midwest-autocomplete-option')\n if (option) { e.preventDefault(); this.pickItem(option) }\n }\n\n // ── Lifecycle ───────────────────────────────────────────────────────────────\n\n connect () {\n this.element.addEventListener('focusout', this.onFocusOut)\n }\n\n disconnect () {\n this.fetchAbort?.abort()\n clearTimeout(this.searchTimer)\n this.element.removeEventListener('focusout', this.onFocusOut)\n }\n\n dropdownTargetConnected (el: HTMLElement) {\n el.addEventListener('toggle', this.onToggle)\n }\n\n dropdownTargetDisconnected (el: HTMLElement) {\n el.removeEventListener('toggle', this.onToggle)\n }\n\n listTargetConnected (el: HTMLElement) {\n el.addEventListener('mousedown', this.onListMouseDown)\n }\n\n listTargetDisconnected (el: HTMLElement) {\n el.removeEventListener('mousedown', this.onListMouseDown)\n }\n\n turboListTargetConnected (el: HTMLElement) {\n el.addEventListener('turbo:frame-load', this.onFrameLoad)\n }\n\n turboListTargetDisconnected (el: HTMLElement) {\n el.removeEventListener('turbo:frame-load', this.onFrameLoad)\n }\n\n // ── Public actions ──────────────────────────────────────────────────────────\n\n focusInput () {\n this.inputTarget.focus()\n this.open()\n }\n\n open () {\n if (!this.hasDropdownTarget) return\n if (this.dropdownTarget.matches(':popover-open')) return\n\n const query = this.inputTarget.value.trim()\n\n // In pure free-form mode (no suggestions list), only open when there's\n // something typed — an empty input has nothing to hint about.\n if (this.freeFormValue && !this.hasListTarget && !this.hasTurboListTarget) {\n if (query) {\n this.updateFreeFormHint(query)\n this.showDropdown()\n }\n return\n }\n\n clearTimeout(this.searchTimer)\n this.resolve(query)\n }\n\n search () {\n const query = this.inputTarget.value.trim()\n\n // Always keep the free-form hint in sync while the user types.\n if (this.freeFormValue) this.updateFreeFormHint(query)\n\n // Pure free-form with no suggestions list: nothing else to do.\n if (!this.hasListTarget && !this.hasTurboListTarget) return\n\n if (!this.hasTurboListTarget && !this.urlValue) {\n this.renderOptions(this.filteredOptions(query))\n return\n }\n\n clearTimeout(this.searchTimer)\n this.searchTimer = window.setTimeout(() => {\n this.searchTimer = undefined\n this.resolve(query)\n }, 200)\n }\n\n navigate (event: KeyboardEvent) {\n switch (event.key) {\n case 'ArrowDown': {\n event.preventDefault()\n if (!this.hasDropdownTarget) break\n if (!this.dropdownTarget.matches(':popover-open')) this.open()\n const items = this.visibleOptions()\n this.highlightedIndex = Math.min(this.highlightedIndex + 1, items.length - 1)\n this.syncHighlight(items)\n break\n }\n\n case 'ArrowUp': {\n event.preventDefault()\n if (!this.hasDropdownTarget || !this.dropdownTarget.matches(':popover-open')) break\n const items = this.visibleOptions()\n this.highlightedIndex = Math.max(this.highlightedIndex - 1, 0)\n this.syncHighlight(items)\n break\n }\n\n case 'Enter': {\n event.preventDefault()\n // If a suggestions list is open and something is highlighted, pick it.\n if (this.hasDropdownTarget && this.dropdownTarget.matches(':popover-open') &&\n (this.hasListTarget || this.hasTurboListTarget)) {\n const items = this.visibleOptions()\n if (this.highlightedIndex >= 0 && items[this.highlightedIndex]) {\n this.pickItem(items[this.highlightedIndex])\n break\n }\n }\n // In free-form mode, commit whatever is typed.\n if (this.freeFormValue) {\n this.commitFreeFormTag()\n }\n break\n }\n\n case ',': {\n // Comma immediately commits in free-form mode (keydown fires before char is inserted).\n if (this.freeFormValue && this.inputTarget.value.trim()) {\n event.preventDefault()\n this.commitFreeFormTag()\n }\n break\n }\n\n case 'Escape':\n this.hideDropdown()\n break\n\n case 'Backspace':\n if (this.inputTarget.value === '' && this.multipleValue) {\n const tokens = this.tokensTarget.querySelectorAll<HTMLElement>('[data-value]')\n const last = tokens[tokens.length - 1]\n if (last?.dataset.value) this.deselect(last.dataset.value)\n }\n break\n }\n }\n\n // Splits pasted text on commas and newlines into individual tags (free-form mode only).\n pasteTag (event: ClipboardEvent) {\n if (!this.freeFormValue) return\n const text = event.clipboardData?.getData('text') ?? ''\n const tags = text.split(/[\\n,]+/).map(t => t.trim()).filter(Boolean)\n if (tags.length === 0) return\n event.preventDefault()\n tags.forEach(tag => this.addFreeFormTag(tag))\n }\n\n // Mousedown on the \"Hit Enter to add 'X'\" hint row.\n addFreeFormTagFromHint (event: MouseEvent) {\n event.preventDefault()\n this.commitFreeFormTag()\n }\n\n removeToken (event: MouseEvent) {\n event.stopPropagation()\n const btn = event.currentTarget as HTMLElement\n const value = btn.dataset.value\n if (value) this.deselect(value)\n }\n\n selectOption (event: MouseEvent) {\n event.preventDefault()\n this.pickItem(event.currentTarget as HTMLElement)\n }\n\n // ── Private ─────────────────────────────────────────────────────────────────\n\n private get optionIdPrefix (): string {\n return `ac-opt-${(this.element.dataset.name ?? 'field').replace(/\\W/g, '-')}`\n }\n\n private isSelected (value: string): boolean {\n return !!this.tokensTarget.querySelector(`[data-value=\"${CSS.escape(value)}\"]`)\n }\n\n private resolve (query: string) {\n if (this.hasTurboListTarget) { this.navigateFrame(query); return }\n if (this.urlValue) { this.fetch(query); return }\n this.renderOptions(this.filteredOptions(query))\n }\n\n private filteredOptions (query: string): Option[] {\n const q = query.toLowerCase()\n return (this.optionsValue as Option[]).filter(\n o => q === '' || o.label.toLowerCase().includes(q) || (o.support?.toLowerCase().includes(q) ?? false)\n )\n }\n\n private navigateFrame (query: string) {\n const url = new URL(this.turboFrameUrlValue, window.location.href)\n if (query) url.searchParams.set('q', query)\n this.tokensTarget.querySelectorAll<HTMLInputElement>('input[type=hidden]').forEach(input => {\n url.searchParams.append('selected[]', input.value)\n })\n this.loading = true\n this.turboListTarget.setAttribute('src', url.toString())\n this.showDropdown()\n }\n\n private syncSelectedState () {\n const options = this.turboListTarget.querySelectorAll<HTMLElement>('.midwest-autocomplete-option')\n options.forEach(option => {\n const val = option.dataset.value ?? ''\n option.setAttribute('aria-selected', this.isSelected(val) ? 'true' : 'false')\n })\n\n const visibleCount = this.visibleOptions().length\n this.announce(visibleCount > 0 ? `${visibleCount} option${visibleCount === 1 ? '' : 's'} available` : 'No more options')\n }\n\n private async fetch (query: string) {\n this.fetchAbort?.abort()\n this.fetchAbort = new AbortController()\n\n this.listTarget.replaceChildren()\n this.loading = true\n this.announce('Loading')\n this.showDropdown()\n\n try {\n const url = new URL(this.urlValue, window.location.href)\n if (query) url.searchParams.set('q', query)\n\n const res = await globalThis.fetch(url.toString(), {\n signal: this.fetchAbort.signal,\n headers: { Accept: 'application/json' }\n })\n\n const data: Option[] = await res.json()\n this.renderOptions(data)\n } catch (err) {\n if ((err as Error).name !== 'AbortError') {\n this.renderEmpty()\n }\n }\n }\n\n private select (option: Option) {\n this.appendToken(option)\n this.hideDropdown()\n this.hideFreeFormHint()\n this.inputTarget.value = ''\n this.inputTarget.focus()\n this.announce(`${option.label} selected`)\n\n this.dispatch('change', { detail: { value: option.value, label: option.label } })\n }\n\n private deselect (value: string) {\n const token = this.tokensTarget.querySelector(`[data-value=\"${CSS.escape(value)}\"]`)\n const label = token?.querySelector('.value')?.textContent?.trim()\n token?.remove()\n if (label) this.announce(`${label} removed`)\n this.dispatch('change', { detail: { value: null } })\n }\n\n private appendToken (option: Option) {\n if (!this.multipleValue) {\n this.tokensTarget.querySelectorAll<HTMLElement>('[data-value]').forEach(el => el.remove())\n }\n const template = this.multipleValue ? this.multipleTokenTemplateTarget : this.singleTokenTemplateTarget\n const fragment = template.content.cloneNode(true) as DocumentFragment\n const badge = fragment.querySelector<HTMLElement>('.midwest-badge')\n if (!badge) return\n badge.dataset.value = option.value\n const valueEl = badge.querySelector<HTMLElement>('.value')\n if (valueEl) valueEl.textContent = option.label\n if (this.multipleValue) {\n const removeBtn = badge.querySelector<HTMLElement>('.remove')\n if (removeBtn) {\n removeBtn.dataset.value = option.value\n removeBtn.setAttribute('aria-label', `Remove ${option.label}`)\n }\n }\n const input = badge.querySelector<HTMLInputElement>('input[type=hidden]')\n if (input) {\n input.name = this.element.dataset.name ?? ''\n input.value = option.value\n }\n this.tokensTarget.appendChild(fragment)\n }\n\n private renderOptions (options: Option[]) {\n this.highlightedIndex = -1\n\n if (options.length === 0) {\n this.renderEmpty()\n return\n }\n\n this.listTarget.replaceChildren()\n\n options.forEach((option, index) => {\n const li = document.createElement('li')\n li.id = `${this.optionIdPrefix}-${index}`\n li.className = 'midwest-autocomplete-option'\n li.setAttribute('role', 'option')\n li.setAttribute('aria-selected', this.isSelected(option.value) ? 'true' : 'false')\n li.dataset.value = option.value\n\n const label = document.createElement('span')\n label.className = 'midwest-autocomplete-option-label'\n label.textContent = option.label\n li.appendChild(label)\n\n if (option.support) {\n const support = document.createElement('span')\n support.className = 'midwest-autocomplete-option-support'\n support.textContent = option.support\n li.appendChild(support)\n }\n\n this.listTarget.appendChild(li)\n })\n\n const availableCount = options.filter(o => !this.isSelected(o.value)).length\n if (availableCount === 0) {\n this.renderEmpty('No more options')\n return\n }\n\n // Suggestions are showing — hide the free-form hint so it doesn't double up.\n this.hideFreeFormHint()\n this.loading = false\n this.announce(`${availableCount} option${availableCount === 1 ? '' : 's'} available`)\n this.showDropdown()\n }\n\n private renderEmpty (message = 'No results') {\n // In free-form mode, swap \"No results\" for the actionable hint so the user\n // knows they can press Enter to add whatever they typed.\n if (this.freeFormValue && this.inputTarget.value.trim()) {\n if (this.hasListTarget) this.listTarget.replaceChildren()\n this.loading = false\n this.updateFreeFormHint(this.inputTarget.value.trim())\n return\n }\n\n const li = document.createElement('li')\n li.className = 'midwest-autocomplete-empty'\n li.setAttribute('aria-hidden', 'true')\n li.textContent = message\n this.listTarget.replaceChildren(li)\n this.loading = false\n this.announce(message)\n this.showDropdown()\n }\n\n // Shows or hides the \"Hit Enter to add 'X'\" hint based on the current query.\n private updateFreeFormHint (query: string) {\n if (!this.hasFreeFormHintListTarget) return\n\n if (query && !this.isSelected(query)) {\n if (this.hasFreeFormHintValueTarget) {\n this.freeFormHintValueTarget.textContent = `\"${query}\"`\n }\n this.freeFormHintListTarget.hidden = false\n this.showDropdown()\n } else {\n this.hideFreeFormHint()\n // In pure free-form mode (no suggestions list), close the dropdown when empty.\n if (!this.hasListTarget && !this.hasTurboListTarget) this.hideDropdown()\n }\n }\n\n private hideFreeFormHint () {\n if (this.hasFreeFormHintListTarget) this.freeFormHintListTarget.hidden = true\n }\n\n private set loading (on: boolean) {\n if (this.hasLoaderTarget) this.loaderTarget.hidden = !on\n }\n\n private showDropdown () {\n if (!this.hasDropdownTarget) return\n if (!this.dropdownTarget.matches(':popover-open')) this.dropdownTarget.showPopover()\n }\n\n private hideDropdown () {\n if (!this.hasDropdownTarget) return\n if (this.dropdownTarget.matches(':popover-open')) this.dropdownTarget.hidePopover()\n }\n\n private visibleOptions (): NodeListOf<HTMLElement> {\n const container = this.hasTurboListTarget ? this.turboListTarget : this.listTarget\n return container.querySelectorAll<HTMLElement>('.midwest-autocomplete-option:not([aria-selected=\"true\"])')\n }\n\n private syncHighlight (items: NodeListOf<HTMLElement>) {\n items.forEach((item, i) => item.classList.toggle('is-highlighted', i === this.highlightedIndex))\n const active = items[this.highlightedIndex]\n active?.scrollIntoView({ block: 'nearest' })\n if (active?.id) {\n this.inputTarget.setAttribute('aria-activedescendant', active.id)\n } else {\n this.inputTarget.removeAttribute('aria-activedescendant')\n }\n }\n\n private pickItem (item: HTMLElement) {\n const value = item.dataset.value ?? ''\n const label = item.dataset.label?.trim() || item.querySelector('.midwest-autocomplete-option-label')?.textContent?.trim() || value\n const support = item.querySelector('.midwest-autocomplete-option-support')?.textContent?.trim()\n this.select({ value, label, support })\n }\n\n private commitFreeFormTag () {\n const value = this.inputTarget.value.trim()\n this.addFreeFormTag(value)\n }\n\n private addFreeFormTag (value: string) {\n if (!value) return\n if (this.isSelected(value)) return\n this.select({ value, label: value })\n }\n\n private announce (message: string) {\n if (this.hasStatusTarget) this.statusTarget.textContent = message\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,129 @@
1
+ import { Controller } from '@hotwired/stimulus';
2
+
3
+ class ColorPicker extends Controller {
4
+ static targets = ["input", "swatch", "customSwatch", "colorInput"];
5
+ onCustomColorChange = null;
6
+ get scaleTarget() {
7
+ return this.element.dataset.scaleTarget ?? "theme";
8
+ }
9
+ connect() {
10
+ if (this.hasColorInputTarget) {
11
+ this.onCustomColorChange = (e) => this.handleCustomColor(e);
12
+ this.colorInputTarget.addEventListener("input", this.onCustomColorChange);
13
+ }
14
+ if (this.hasInputTarget && this.inputTarget.value.startsWith("#")) {
15
+ this.restoreCustom(this.inputTarget.value);
16
+ }
17
+ }
18
+ disconnect() {
19
+ if (this.hasColorInputTarget && this.onCustomColorChange) {
20
+ this.colorInputTarget.removeEventListener("input", this.onCustomColorChange);
21
+ this.onCustomColorChange = null;
22
+ }
23
+ }
24
+ select(e) {
25
+ const button = e.currentTarget;
26
+ const color = button.dataset.color ?? "";
27
+ this.applySelection(color);
28
+ this.dispatch("update", { detail: { color, scaleTarget: this.scaleTarget } });
29
+ }
30
+ openCustom(e) {
31
+ e.preventDefault();
32
+ this.colorInputTarget.click();
33
+ }
34
+ handleCustomColor(e) {
35
+ const hex = e.target.value;
36
+ if (this.hasCustomSwatchTarget) {
37
+ this.customSwatchTarget.dataset.color = hex;
38
+ this.customSwatchTarget.style.background = hex;
39
+ }
40
+ const scale = this.generateScale(hex);
41
+ this.applyScale(scale);
42
+ this.applySelection(hex);
43
+ this.dispatch("update", { detail: { color: hex, scale, scaleTarget: this.scaleTarget } });
44
+ }
45
+ restoreCustom(hex) {
46
+ if (this.hasColorInputTarget) {
47
+ this.colorInputTarget.value = hex;
48
+ }
49
+ if (this.hasCustomSwatchTarget) {
50
+ this.customSwatchTarget.style.background = hex;
51
+ }
52
+ const scale = this.generateScale(hex);
53
+ this.applyScale(scale);
54
+ }
55
+ applySelection(color) {
56
+ if (this.hasInputTarget) {
57
+ this.inputTarget.value = color;
58
+ }
59
+ this.swatchTargets.forEach((swatch) => {
60
+ const isSelected = swatch.dataset.color === color;
61
+ swatch.classList.toggle("is-selected", isSelected);
62
+ swatch.setAttribute("aria-pressed", String(isSelected));
63
+ });
64
+ }
65
+ // Generates 13 RGB channel strings (space-separated, matching --{color}-{n}-channels format)
66
+ // from a hex color by fixing the hue/saturation and distributing lightness across the scale.
67
+ generateScale(hex) {
68
+ const [h, s] = this.hexToHsl(hex);
69
+ const lightnessSteps = [97, 90, 82, 74, 65, 56, 48, 40, 33, 26, 18, 10, 3];
70
+ return Object.fromEntries(
71
+ lightnessSteps.map((l, i) => {
72
+ const [r, g, b] = this.hslToRgb(h, s, l);
73
+ return [String(i), `${r} ${g} ${b}`];
74
+ })
75
+ );
76
+ }
77
+ applyScale(scale) {
78
+ const el = this.element;
79
+ Object.entries(scale).forEach(([step, channels]) => {
80
+ el.style.setProperty(`--${this.scaleTarget}-${step}-channels`, channels);
81
+ });
82
+ }
83
+ hexToHsl(hex) {
84
+ const r = parseInt(hex.slice(1, 3), 16) / 255;
85
+ const g = parseInt(hex.slice(3, 5), 16) / 255;
86
+ const b = parseInt(hex.slice(5, 7), 16) / 255;
87
+ const max = Math.max(r, g, b);
88
+ const min = Math.min(r, g, b);
89
+ const l = (max + min) / 2;
90
+ if (max === min)
91
+ return [0, 0, l * 100];
92
+ const d = max - min;
93
+ const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
94
+ const h = max === r ? ((g - b) / d + (g < b ? 6 : 0)) / 6 : max === g ? ((b - r) / d + 2) / 6 : ((r - g) / d + 4) / 6;
95
+ return [h * 360, s * 100, l * 100];
96
+ }
97
+ hslToRgb(h, s, l) {
98
+ h /= 360;
99
+ s /= 100;
100
+ l /= 100;
101
+ if (s === 0) {
102
+ const v = Math.round(l * 255);
103
+ return [v, v, v];
104
+ }
105
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
106
+ const p = 2 * l - q;
107
+ return [
108
+ Math.round(this.hue2rgb(p, q, h + 1 / 3) * 255),
109
+ Math.round(this.hue2rgb(p, q, h) * 255),
110
+ Math.round(this.hue2rgb(p, q, h - 1 / 3) * 255)
111
+ ];
112
+ }
113
+ hue2rgb(p, q, t) {
114
+ if (t < 0)
115
+ t += 1;
116
+ if (t > 1)
117
+ t -= 1;
118
+ if (t < 1 / 6)
119
+ return p + (q - p) * 6 * t;
120
+ if (t < 1 / 2)
121
+ return q;
122
+ if (t < 2 / 3)
123
+ return p + (q - p) * (2 / 3 - t) * 6;
124
+ return p;
125
+ }
126
+ }
127
+
128
+ export { ColorPicker as default };
129
+ //# sourceMappingURL=color_picker_component_controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color_picker_component_controller.js","sources":["../../../../../../../../app/components/midwest/form/color_picker_component/color_picker_component_controller.ts"],"sourcesContent":["import { Controller } from '@hotwired/stimulus'\n\nexport default class ColorPicker extends Controller {\n static targets = ['input', 'swatch', 'customSwatch', 'colorInput']\n\n declare readonly hasInputTarget: boolean\n declare readonly inputTarget: HTMLInputElement\n declare readonly swatchTargets: HTMLElement[]\n declare readonly hasCustomSwatchTarget: boolean\n declare readonly customSwatchTarget: HTMLButtonElement\n declare readonly hasColorInputTarget: boolean\n declare readonly colorInputTarget: HTMLInputElement\n\n private onCustomColorChange: ((e: Event) => void) | null = null\n\n get scaleTarget (): string {\n return (this.element as HTMLElement).dataset.scaleTarget ?? 'theme'\n }\n\n connect () {\n if (this.hasColorInputTarget) {\n this.onCustomColorChange = (e: Event) => this.handleCustomColor(e)\n this.colorInputTarget.addEventListener('input', this.onCustomColorChange)\n }\n\n // Restore state when initial value is a custom hex color\n if (this.hasInputTarget && this.inputTarget.value.startsWith('#')) {\n this.restoreCustom(this.inputTarget.value)\n }\n }\n\n disconnect () {\n if (this.hasColorInputTarget && this.onCustomColorChange) {\n this.colorInputTarget.removeEventListener('input', this.onCustomColorChange)\n this.onCustomColorChange = null\n }\n }\n\n select (e: Event) {\n const button = e.currentTarget as HTMLElement\n const color = button.dataset.color ?? ''\n this.applySelection(color)\n this.dispatch('update', { detail: { color, scaleTarget: this.scaleTarget } })\n }\n\n openCustom (e: Event) {\n e.preventDefault()\n this.colorInputTarget.click()\n }\n\n private handleCustomColor (e: Event) {\n const hex = (e.target as HTMLInputElement).value\n\n if (this.hasCustomSwatchTarget) {\n this.customSwatchTarget.dataset.color = hex\n this.customSwatchTarget.style.background = hex\n }\n\n const scale = this.generateScale(hex)\n this.applyScale(scale)\n this.applySelection(hex)\n this.dispatch('update', { detail: { color: hex, scale, scaleTarget: this.scaleTarget } })\n }\n\n private restoreCustom (hex: string) {\n if (this.hasColorInputTarget) {\n this.colorInputTarget.value = hex\n }\n if (this.hasCustomSwatchTarget) {\n this.customSwatchTarget.style.background = hex\n }\n const scale = this.generateScale(hex)\n this.applyScale(scale)\n }\n\n private applySelection (color: string) {\n if (this.hasInputTarget) {\n this.inputTarget.value = color\n }\n\n this.swatchTargets.forEach(swatch => {\n const isSelected = swatch.dataset.color === color\n swatch.classList.toggle('is-selected', isSelected)\n swatch.setAttribute('aria-pressed', String(isSelected))\n })\n }\n\n // Generates 13 RGB channel strings (space-separated, matching --{color}-{n}-channels format)\n // from a hex color by fixing the hue/saturation and distributing lightness across the scale.\n private generateScale (hex: string): Record<string, string> {\n const [h, s] = this.hexToHsl(hex)\n\n // Lightness curve derived from the built-in named color scales (0 = lightest, 12 = darkest)\n const lightnessSteps = [97, 90, 82, 74, 65, 56, 48, 40, 33, 26, 18, 10, 3]\n\n return Object.fromEntries(\n lightnessSteps.map((l, i) => {\n const [r, g, b] = this.hslToRgb(h, s, l)\n return [String(i), `${r} ${g} ${b}`]\n })\n )\n }\n\n private applyScale (scale: Record<string, string>) {\n const el = this.element as HTMLElement\n Object.entries(scale).forEach(([step, channels]) => {\n el.style.setProperty(`--${this.scaleTarget}-${step}-channels`, channels)\n })\n }\n\n private hexToHsl (hex: string): [number, number, number] {\n const r = parseInt(hex.slice(1, 3), 16) / 255\n const g = parseInt(hex.slice(3, 5), 16) / 255\n const b = parseInt(hex.slice(5, 7), 16) / 255\n\n const max = Math.max(r, g, b)\n const min = Math.min(r, g, b)\n const l = (max + min) / 2\n\n if (max === min) return [0, 0, l * 100]\n\n const d = max - min\n const s = l > 0.5 ? d / (2 - max - min) : d / (max + min)\n\n const h = max === r\n ? ((g - b) / d + (g < b ? 6 : 0)) / 6\n : max === g\n ? ((b - r) / d + 2) / 6\n : ((r - g) / d + 4) / 6\n\n return [h * 360, s * 100, l * 100]\n }\n\n private hslToRgb (h: number, s: number, l: number): [number, number, number] {\n h /= 360\n s /= 100\n l /= 100\n\n if (s === 0) {\n const v = Math.round(l * 255)\n return [v, v, v]\n }\n\n const q = l < 0.5 ? l * (1 + s) : l + s - l * s\n const p = 2 * l - q\n\n return [\n Math.round(this.hue2rgb(p, q, h + 1 / 3) * 255),\n Math.round(this.hue2rgb(p, q, h) * 255),\n Math.round(this.hue2rgb(p, q, h - 1 / 3) * 255)\n ]\n }\n\n private hue2rgb (p: number, q: number, t: number): number {\n if (t < 0) t += 1\n if (t > 1) t -= 1\n if (t < 1 / 6) return p + (q - p) * 6 * t\n if (t < 1 / 2) return q\n if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6\n return p\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1,13 +1,16 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
3
  class Input extends Controller {
4
- static targets = ["input", "counter"];
4
+ static targets = ["input", "counter", "capsLockWarning"];
5
5
  updateClearButton = null;
6
6
  updateCounter = null;
7
+ updateCapsLock = null;
8
+ hideCapsLock = null;
7
9
  connect() {
8
10
  this.applyColor();
9
11
  this.applySearchClear();
10
12
  this.applyCounter();
13
+ this.applyPassword();
11
14
  }
12
15
  disconnect() {
13
16
  if (this.updateClearButton) {
@@ -18,6 +21,38 @@ class Input extends Controller {
18
21
  this.inputTarget.removeEventListener("input", this.updateCounter);
19
22
  this.updateCounter = null;
20
23
  }
24
+ if (this.updateCapsLock) {
25
+ this.inputTarget.removeEventListener("keydown", this.updateCapsLock);
26
+ this.inputTarget.removeEventListener("keyup", this.updateCapsLock);
27
+ this.inputTarget.removeEventListener("mousedown", this.updateCapsLock);
28
+ this.updateCapsLock = null;
29
+ }
30
+ if (this.hideCapsLock) {
31
+ this.inputTarget.removeEventListener("blur", this.hideCapsLock);
32
+ this.hideCapsLock = null;
33
+ }
34
+ }
35
+ increment() {
36
+ const input = this.inputTarget;
37
+ const step = parseFloat(input.step) || 1;
38
+ const max = input.max !== "" ? parseFloat(input.max) : Infinity;
39
+ const newValue = (parseFloat(input.value) || 0) + step;
40
+ if (newValue <= max) {
41
+ input.value = String(newValue);
42
+ input.dispatchEvent(new Event("input", { bubbles: true }));
43
+ input.dispatchEvent(new Event("change", { bubbles: true }));
44
+ }
45
+ }
46
+ decrement() {
47
+ const input = this.inputTarget;
48
+ const step = parseFloat(input.step) || 1;
49
+ const min = input.min !== "" ? parseFloat(input.min) : -Infinity;
50
+ const newValue = (parseFloat(input.value) || 0) - step;
51
+ if (newValue >= min) {
52
+ input.value = String(newValue);
53
+ input.dispatchEvent(new Event("input", { bubbles: true }));
54
+ input.dispatchEvent(new Event("change", { bubbles: true }));
55
+ }
21
56
  }
22
57
  applyColor() {
23
58
  if (this.inputTarget.type !== "color") {
@@ -36,6 +71,20 @@ class Input extends Controller {
36
71
  };
37
72
  this.inputTarget.addEventListener("input", this.updateCounter);
38
73
  }
74
+ applyPassword() {
75
+ if (!this.hasCapsLockWarningTarget)
76
+ return;
77
+ this.updateCapsLock = (e) => {
78
+ this.capsLockWarningTarget.hidden = !e.getModifierState("CapsLock");
79
+ };
80
+ this.hideCapsLock = () => {
81
+ this.capsLockWarningTarget.hidden = true;
82
+ };
83
+ this.inputTarget.addEventListener("keydown", this.updateCapsLock);
84
+ this.inputTarget.addEventListener("keyup", this.updateCapsLock);
85
+ this.inputTarget.addEventListener("mousedown", this.updateCapsLock);
86
+ this.inputTarget.addEventListener("blur", this.hideCapsLock);
87
+ }
39
88
  applySearchClear() {
40
89
  if (this.inputTarget.type !== "search")
41
90
  return;