@unabridged/midwest 0.17.0 → 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.
- package/app/assets/javascript/midwest/index.ts +4 -0
- package/app/assets/javascript/midwest.js +407 -33
- package/app/assets/javascript/midwest.js.map +1 -1
- package/app/assets/stylesheets/midwest.css +1 -1
- package/app/assets/stylesheets/midwest.tailwind.css +4 -1
- package/dist/css/midwest.css +1 -1
- package/dist/javascript/collection/app/assets/javascript/midwest/index.js +4 -0
- package/dist/javascript/collection/app/assets/javascript/midwest/index.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/color_picker_component/color_picker_component_controller.js +135 -0
- package/dist/javascript/collection/app/components/midwest/color_picker_component/color_picker_component_controller.js.map +1 -0
- package/dist/javascript/collection/app/components/midwest/countdown_timer_component/countdown_timer_component_controller.js +27 -6
- package/dist/javascript/collection/app/components/midwest/countdown_timer_component/countdown_timer_component_controller.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/form/autocomplete_component/autocomplete_component_controller.js +109 -21
- package/dist/javascript/collection/app/components/midwest/form/autocomplete_component/autocomplete_component_controller.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/form/color_picker_component/color_picker_component_controller.js +129 -0
- package/dist/javascript/collection/app/components/midwest/form/color_picker_component/color_picker_component_controller.js.map +1 -0
- package/dist/javascript/collection/app/components/midwest/form/input_component/input_component_controller.js +50 -1
- package/dist/javascript/collection/app/components/midwest/form/input_component/input_component_controller.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/form/live_summary_component/live_summary_component_controller.js +38 -5
- package/dist/javascript/collection/app/components/midwest/form/live_summary_component/live_summary_component_controller.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/notification_component/notification_component_controller.js +60 -0
- package/dist/javascript/collection/app/components/midwest/notification_component/notification_component_controller.js.map +1 -0
- package/dist/javascript/midwest.d.ts +5 -0
- package/dist/javascript/midwest.js +407 -33
- package/dist/javascript/midwest.js.map +1 -1
- package/package.json +1 -1
|
@@ -23,6 +23,8 @@ import Range from '../../../components/midwest/form/range_component/range_compon
|
|
|
23
23
|
import Rating from '../../../components/midwest/rating_component/rating_component_controller.js';
|
|
24
24
|
import Autocomplete from '../../../components/midwest/form/autocomplete_component/autocomplete_component_controller.js';
|
|
25
25
|
import Image from '../../../components/midwest/image_component/image_component_controller.js';
|
|
26
|
+
import ColorPicker from '../../../components/midwest/form/color_picker_component/color_picker_component_controller.js';
|
|
27
|
+
import Notification from '../../../components/midwest/notification_component/notification_component_controller.js';
|
|
26
28
|
|
|
27
29
|
function registerMidwestControllers(application) {
|
|
28
30
|
application.register("midwest-card", Card);
|
|
@@ -46,6 +48,8 @@ function registerMidwestControllers(application) {
|
|
|
46
48
|
application.register("midwest-rating", Rating);
|
|
47
49
|
application.register("midwest-autocomplete", Autocomplete);
|
|
48
50
|
application.register("midwest-image", Image);
|
|
51
|
+
application.register("midwest-color-picker", ColorPicker);
|
|
52
|
+
application.register("midwest-notification", Notification);
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
export { registerMidwestControllers };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../../../../../app/assets/javascript/midwest/index.ts"],"sourcesContent":["import Card from '../../../components/midwest/card_component/card_component_controller'\nimport Banner from '../../../components/midwest/banner_component/banner_component_controller'\nimport Input from '../../../components/midwest/form/input_component/input_component_controller'\nimport File from '../../../components/midwest/form/file_component/file_component_controller'\nimport Dropdown from '../../../components/midwest/dropdown_component/dropdown_component_controller'\nimport Prose from '../../../components/midwest/prose_component/prose_component_controller'\nimport Dialog from '../../../components/midwest/dialog_component/dialog_component_controller'\nimport Tabs from '../../../components/midwest/tabs_component/tabs_component_controller'\nimport Tooltip from '../../../components/midwest/tooltip_component/tooltip_component_controller'\nimport Carousel from '../../../components/midwest/carousel_component/carousel_component_controller'\nimport FormLiveSummary from '../../../components/midwest/form/live_summary_component/live_summary_component_controller'\nimport Form from '../../../components/midwest/form/form_controller'\nimport CountdownTimer from '../../../components/midwest/countdown_timer_component/countdown_timer_component_controller'\nimport Badge from '../../../components/midwest/badge_component/badge_component_controller'\nimport Confirmation from '../../../components/midwest/confirmation_component/confirmation_component_controller'\nimport AvatarGroup from '../../../components/midwest/avatar_group_component/avatar_group_component_controller'\nimport Chart from '../../../components/midwest/chart_component/chart_component_controller'\nimport Range from '../../../components/midwest/form/range_component/range_component_controller'\nimport Rating from '../../../components/midwest/rating_component/rating_component_controller'\nimport Autocomplete from '../../../components/midwest/form/autocomplete_component/autocomplete_component_controller'\nimport Image from '../../../components/midwest/image_component/image_component_controller'\n/* IMPORTS */\n\nexport { Card, Banner, CountdownTimer, Chart }\n\nexport function registerMidwestControllers (application: any) {\n application.register('midwest-card', Card)\n application.register('midwest-banner', Banner)\n application.register('midwest-countdown-timer', CountdownTimer)\n application.register('midwest-input', Input)\n application.register('midwest-file', File)\n application.register('midwest-dropdown', Dropdown)\n application.register('midwest-prose', Prose)\n application.register('midwest-dialog', Dialog)\n application.register('midwest-tabs', Tabs)\n application.register('midwest-tooltip', Tooltip)\n application.register('midwest-carousel', Carousel)\n application.register('midwest-form-live-summary', FormLiveSummary)\n application.register('midwest-form', Form)\n application.register('midwest-badge', Badge)\n application.register('midwest-confirmation', Confirmation)\n application.register('midwest-avatar-group', AvatarGroup)\n application.register('midwest-chart', Chart)\n application.register('midwest-range', Range)\n application.register('midwest-rating', Rating)\n application.register('midwest-autocomplete', Autocomplete)\n application.register('midwest-image', Image)\n /* EXPORTS */\n}\n"],"names":["Confirmation"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../../../../app/assets/javascript/midwest/index.ts"],"sourcesContent":["import Card from '../../../components/midwest/card_component/card_component_controller'\nimport Banner from '../../../components/midwest/banner_component/banner_component_controller'\nimport Input from '../../../components/midwest/form/input_component/input_component_controller'\nimport File from '../../../components/midwest/form/file_component/file_component_controller'\nimport Dropdown from '../../../components/midwest/dropdown_component/dropdown_component_controller'\nimport Prose from '../../../components/midwest/prose_component/prose_component_controller'\nimport Dialog from '../../../components/midwest/dialog_component/dialog_component_controller'\nimport Tabs from '../../../components/midwest/tabs_component/tabs_component_controller'\nimport Tooltip from '../../../components/midwest/tooltip_component/tooltip_component_controller'\nimport Carousel from '../../../components/midwest/carousel_component/carousel_component_controller'\nimport FormLiveSummary from '../../../components/midwest/form/live_summary_component/live_summary_component_controller'\nimport Form from '../../../components/midwest/form/form_controller'\nimport CountdownTimer from '../../../components/midwest/countdown_timer_component/countdown_timer_component_controller'\nimport Badge from '../../../components/midwest/badge_component/badge_component_controller'\nimport Confirmation from '../../../components/midwest/confirmation_component/confirmation_component_controller'\nimport AvatarGroup from '../../../components/midwest/avatar_group_component/avatar_group_component_controller'\nimport Chart from '../../../components/midwest/chart_component/chart_component_controller'\nimport Range from '../../../components/midwest/form/range_component/range_component_controller'\nimport Rating from '../../../components/midwest/rating_component/rating_component_controller'\nimport Autocomplete from '../../../components/midwest/form/autocomplete_component/autocomplete_component_controller'\nimport Image from '../../../components/midwest/image_component/image_component_controller'\nimport ColorPicker from '../../../components/midwest/form/color_picker_component/color_picker_component_controller'\nimport Notification from '../../../components/midwest/notification_component/notification_component_controller'\n/* IMPORTS */\n\nexport { Card, Banner, CountdownTimer, Chart }\n\nexport function registerMidwestControllers (application: any) {\n application.register('midwest-card', Card)\n application.register('midwest-banner', Banner)\n application.register('midwest-countdown-timer', CountdownTimer)\n application.register('midwest-input', Input)\n application.register('midwest-file', File)\n application.register('midwest-dropdown', Dropdown)\n application.register('midwest-prose', Prose)\n application.register('midwest-dialog', Dialog)\n application.register('midwest-tabs', Tabs)\n application.register('midwest-tooltip', Tooltip)\n application.register('midwest-carousel', Carousel)\n application.register('midwest-form-live-summary', FormLiveSummary)\n application.register('midwest-form', Form)\n application.register('midwest-badge', Badge)\n application.register('midwest-confirmation', Confirmation)\n application.register('midwest-avatar-group', AvatarGroup)\n application.register('midwest-chart', Chart)\n application.register('midwest-range', Range)\n application.register('midwest-rating', Rating)\n application.register('midwest-autocomplete', Autocomplete)\n application.register('midwest-image', Image)\n application.register('midwest-color-picker', ColorPicker)\n application.register('midwest-notification', Notification)\n /* EXPORTS */\n}\n"],"names":["Confirmation"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BO,SAAS,2BAA4B,WAAkB,EAAA;AAC5D,EAAY,WAAA,CAAA,QAAA,CAAS,gBAAgB,IAAI,CAAA,CAAA;AACzC,EAAY,WAAA,CAAA,QAAA,CAAS,kBAAkB,MAAM,CAAA,CAAA;AAC7C,EAAY,WAAA,CAAA,QAAA,CAAS,2BAA2B,cAAc,CAAA,CAAA;AAC9D,EAAY,WAAA,CAAA,QAAA,CAAS,iBAAiB,KAAK,CAAA,CAAA;AAC3C,EAAY,WAAA,CAAA,QAAA,CAAS,gBAAgB,IAAI,CAAA,CAAA;AACzC,EAAY,WAAA,CAAA,QAAA,CAAS,oBAAoB,QAAQ,CAAA,CAAA;AACjD,EAAY,WAAA,CAAA,QAAA,CAAS,iBAAiB,KAAK,CAAA,CAAA;AAC3C,EAAY,WAAA,CAAA,QAAA,CAAS,kBAAkB,MAAM,CAAA,CAAA;AAC7C,EAAY,WAAA,CAAA,QAAA,CAAS,gBAAgB,IAAI,CAAA,CAAA;AACzC,EAAY,WAAA,CAAA,QAAA,CAAS,mBAAmB,OAAO,CAAA,CAAA;AAC/C,EAAY,WAAA,CAAA,QAAA,CAAS,oBAAoB,QAAQ,CAAA,CAAA;AACjD,EAAY,WAAA,CAAA,QAAA,CAAS,6BAA6B,eAAe,CAAA,CAAA;AACjE,EAAY,WAAA,CAAA,QAAA,CAAS,gBAAgB,IAAI,CAAA,CAAA;AACzC,EAAY,WAAA,CAAA,QAAA,CAAS,iBAAiB,KAAK,CAAA,CAAA;AAC3C,EAAY,WAAA,CAAA,QAAA,CAAS,wBAAwBA,sBAAY,CAAA,CAAA;AACzD,EAAY,WAAA,CAAA,QAAA,CAAS,wBAAwB,WAAW,CAAA,CAAA;AACxD,EAAY,WAAA,CAAA,QAAA,CAAS,iBAAiB,KAAK,CAAA,CAAA;AAC3C,EAAY,WAAA,CAAA,QAAA,CAAS,iBAAiB,KAAK,CAAA,CAAA;AAC3C,EAAY,WAAA,CAAA,QAAA,CAAS,kBAAkB,MAAM,CAAA,CAAA;AAC7C,EAAY,WAAA,CAAA,QAAA,CAAS,wBAAwB,YAAY,CAAA,CAAA;AACzD,EAAY,WAAA,CAAA,QAAA,CAAS,iBAAiB,KAAK,CAAA,CAAA;AAC3C,EAAY,WAAA,CAAA,QAAA,CAAS,wBAAwB,WAAW,CAAA,CAAA;AACxD,EAAY,WAAA,CAAA,QAAA,CAAS,wBAAwB,YAAY,CAAA,CAAA;AAE3D;;;;"}
|
|
@@ -0,0 +1,135 @@
|
|
|
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
|
+
let h = 0;
|
|
95
|
+
if (max === r)
|
|
96
|
+
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
97
|
+
else if (max === g)
|
|
98
|
+
h = ((b - r) / d + 2) / 6;
|
|
99
|
+
else
|
|
100
|
+
h = ((r - g) / d + 4) / 6;
|
|
101
|
+
return [h * 360, s * 100, l * 100];
|
|
102
|
+
}
|
|
103
|
+
hslToRgb(h, s, l) {
|
|
104
|
+
h /= 360;
|
|
105
|
+
s /= 100;
|
|
106
|
+
l /= 100;
|
|
107
|
+
if (s === 0) {
|
|
108
|
+
const v = Math.round(l * 255);
|
|
109
|
+
return [v, v, v];
|
|
110
|
+
}
|
|
111
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
112
|
+
const p = 2 * l - q;
|
|
113
|
+
return [
|
|
114
|
+
Math.round(this.hue2rgb(p, q, h + 1 / 3) * 255),
|
|
115
|
+
Math.round(this.hue2rgb(p, q, h) * 255),
|
|
116
|
+
Math.round(this.hue2rgb(p, q, h - 1 / 3) * 255)
|
|
117
|
+
];
|
|
118
|
+
}
|
|
119
|
+
hue2rgb(p, q, t) {
|
|
120
|
+
if (t < 0)
|
|
121
|
+
t += 1;
|
|
122
|
+
if (t > 1)
|
|
123
|
+
t -= 1;
|
|
124
|
+
if (t < 1 / 6)
|
|
125
|
+
return p + (q - p) * 6 * t;
|
|
126
|
+
if (t < 1 / 2)
|
|
127
|
+
return q;
|
|
128
|
+
if (t < 2 / 3)
|
|
129
|
+
return p + (q - p) * (2 / 3 - t) * 6;
|
|
130
|
+
return p;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export { ColorPicker as default };
|
|
135
|
+
//# sourceMappingURL=color_picker_component_controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"color_picker_component_controller.js","sources":["../../../../../../../app/components/midwest/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 let h = 0\n if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6\n else if (max === g) h = ((b - r) / d + 2) / 6\n else h = ((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":";;AAEA,MAAqB,oBAAoB,UAAW,CAAA;AAAA,EAClD,OAAO,OAAU,GAAA,CAAC,OAAS,EAAA,QAAA,EAAU,gBAAgB,YAAY,CAAA,CAAA;AAAA,EAUzD,mBAAmD,GAAA,IAAA,CAAA;AAAA,EAE3D,IAAI,WAAuB,GAAA;AACzB,IAAQ,OAAA,IAAA,CAAK,OAAwB,CAAA,OAAA,CAAQ,WAAe,IAAA,OAAA,CAAA;AAAA,GAC9D;AAAA,EAEA,OAAW,GAAA;AACT,IAAA,IAAI,KAAK,mBAAqB,EAAA;AAC5B,MAAA,IAAA,CAAK,mBAAsB,GAAA,CAAC,CAAa,KAAA,IAAA,CAAK,kBAAkB,CAAC,CAAA,CAAA;AACjE,MAAA,IAAA,CAAK,gBAAiB,CAAA,gBAAA,CAAiB,OAAS,EAAA,IAAA,CAAK,mBAAmB,CAAA,CAAA;AAAA,KAC1E;AAGA,IAAA,IAAI,KAAK,cAAkB,IAAA,IAAA,CAAK,YAAY,KAAM,CAAA,UAAA,CAAW,GAAG,CAAG,EAAA;AACjE,MAAK,IAAA,CAAA,aAAA,CAAc,IAAK,CAAA,WAAA,CAAY,KAAK,CAAA,CAAA;AAAA,KAC3C;AAAA,GACF;AAAA,EAEA,UAAc,GAAA;AACZ,IAAI,IAAA,IAAA,CAAK,mBAAuB,IAAA,IAAA,CAAK,mBAAqB,EAAA;AACxD,MAAA,IAAA,CAAK,gBAAiB,CAAA,mBAAA,CAAoB,OAAS,EAAA,IAAA,CAAK,mBAAmB,CAAA,CAAA;AAC3E,MAAA,IAAA,CAAK,mBAAsB,GAAA,IAAA,CAAA;AAAA,KAC7B;AAAA,GACF;AAAA,EAEA,OAAQ,CAAU,EAAA;AAChB,IAAA,MAAM,SAAS,CAAE,CAAA,aAAA,CAAA;AACjB,IAAM,MAAA,KAAA,GAAQ,MAAO,CAAA,OAAA,CAAQ,KAAS,IAAA,EAAA,CAAA;AACtC,IAAA,IAAA,CAAK,eAAe,KAAK,CAAA,CAAA;AACzB,IAAK,IAAA,CAAA,QAAA,CAAS,QAAU,EAAA,EAAE,MAAQ,EAAA,EAAE,OAAO,WAAa,EAAA,IAAA,CAAK,WAAY,EAAA,EAAG,CAAA,CAAA;AAAA,GAC9E;AAAA,EAEA,WAAY,CAAU,EAAA;AACpB,IAAA,CAAA,CAAE,cAAe,EAAA,CAAA;AACjB,IAAA,IAAA,CAAK,iBAAiB,KAAM,EAAA,CAAA;AAAA,GAC9B;AAAA,EAEQ,kBAAmB,CAAU,EAAA;AACnC,IAAM,MAAA,GAAA,GAAO,EAAE,MAA4B,CAAA,KAAA,CAAA;AAE3C,IAAA,IAAI,KAAK,qBAAuB,EAAA;AAC9B,MAAK,IAAA,CAAA,kBAAA,CAAmB,QAAQ,KAAQ,GAAA,GAAA,CAAA;AACxC,MAAK,IAAA,CAAA,kBAAA,CAAmB,MAAM,UAAa,GAAA,GAAA,CAAA;AAAA,KAC7C;AAEA,IAAM,MAAA,KAAA,GAAQ,IAAK,CAAA,aAAA,CAAc,GAAG,CAAA,CAAA;AACpC,IAAA,IAAA,CAAK,WAAW,KAAK,CAAA,CAAA;AACrB,IAAA,IAAA,CAAK,eAAe,GAAG,CAAA,CAAA;AACvB,IAAA,IAAA,CAAK,QAAS,CAAA,QAAA,EAAU,EAAE,MAAA,EAAQ,EAAE,KAAA,EAAO,GAAK,EAAA,KAAA,EAAO,WAAa,EAAA,IAAA,CAAK,WAAY,EAAA,EAAG,CAAA,CAAA;AAAA,GAC1F;AAAA,EAEQ,cAAe,GAAa,EAAA;AAClC,IAAA,IAAI,KAAK,mBAAqB,EAAA;AAC5B,MAAA,IAAA,CAAK,iBAAiB,KAAQ,GAAA,GAAA,CAAA;AAAA,KAChC;AACA,IAAA,IAAI,KAAK,qBAAuB,EAAA;AAC9B,MAAK,IAAA,CAAA,kBAAA,CAAmB,MAAM,UAAa,GAAA,GAAA,CAAA;AAAA,KAC7C;AACA,IAAM,MAAA,KAAA,GAAQ,IAAK,CAAA,aAAA,CAAc,GAAG,CAAA,CAAA;AACpC,IAAA,IAAA,CAAK,WAAW,KAAK,CAAA,CAAA;AAAA,GACvB;AAAA,EAEQ,eAAgB,KAAe,EAAA;AACrC,IAAA,IAAI,KAAK,cAAgB,EAAA;AACvB,MAAA,IAAA,CAAK,YAAY,KAAQ,GAAA,KAAA,CAAA;AAAA,KAC3B;AAEA,IAAK,IAAA,CAAA,aAAA,CAAc,QAAQ,CAAU,MAAA,KAAA;AACnC,MAAM,MAAA,UAAA,GAAa,MAAO,CAAA,OAAA,CAAQ,KAAU,KAAA,KAAA,CAAA;AAC5C,MAAO,MAAA,CAAA,SAAA,CAAU,MAAO,CAAA,aAAA,EAAe,UAAU,CAAA,CAAA;AACjD,MAAA,MAAA,CAAO,YAAa,CAAA,cAAA,EAAgB,MAAO,CAAA,UAAU,CAAC,CAAA,CAAA;AAAA,KACvD,CAAA,CAAA;AAAA,GACH;AAAA;AAAA;AAAA,EAIQ,cAAe,GAAqC,EAAA;AAC1D,IAAA,MAAM,CAAC,CAAG,EAAA,CAAC,CAAI,GAAA,IAAA,CAAK,SAAS,GAAG,CAAA,CAAA;AAGhC,IAAA,MAAM,cAAiB,GAAA,CAAC,EAAI,EAAA,EAAA,EAAI,IAAI,EAAI,EAAA,EAAA,EAAI,EAAI,EAAA,EAAA,EAAI,EAAI,EAAA,EAAA,EAAI,EAAI,EAAA,EAAA,EAAI,IAAI,CAAC,CAAA,CAAA;AAEzE,IAAA,OAAO,MAAO,CAAA,WAAA;AAAA,MACZ,cAAe,CAAA,GAAA,CAAI,CAAC,CAAA,EAAG,CAAM,KAAA;AAC3B,QAAM,MAAA,CAAC,GAAG,CAAG,EAAA,CAAC,IAAI,IAAK,CAAA,QAAA,CAAS,CAAG,EAAA,CAAA,EAAG,CAAC,CAAA,CAAA;AACvC,QAAA,OAAO,CAAC,MAAO,CAAA,CAAC,GAAG,CAAG,EAAA,CAAA,CAAA,CAAA,EAAK,KAAK,CAAG,CAAA,CAAA,CAAA,CAAA;AAAA,OACpC,CAAA;AAAA,KACH,CAAA;AAAA,GACF;AAAA,EAEQ,WAAY,KAA+B,EAAA;AACjD,IAAA,MAAM,KAAK,IAAK,CAAA,OAAA,CAAA;AAChB,IAAO,MAAA,CAAA,OAAA,CAAQ,KAAK,CAAE,CAAA,OAAA,CAAQ,CAAC,CAAC,IAAA,EAAM,QAAQ,CAAM,KAAA;AAClD,MAAA,EAAA,CAAG,MAAM,WAAY,CAAA,CAAA,EAAA,EAAK,IAAK,CAAA,WAAA,CAAA,CAAA,EAAe,iBAAiB,QAAQ,CAAA,CAAA;AAAA,KACxE,CAAA,CAAA;AAAA,GACH;AAAA,EAEQ,SAAU,GAAuC,EAAA;AACvD,IAAM,MAAA,CAAA,GAAI,SAAS,GAAI,CAAA,KAAA,CAAM,GAAG,CAAC,CAAA,EAAG,EAAE,CAAI,GAAA,GAAA,CAAA;AAC1C,IAAM,MAAA,CAAA,GAAI,SAAS,GAAI,CAAA,KAAA,CAAM,GAAG,CAAC,CAAA,EAAG,EAAE,CAAI,GAAA,GAAA,CAAA;AAC1C,IAAM,MAAA,CAAA,GAAI,SAAS,GAAI,CAAA,KAAA,CAAM,GAAG,CAAC,CAAA,EAAG,EAAE,CAAI,GAAA,GAAA,CAAA;AAE1C,IAAA,MAAM,GAAM,GAAA,IAAA,CAAK,GAAI,CAAA,CAAA,EAAG,GAAG,CAAC,CAAA,CAAA;AAC5B,IAAA,MAAM,GAAM,GAAA,IAAA,CAAK,GAAI,CAAA,CAAA,EAAG,GAAG,CAAC,CAAA,CAAA;AAC5B,IAAM,MAAA,CAAA,GAAA,CAAK,MAAM,GAAO,IAAA,CAAA,CAAA;AAExB,IAAA,IAAI,GAAQ,KAAA,GAAA;AAAK,MAAA,OAAO,CAAC,CAAA,EAAG,CAAG,EAAA,CAAA,GAAI,GAAG,CAAA,CAAA;AAEtC,IAAA,MAAM,IAAI,GAAM,GAAA,GAAA,CAAA;AAChB,IAAM,MAAA,CAAA,GAAI,IAAI,GAAM,GAAA,CAAA,IAAK,IAAI,GAAM,GAAA,GAAA,CAAA,GAAO,KAAK,GAAM,GAAA,GAAA,CAAA,CAAA;AAErD,IAAA,IAAI,CAAI,GAAA,CAAA,CAAA;AACR,IAAA,IAAI,GAAQ,KAAA,CAAA;AAAG,MAAA,CAAA,GAAA,CAAA,CAAM,IAAI,CAAK,IAAA,CAAA,IAAK,CAAI,GAAA,CAAA,GAAI,IAAI,CAAM,CAAA,IAAA,CAAA,CAAA;AAAA,SAAA,IAC5C,GAAQ,KAAA,CAAA;AAAG,MAAM,CAAA,GAAA,CAAA,CAAA,CAAA,GAAI,CAAK,IAAA,CAAA,GAAI,CAAK,IAAA,CAAA,CAAA;AAAA;AACvC,MAAM,CAAA,GAAA,CAAA,CAAA,CAAA,GAAI,CAAK,IAAA,CAAA,GAAI,CAAK,IAAA,CAAA,CAAA;AAE7B,IAAA,OAAO,CAAC,CAAI,GAAA,GAAA,EAAK,CAAI,GAAA,GAAA,EAAK,IAAI,GAAG,CAAA,CAAA;AAAA,GACnC;AAAA,EAEQ,QAAA,CAAU,CAAW,EAAA,CAAA,EAAW,CAAqC,EAAA;AAC3E,IAAK,CAAA,IAAA,GAAA,CAAA;AACL,IAAK,CAAA,IAAA,GAAA,CAAA;AACL,IAAK,CAAA,IAAA,GAAA,CAAA;AAEL,IAAA,IAAI,MAAM,CAAG,EAAA;AACX,MAAA,MAAM,CAAI,GAAA,IAAA,CAAK,KAAM,CAAA,CAAA,GAAI,GAAG,CAAA,CAAA;AAC5B,MAAO,OAAA,CAAC,CAAG,EAAA,CAAA,EAAG,CAAC,CAAA,CAAA;AAAA,KACjB;AAEA,IAAM,MAAA,CAAA,GAAI,IAAI,GAAM,GAAA,CAAA,IAAK,IAAI,CAAK,CAAA,GAAA,CAAA,GAAI,IAAI,CAAI,GAAA,CAAA,CAAA;AAC9C,IAAM,MAAA,CAAA,GAAI,IAAI,CAAI,GAAA,CAAA,CAAA;AAElB,IAAO,OAAA;AAAA,MACL,IAAA,CAAK,KAAM,CAAA,IAAA,CAAK,OAAQ,CAAA,CAAA,EAAG,GAAG,CAAI,GAAA,CAAA,GAAI,CAAC,CAAA,GAAI,GAAG,CAAA;AAAA,MAC9C,IAAA,CAAK,MAAM,IAAK,CAAA,OAAA,CAAQ,GAAG,CAAG,EAAA,CAAC,IAAI,GAAG,CAAA;AAAA,MACtC,IAAA,CAAK,KAAM,CAAA,IAAA,CAAK,OAAQ,CAAA,CAAA,EAAG,GAAG,CAAI,GAAA,CAAA,GAAI,CAAC,CAAA,GAAI,GAAG,CAAA;AAAA,KAChD,CAAA;AAAA,GACF;AAAA,EAEQ,OAAA,CAAS,CAAW,EAAA,CAAA,EAAW,CAAmB,EAAA;AACxD,IAAA,IAAI,CAAI,GAAA,CAAA;AAAG,MAAK,CAAA,IAAA,CAAA,CAAA;AAChB,IAAA,IAAI,CAAI,GAAA,CAAA;AAAG,MAAK,CAAA,IAAA,CAAA,CAAA;AAChB,IAAA,IAAI,IAAI,CAAI,GAAA,CAAA;AAAG,MAAO,OAAA,CAAA,GAAA,CAAK,CAAI,GAAA,CAAA,IAAK,CAAI,GAAA,CAAA,CAAA;AACxC,IAAA,IAAI,IAAI,CAAI,GAAA,CAAA;AAAG,MAAO,OAAA,CAAA,CAAA;AACtB,IAAA,IAAI,IAAI,CAAI,GAAA,CAAA;AAAG,MAAA,OAAO,CAAK,GAAA,CAAA,CAAA,GAAI,CAAM,KAAA,CAAA,GAAI,IAAI,CAAK,CAAA,GAAA,CAAA,CAAA;AAClD,IAAO,OAAA,CAAA,CAAA;AAAA,GACT;AACF;;;;"}
|
|
@@ -5,16 +5,37 @@ class CountdownTimer extends Controller {
|
|
|
5
5
|
seconds: { type: Number, default: 5 }
|
|
6
6
|
};
|
|
7
7
|
timer = null;
|
|
8
|
+
remaining = 0;
|
|
9
|
+
startTime = 0;
|
|
8
10
|
connect() {
|
|
9
|
-
this.
|
|
10
|
-
|
|
11
|
-
}, this.secondsValue * 1e3);
|
|
11
|
+
this.remaining = this.secondsValue * 1e3;
|
|
12
|
+
this.resume();
|
|
12
13
|
}
|
|
13
14
|
disconnect() {
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
this.pause();
|
|
16
|
+
}
|
|
17
|
+
pause() {
|
|
18
|
+
if (this.timer === null)
|
|
19
|
+
return;
|
|
20
|
+
clearTimeout(this.timer);
|
|
21
|
+
this.timer = null;
|
|
22
|
+
this.remaining = Math.max(0, this.remaining - (Date.now() - this.startTime));
|
|
23
|
+
this.setPlayState("paused");
|
|
24
|
+
}
|
|
25
|
+
resume() {
|
|
26
|
+
if (this.timer !== null)
|
|
27
|
+
return;
|
|
28
|
+
this.startTime = Date.now();
|
|
29
|
+
this.timer = setTimeout(() => {
|
|
16
30
|
this.timer = null;
|
|
17
|
-
|
|
31
|
+
this.dispatch("complete", { bubbles: true });
|
|
32
|
+
}, this.remaining);
|
|
33
|
+
this.setPlayState("running");
|
|
34
|
+
}
|
|
35
|
+
setPlayState(state) {
|
|
36
|
+
const progress = this.element.querySelector(".midwest-countdown-timer__progress");
|
|
37
|
+
if (progress)
|
|
38
|
+
progress.style.animationPlayState = state;
|
|
18
39
|
}
|
|
19
40
|
}
|
|
20
41
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"countdown_timer_component_controller.js","sources":["../../../../../../../app/components/midwest/countdown_timer_component/countdown_timer_component_controller.ts"],"sourcesContent":["import { Controller } from '@hotwired/stimulus'\n\nexport default class CountdownTimer extends Controller {\n static values = {\n seconds: { type: Number, default: 5 }\n }\n\n declare secondsValue: number\n\n private timer: ReturnType<typeof setTimeout> | null = null\n\n connect () {\n this.
|
|
1
|
+
{"version":3,"file":"countdown_timer_component_controller.js","sources":["../../../../../../../app/components/midwest/countdown_timer_component/countdown_timer_component_controller.ts"],"sourcesContent":["import { Controller } from '@hotwired/stimulus'\n\nexport default class CountdownTimer extends Controller {\n static values = {\n seconds: { type: Number, default: 5 }\n }\n\n declare secondsValue: number\n\n private timer: ReturnType<typeof setTimeout> | null = null\n private remaining = 0\n private startTime = 0\n\n connect () {\n this.remaining = this.secondsValue * 1000\n this.resume()\n }\n\n disconnect () {\n this.pause()\n }\n\n pause () {\n if (this.timer === null) return\n clearTimeout(this.timer)\n this.timer = null\n this.remaining = Math.max(0, this.remaining - (Date.now() - this.startTime))\n this.setPlayState('paused')\n }\n\n resume () {\n if (this.timer !== null) return // already running\n this.startTime = Date.now()\n this.timer = setTimeout(() => {\n this.timer = null\n this.dispatch('complete', { bubbles: true })\n }, this.remaining)\n this.setPlayState('running')\n }\n\n private setPlayState (state: 'running' | 'paused') {\n const progress = this.element.querySelector<SVGElement>('.midwest-countdown-timer__progress')\n if (progress) progress.style.animationPlayState = state\n }\n}\n"],"names":[],"mappings":";;AAEA,MAAqB,uBAAuB,UAAW,CAAA;AAAA,EACrD,OAAO,MAAS,GAAA;AAAA,IACd,OAAS,EAAA,EAAE,IAAM,EAAA,MAAA,EAAQ,SAAS,CAAE,EAAA;AAAA,GACtC,CAAA;AAAA,EAIQ,KAA8C,GAAA,IAAA,CAAA;AAAA,EAC9C,SAAY,GAAA,CAAA,CAAA;AAAA,EACZ,SAAY,GAAA,CAAA,CAAA;AAAA,EAEpB,OAAW,GAAA;AACT,IAAK,IAAA,CAAA,SAAA,GAAY,KAAK,YAAe,GAAA,GAAA,CAAA;AACrC,IAAA,IAAA,CAAK,MAAO,EAAA,CAAA;AAAA,GACd;AAAA,EAEA,UAAc,GAAA;AACZ,IAAA,IAAA,CAAK,KAAM,EAAA,CAAA;AAAA,GACb;AAAA,EAEA,KAAS,GAAA;AACP,IAAA,IAAI,KAAK,KAAU,KAAA,IAAA;AAAM,MAAA,OAAA;AACzB,IAAA,YAAA,CAAa,KAAK,KAAK,CAAA,CAAA;AACvB,IAAA,IAAA,CAAK,KAAQ,GAAA,IAAA,CAAA;AACb,IAAK,IAAA,CAAA,SAAA,GAAY,IAAK,CAAA,GAAA,CAAI,CAAG,EAAA,IAAA,CAAK,aAAa,IAAK,CAAA,GAAA,EAAQ,GAAA,IAAA,CAAK,SAAU,CAAA,CAAA,CAAA;AAC3E,IAAA,IAAA,CAAK,aAAa,QAAQ,CAAA,CAAA;AAAA,GAC5B;AAAA,EAEA,MAAU,GAAA;AACR,IAAA,IAAI,KAAK,KAAU,KAAA,IAAA;AAAM,MAAA,OAAA;AACzB,IAAK,IAAA,CAAA,SAAA,GAAY,KAAK,GAAI,EAAA,CAAA;AAC1B,IAAK,IAAA,CAAA,KAAA,GAAQ,WAAW,MAAM;AAC5B,MAAA,IAAA,CAAK,KAAQ,GAAA,IAAA,CAAA;AACb,MAAA,IAAA,CAAK,QAAS,CAAA,UAAA,EAAY,EAAE,OAAA,EAAS,MAAM,CAAA,CAAA;AAAA,KAC7C,EAAG,KAAK,SAAS,CAAA,CAAA;AACjB,IAAA,IAAA,CAAK,aAAa,SAAS,CAAA,CAAA;AAAA,GAC7B;AAAA,EAEQ,aAAc,KAA6B,EAAA;AACjD,IAAA,MAAM,QAAW,GAAA,IAAA,CAAK,OAAQ,CAAA,aAAA,CAA0B,oCAAoC,CAAA,CAAA;AAC5F,IAAI,IAAA,QAAA;AAAU,MAAA,QAAA,CAAS,MAAM,kBAAqB,GAAA,KAAA,CAAA;AAAA,GACpD;AACF;;;;"}
|
|
@@ -1,19 +1,30 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
3
|
class Autocomplete extends Controller {
|
|
4
|
-
static targets = [
|
|
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(
|
|
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 (
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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;
|