@unabridged/midwest 0.17.0 → 0.19.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/README.md +4 -2
- package/app/assets/javascript/midwest/index.ts +10 -0
- package/app/assets/javascript/midwest.js +733 -603
- package/app/assets/javascript/midwest.js.map +1 -1
- package/app/assets/stylesheets/midwest/form-group.css +4 -0
- package/app/assets/stylesheets/midwest.css +1 -1
- package/app/assets/stylesheets/midwest.tailwind.css +7 -1
- package/dist/css/midwest.css +1 -1
- package/dist/javascript/collection/app/assets/javascript/midwest/index.js +11 -5
- package/dist/javascript/collection/app/assets/javascript/midwest/index.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/carousel_component/carousel_component_controller.js +106 -6
- package/dist/javascript/collection/app/components/midwest/carousel_component/carousel_component_controller.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/dropdown_component/dropdown_component_controller.js +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 +165 -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/horizontal_scroll_component/horizontal_scroll_component_controller.js +24 -0
- package/dist/javascript/collection/app/components/midwest/horizontal_scroll_component/horizontal_scroll_component_controller.js.map +1 -0
- 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/collection/app/components/midwest/table_component/load_more_controller.js +60 -0
- package/dist/javascript/collection/app/components/midwest/table_component/load_more_controller.js.map +1 -0
- package/dist/javascript/collection/app/components/midwest/table_component/table_component_controller.js +30 -0
- package/dist/javascript/collection/app/components/midwest/table_component/table_component_controller.js.map +1 -0
- package/dist/javascript/collection/node_modules/@floating-ui/core/dist/floating-ui.core.js +561 -0
- package/dist/javascript/collection/node_modules/@floating-ui/core/dist/floating-ui.core.js.map +1 -0
- package/dist/javascript/collection/node_modules/@floating-ui/dom/dist/floating-ui.dom.js +736 -0
- package/dist/javascript/collection/node_modules/@floating-ui/dom/dist/floating-ui.dom.js.map +1 -0
- package/dist/javascript/collection/node_modules/@floating-ui/utils/dist/floating-ui.utils.dom.js +166 -0
- package/dist/javascript/collection/node_modules/@floating-ui/utils/dist/floating-ui.utils.dom.js.map +1 -0
- package/dist/javascript/collection/node_modules/@floating-ui/utils/dist/floating-ui.utils.js +105 -0
- package/dist/javascript/collection/node_modules/@floating-ui/utils/dist/floating-ui.utils.js.map +1 -0
- package/dist/javascript/midwest.d.ts +5 -0
- package/dist/javascript/midwest.js +733 -603
- package/dist/javascript/midwest.js.map +1 -1
- package/package.json +3 -4
|
@@ -1,13 +1,47 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
+
const FORMATTERS = {
|
|
4
|
+
phone: (v) => {
|
|
5
|
+
const d = v.replace(/\D/g, "").slice(0, 10);
|
|
6
|
+
if (d.length <= 3)
|
|
7
|
+
return d;
|
|
8
|
+
if (d.length <= 6)
|
|
9
|
+
return `(${d.slice(0, 3)}) ${d.slice(3)}`;
|
|
10
|
+
return `(${d.slice(0, 3)}) ${d.slice(3, 6)}-${d.slice(6)}`;
|
|
11
|
+
},
|
|
12
|
+
credit_card: (v) => {
|
|
13
|
+
const d = v.replace(/\D/g, "").slice(0, 16);
|
|
14
|
+
return d.match(/.{1,4}/g)?.join(" ") ?? d;
|
|
15
|
+
},
|
|
16
|
+
zip: (v) => {
|
|
17
|
+
const d = v.replace(/\D/g, "");
|
|
18
|
+
if (d.length <= 5)
|
|
19
|
+
return d;
|
|
20
|
+
return `${d.slice(0, 5)}-${d.slice(5, 9)}`;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
3
23
|
class Input extends Controller {
|
|
4
|
-
static targets = ["input", "counter"];
|
|
24
|
+
static targets = ["input", "counter", "capsLockWarning", "matchError"];
|
|
25
|
+
static values = {
|
|
26
|
+
match: { type: String, default: "" },
|
|
27
|
+
autoformat: { type: String, default: "" }
|
|
28
|
+
};
|
|
5
29
|
updateClearButton = null;
|
|
6
30
|
updateCounter = null;
|
|
31
|
+
updateCapsLock = null;
|
|
32
|
+
hideCapsLock = null;
|
|
33
|
+
matchListener = null;
|
|
34
|
+
matchInvalidListener = null;
|
|
35
|
+
matchSourceListener = null;
|
|
36
|
+
matchSourceElement = null;
|
|
37
|
+
autoformatListener = null;
|
|
7
38
|
connect() {
|
|
8
39
|
this.applyColor();
|
|
9
40
|
this.applySearchClear();
|
|
10
41
|
this.applyCounter();
|
|
42
|
+
this.applyPassword();
|
|
43
|
+
this.applyMatch();
|
|
44
|
+
this.applyAutoformat();
|
|
11
45
|
}
|
|
12
46
|
disconnect() {
|
|
13
47
|
if (this.updateClearButton) {
|
|
@@ -18,6 +52,56 @@ class Input extends Controller {
|
|
|
18
52
|
this.inputTarget.removeEventListener("input", this.updateCounter);
|
|
19
53
|
this.updateCounter = null;
|
|
20
54
|
}
|
|
55
|
+
if (this.updateCapsLock) {
|
|
56
|
+
this.inputTarget.removeEventListener("keydown", this.updateCapsLock);
|
|
57
|
+
this.inputTarget.removeEventListener("keyup", this.updateCapsLock);
|
|
58
|
+
this.inputTarget.removeEventListener("mousedown", this.updateCapsLock);
|
|
59
|
+
this.updateCapsLock = null;
|
|
60
|
+
}
|
|
61
|
+
if (this.hideCapsLock) {
|
|
62
|
+
this.inputTarget.removeEventListener("blur", this.hideCapsLock);
|
|
63
|
+
this.hideCapsLock = null;
|
|
64
|
+
}
|
|
65
|
+
if (this.matchListener) {
|
|
66
|
+
this.inputTarget.removeEventListener("input", this.matchListener);
|
|
67
|
+
this.inputTarget.removeEventListener("blur", this.matchListener);
|
|
68
|
+
this.matchListener = null;
|
|
69
|
+
}
|
|
70
|
+
if (this.matchInvalidListener) {
|
|
71
|
+
this.inputTarget.removeEventListener("invalid", this.matchInvalidListener);
|
|
72
|
+
this.matchInvalidListener = null;
|
|
73
|
+
}
|
|
74
|
+
if (this.matchSourceListener && this.matchSourceElement) {
|
|
75
|
+
this.matchSourceElement.removeEventListener("input", this.matchSourceListener);
|
|
76
|
+
this.matchSourceListener = null;
|
|
77
|
+
this.matchSourceElement = null;
|
|
78
|
+
}
|
|
79
|
+
if (this.autoformatListener) {
|
|
80
|
+
this.inputTarget.removeEventListener("blur", this.autoformatListener);
|
|
81
|
+
this.autoformatListener = null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
increment() {
|
|
85
|
+
const input = this.inputTarget;
|
|
86
|
+
const step = parseFloat(input.step) || 1;
|
|
87
|
+
const max = input.max !== "" ? parseFloat(input.max) : Infinity;
|
|
88
|
+
const newValue = (parseFloat(input.value) || 0) + step;
|
|
89
|
+
if (newValue <= max) {
|
|
90
|
+
input.value = String(newValue);
|
|
91
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
92
|
+
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
decrement() {
|
|
96
|
+
const input = this.inputTarget;
|
|
97
|
+
const step = parseFloat(input.step) || 1;
|
|
98
|
+
const min = input.min !== "" ? parseFloat(input.min) : -Infinity;
|
|
99
|
+
const newValue = (parseFloat(input.value) || 0) - step;
|
|
100
|
+
if (newValue >= min) {
|
|
101
|
+
input.value = String(newValue);
|
|
102
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
103
|
+
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
104
|
+
}
|
|
21
105
|
}
|
|
22
106
|
applyColor() {
|
|
23
107
|
if (this.inputTarget.type !== "color") {
|
|
@@ -36,6 +120,20 @@ class Input extends Controller {
|
|
|
36
120
|
};
|
|
37
121
|
this.inputTarget.addEventListener("input", this.updateCounter);
|
|
38
122
|
}
|
|
123
|
+
applyPassword() {
|
|
124
|
+
if (!this.hasCapsLockWarningTarget)
|
|
125
|
+
return;
|
|
126
|
+
this.updateCapsLock = (e) => {
|
|
127
|
+
this.capsLockWarningTarget.hidden = !e.getModifierState("CapsLock");
|
|
128
|
+
};
|
|
129
|
+
this.hideCapsLock = () => {
|
|
130
|
+
this.capsLockWarningTarget.hidden = true;
|
|
131
|
+
};
|
|
132
|
+
this.inputTarget.addEventListener("keydown", this.updateCapsLock);
|
|
133
|
+
this.inputTarget.addEventListener("keyup", this.updateCapsLock);
|
|
134
|
+
this.inputTarget.addEventListener("mousedown", this.updateCapsLock);
|
|
135
|
+
this.inputTarget.addEventListener("blur", this.hideCapsLock);
|
|
136
|
+
}
|
|
39
137
|
applySearchClear() {
|
|
40
138
|
if (this.inputTarget.type !== "search")
|
|
41
139
|
return;
|
|
@@ -76,6 +174,72 @@ class Input extends Controller {
|
|
|
76
174
|
this.inputTarget.focus();
|
|
77
175
|
});
|
|
78
176
|
}
|
|
177
|
+
// Validates that this field's value matches the value of the element
|
|
178
|
+
// identified by matchValue (a CSS selector). Uses setCustomValidity to
|
|
179
|
+
// block form submission. Shows the error inline via the matchError target
|
|
180
|
+
// rather than the browser's native tooltip (invalid event is suppressed).
|
|
181
|
+
applyMatch() {
|
|
182
|
+
if (!this.matchValue)
|
|
183
|
+
return;
|
|
184
|
+
const showError = () => {
|
|
185
|
+
this.element.classList.add("has-error", "theme-red");
|
|
186
|
+
if (this.hasMatchErrorTarget)
|
|
187
|
+
this.matchErrorTarget.hidden = false;
|
|
188
|
+
};
|
|
189
|
+
const clearError = () => {
|
|
190
|
+
this.element.classList.remove("has-error", "theme-red");
|
|
191
|
+
if (this.hasMatchErrorTarget)
|
|
192
|
+
this.matchErrorTarget.hidden = true;
|
|
193
|
+
};
|
|
194
|
+
const validate = () => {
|
|
195
|
+
const other2 = document.querySelector(this.matchValue);
|
|
196
|
+
if (!this.inputTarget.value) {
|
|
197
|
+
this.inputTarget.setCustomValidity("");
|
|
198
|
+
clearError();
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (!other2 || this.inputTarget.value === other2.value) {
|
|
202
|
+
this.inputTarget.setCustomValidity("");
|
|
203
|
+
clearError();
|
|
204
|
+
} else {
|
|
205
|
+
this.inputTarget.setCustomValidity("Values do not match");
|
|
206
|
+
showError();
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
this.matchListener = validate;
|
|
210
|
+
this.inputTarget.addEventListener("input", validate);
|
|
211
|
+
this.inputTarget.addEventListener("blur", validate);
|
|
212
|
+
this.matchInvalidListener = (e) => {
|
|
213
|
+
e.preventDefault();
|
|
214
|
+
showError();
|
|
215
|
+
};
|
|
216
|
+
this.inputTarget.addEventListener("invalid", this.matchInvalidListener);
|
|
217
|
+
const other = document.querySelector(this.matchValue);
|
|
218
|
+
if (other) {
|
|
219
|
+
this.matchSourceElement = other;
|
|
220
|
+
this.matchSourceListener = validate;
|
|
221
|
+
other.addEventListener("input", validate);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// Reformats the input value on blur using a named formatter from FORMATTERS.
|
|
225
|
+
// Dispatches input + change events after reformatting so downstream
|
|
226
|
+
// listeners (counters, form state) stay in sync.
|
|
227
|
+
applyAutoformat() {
|
|
228
|
+
if (!this.autoformatValue)
|
|
229
|
+
return;
|
|
230
|
+
const fmt = FORMATTERS[this.autoformatValue];
|
|
231
|
+
if (!fmt)
|
|
232
|
+
return;
|
|
233
|
+
this.autoformatListener = () => {
|
|
234
|
+
const formatted = fmt(this.inputTarget.value);
|
|
235
|
+
if (formatted !== this.inputTarget.value) {
|
|
236
|
+
this.inputTarget.value = formatted;
|
|
237
|
+
this.inputTarget.dispatchEvent(new Event("input", { bubbles: true }));
|
|
238
|
+
this.inputTarget.dispatchEvent(new Event("change", { bubbles: true }));
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
this.inputTarget.addEventListener("blur", this.autoformatListener);
|
|
242
|
+
}
|
|
79
243
|
}
|
|
80
244
|
|
|
81
245
|
export { Input as default };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"input_component_controller.js","sources":["../../../../../../../../app/components/midwest/form/input_component/input_component_controller.ts"],"sourcesContent":["\nimport { Controller } from '@hotwired/stimulus'\n\nexport default class Input extends Controller<HTMLFormElement> {\n static targets = ['input', 'counter']\n\n declare readonly hasInputTarget: boolean\n declare readonly inputTarget: HTMLInputElement\n declare readonly inputTargets: HTMLInputElement[]\n declare readonly hasCounterTarget: boolean\n declare readonly counterTarget: HTMLElement\n\n private updateClearButton: (() => void) | null = null\n private updateCounter: (() => void) | null = null\n\n connect () {\n this.applyColor()\n this.applySearchClear()\n this.applyCounter()\n }\n\n disconnect () {\n if (this.updateClearButton) {\n this.inputTarget.removeEventListener('input', this.updateClearButton)\n this.updateClearButton = null\n }\n if (this.updateCounter) {\n this.inputTarget.removeEventListener('input', this.updateCounter)\n this.updateCounter = null\n }\n }\n\n applyColor () {\n if (this.inputTarget.type !== 'color') {\n return\n }\n\n this.inputTarget.addEventListener('input', () => {\n this.inputTarget.setAttribute('value', this.inputTarget.value)\n })\n }\n\n private applyCounter () {\n if (!this.hasCounterTarget) return\n\n this.counterTarget.textContent = String(this.inputTarget.value.length)\n\n this.updateCounter = () => {\n this.counterTarget.textContent = String(this.inputTarget.value.length)\n }\n\n this.inputTarget.addEventListener('input', this.updateCounter)\n }\n\n private applySearchClear () {\n if (this.inputTarget.type !== 'search') return\n // Browsers that natively render a cancel button (Chrome, Safari, Edge)\n // already handle clearing. Only inject for browsers that don't (Firefox).\n if (CSS.supports('selector(::-webkit-search-cancel-button)')) return\n\n const button = document.createElement('button')\n button.type = 'button'\n button.setAttribute('aria-label', 'Clear search')\n button.classList.add('midwest-input-search-clear')\n button.hidden = this.inputTarget.value.length === 0\n\n const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')\n svg.setAttribute('viewBox', '0 0 24 24')\n svg.setAttribute('fill', 'none')\n svg.setAttribute('stroke', 'currentColor')\n svg.setAttribute('stroke-width', '2')\n svg.setAttribute('aria-hidden', 'true')\n svg.setAttribute('width', '16')\n svg.setAttribute('height', '16')\n\n const path = document.createElementNS('http://www.w3.org/2000/svg', 'path')\n path.setAttribute('stroke-linecap', 'round')\n path.setAttribute('stroke-linejoin', 'round')\n path.setAttribute('d', 'M6 18L18 6M6 6l12 12')\n svg.appendChild(path)\n button.appendChild(svg)\n\n const wrapper = document.createElement('div')\n wrapper.classList.add('midwest-input-search-wrapper')\n this.inputTarget.parentElement?.insertBefore(wrapper, this.inputTarget)\n wrapper.appendChild(this.inputTarget)\n wrapper.appendChild(button)\n\n this.updateClearButton = () => {\n button.hidden = this.inputTarget.value.length === 0\n }\n\n this.inputTarget.addEventListener('input', this.updateClearButton)\n\n button.addEventListener('click', () => {\n this.inputTarget.value = ''\n button.hidden = true\n this.inputTarget.dispatchEvent(new Event('input', { bubbles: true }))\n this.inputTarget.focus()\n })\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"input_component_controller.js","sources":["../../../../../../../../app/components/midwest/form/input_component/input_component_controller.ts"],"sourcesContent":["\nimport { Controller } from '@hotwired/stimulus'\n\n// Named formatters applied on blur when autoformat is set.\nconst FORMATTERS: Record<string, (value: string) => string> = {\n phone: (v: string) => {\n const d = v.replace(/\\D/g, '').slice(0, 10)\n if (d.length <= 3) return d\n if (d.length <= 6) return `(${d.slice(0, 3)}) ${d.slice(3)}`\n return `(${d.slice(0, 3)}) ${d.slice(3, 6)}-${d.slice(6)}`\n },\n credit_card: (v: string) => {\n const d = v.replace(/\\D/g, '').slice(0, 16)\n return d.match(/.{1,4}/g)?.join(' ') ?? d\n },\n zip: (v: string) => {\n const d = v.replace(/\\D/g, '')\n if (d.length <= 5) return d\n return `${d.slice(0, 5)}-${d.slice(5, 9)}`\n }\n}\n\nexport default class Input extends Controller<HTMLFormElement> {\n static targets = ['input', 'counter', 'capsLockWarning', 'matchError']\n static values = {\n match: { type: String, default: '' },\n autoformat: { type: String, default: '' }\n }\n\n declare readonly hasInputTarget: boolean\n declare readonly inputTarget: HTMLInputElement\n declare readonly inputTargets: HTMLInputElement[]\n declare readonly hasCounterTarget: boolean\n declare readonly counterTarget: HTMLElement\n declare readonly hasCapsLockWarningTarget: boolean\n declare readonly capsLockWarningTarget: HTMLElement\n declare readonly hasMatchErrorTarget: boolean\n declare readonly matchErrorTarget: HTMLElement\n declare matchValue: string\n declare autoformatValue: string\n\n private updateClearButton: (() => void) | null = null\n private updateCounter: (() => void) | null = null\n private updateCapsLock: ((e: KeyboardEvent | MouseEvent) => void) | null = null\n private hideCapsLock: (() => void) | null = null\n private matchListener: (() => void) | null = null\n private matchInvalidListener: ((e: Event) => void) | null = null\n private matchSourceListener: (() => void) | null = null\n private matchSourceElement: HTMLInputElement | null = null\n private autoformatListener: (() => void) | null = null\n\n connect () {\n this.applyColor()\n this.applySearchClear()\n this.applyCounter()\n this.applyPassword()\n this.applyMatch()\n this.applyAutoformat()\n }\n\n disconnect () {\n if (this.updateClearButton) {\n this.inputTarget.removeEventListener('input', this.updateClearButton)\n this.updateClearButton = null\n }\n if (this.updateCounter) {\n this.inputTarget.removeEventListener('input', this.updateCounter)\n this.updateCounter = null\n }\n if (this.updateCapsLock) {\n this.inputTarget.removeEventListener('keydown', this.updateCapsLock)\n this.inputTarget.removeEventListener('keyup', this.updateCapsLock)\n this.inputTarget.removeEventListener('mousedown', this.updateCapsLock)\n this.updateCapsLock = null\n }\n if (this.hideCapsLock) {\n this.inputTarget.removeEventListener('blur', this.hideCapsLock)\n this.hideCapsLock = null\n }\n if (this.matchListener) {\n this.inputTarget.removeEventListener('input', this.matchListener)\n this.inputTarget.removeEventListener('blur', this.matchListener)\n this.matchListener = null\n }\n if (this.matchInvalidListener) {\n this.inputTarget.removeEventListener('invalid', this.matchInvalidListener)\n this.matchInvalidListener = null\n }\n if (this.matchSourceListener && this.matchSourceElement) {\n this.matchSourceElement.removeEventListener('input', this.matchSourceListener)\n this.matchSourceListener = null\n this.matchSourceElement = null\n }\n if (this.autoformatListener) {\n this.inputTarget.removeEventListener('blur', this.autoformatListener)\n this.autoformatListener = null\n }\n }\n\n increment () {\n const input = this.inputTarget\n const step = parseFloat(input.step) || 1\n const max = input.max !== '' ? parseFloat(input.max) : Infinity\n const newValue = (parseFloat(input.value) || 0) + step\n if (newValue <= max) {\n input.value = String(newValue)\n input.dispatchEvent(new Event('input', { bubbles: true }))\n input.dispatchEvent(new Event('change', { bubbles: true }))\n }\n }\n\n decrement () {\n const input = this.inputTarget\n const step = parseFloat(input.step) || 1\n const min = input.min !== '' ? parseFloat(input.min) : -Infinity\n const newValue = (parseFloat(input.value) || 0) - step\n if (newValue >= min) {\n input.value = String(newValue)\n input.dispatchEvent(new Event('input', { bubbles: true }))\n input.dispatchEvent(new Event('change', { bubbles: true }))\n }\n }\n\n applyColor () {\n if (this.inputTarget.type !== 'color') {\n return\n }\n\n this.inputTarget.addEventListener('input', () => {\n this.inputTarget.setAttribute('value', this.inputTarget.value)\n })\n }\n\n private applyCounter () {\n if (!this.hasCounterTarget) return\n\n this.counterTarget.textContent = String(this.inputTarget.value.length)\n\n this.updateCounter = () => {\n this.counterTarget.textContent = String(this.inputTarget.value.length)\n }\n\n this.inputTarget.addEventListener('input', this.updateCounter)\n }\n\n private applyPassword () {\n if (!this.hasCapsLockWarningTarget) return\n\n this.updateCapsLock = (e: KeyboardEvent | MouseEvent) => {\n this.capsLockWarningTarget.hidden = !e.getModifierState('CapsLock')\n }\n\n this.hideCapsLock = () => {\n this.capsLockWarningTarget.hidden = true\n }\n\n this.inputTarget.addEventListener('keydown', this.updateCapsLock)\n this.inputTarget.addEventListener('keyup', this.updateCapsLock)\n this.inputTarget.addEventListener('mousedown', this.updateCapsLock)\n this.inputTarget.addEventListener('blur', this.hideCapsLock)\n }\n\n private applySearchClear () {\n if (this.inputTarget.type !== 'search') return\n // Browsers that natively render a cancel button (Chrome, Safari, Edge)\n // already handle clearing. Only inject for browsers that don't (Firefox).\n if (CSS.supports('selector(::-webkit-search-cancel-button)')) return\n\n const button = document.createElement('button')\n button.type = 'button'\n button.setAttribute('aria-label', 'Clear search')\n button.classList.add('midwest-input-search-clear')\n button.hidden = this.inputTarget.value.length === 0\n\n const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')\n svg.setAttribute('viewBox', '0 0 24 24')\n svg.setAttribute('fill', 'none')\n svg.setAttribute('stroke', 'currentColor')\n svg.setAttribute('stroke-width', '2')\n svg.setAttribute('aria-hidden', 'true')\n svg.setAttribute('width', '16')\n svg.setAttribute('height', '16')\n\n const path = document.createElementNS('http://www.w3.org/2000/svg', 'path')\n path.setAttribute('stroke-linecap', 'round')\n path.setAttribute('stroke-linejoin', 'round')\n path.setAttribute('d', 'M6 18L18 6M6 6l12 12')\n svg.appendChild(path)\n button.appendChild(svg)\n\n const wrapper = document.createElement('div')\n wrapper.classList.add('midwest-input-search-wrapper')\n this.inputTarget.parentElement?.insertBefore(wrapper, this.inputTarget)\n wrapper.appendChild(this.inputTarget)\n wrapper.appendChild(button)\n\n this.updateClearButton = () => {\n button.hidden = this.inputTarget.value.length === 0\n }\n\n this.inputTarget.addEventListener('input', this.updateClearButton)\n\n button.addEventListener('click', () => {\n this.inputTarget.value = ''\n button.hidden = true\n this.inputTarget.dispatchEvent(new Event('input', { bubbles: true }))\n this.inputTarget.focus()\n })\n }\n\n // Validates that this field's value matches the value of the element\n // identified by matchValue (a CSS selector). Uses setCustomValidity to\n // block form submission. Shows the error inline via the matchError target\n // rather than the browser's native tooltip (invalid event is suppressed).\n private applyMatch () {\n if (!this.matchValue) return\n\n const showError = () => {\n this.element.classList.add('has-error', 'theme-red')\n if (this.hasMatchErrorTarget) this.matchErrorTarget.hidden = false\n }\n\n const clearError = () => {\n this.element.classList.remove('has-error', 'theme-red')\n if (this.hasMatchErrorTarget) this.matchErrorTarget.hidden = true\n }\n\n const validate = () => {\n const other = document.querySelector<HTMLInputElement>(this.matchValue)\n if (!this.inputTarget.value) {\n this.inputTarget.setCustomValidity('')\n clearError()\n return\n }\n if (!other || this.inputTarget.value === other.value) {\n this.inputTarget.setCustomValidity('')\n clearError()\n } else {\n this.inputTarget.setCustomValidity('Values do not match')\n showError()\n }\n }\n\n this.matchListener = validate\n this.inputTarget.addEventListener('input', validate)\n this.inputTarget.addEventListener('blur', validate)\n\n // Suppress the browser's native validation tooltip on form submission;\n // our inline error element already shows the message.\n this.matchInvalidListener = (e: Event) => {\n e.preventDefault()\n showError()\n }\n this.inputTarget.addEventListener('invalid', this.matchInvalidListener)\n\n // Re-validate when the source field changes so the confirmation stays\n // in sync even if the user goes back and edits the original value.\n const other = document.querySelector<HTMLInputElement>(this.matchValue)\n if (other) {\n this.matchSourceElement = other\n this.matchSourceListener = validate\n other.addEventListener('input', validate)\n }\n }\n\n // Reformats the input value on blur using a named formatter from FORMATTERS.\n // Dispatches input + change events after reformatting so downstream\n // listeners (counters, form state) stay in sync.\n private applyAutoformat () {\n if (!this.autoformatValue) return\n const fmt = FORMATTERS[this.autoformatValue]\n if (!fmt) return\n\n this.autoformatListener = () => {\n const formatted = fmt(this.inputTarget.value)\n if (formatted !== this.inputTarget.value) {\n this.inputTarget.value = formatted\n this.inputTarget.dispatchEvent(new Event('input', { bubbles: true }))\n this.inputTarget.dispatchEvent(new Event('change', { bubbles: true }))\n }\n }\n\n this.inputTarget.addEventListener('blur', this.autoformatListener)\n }\n}\n"],"names":["other"],"mappings":"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"}
|
|
@@ -14,11 +14,13 @@ class FormLiveSummary extends Controller {
|
|
|
14
14
|
this.form?.addEventListener("input", this.render);
|
|
15
15
|
this.form?.addEventListener("change", this.render);
|
|
16
16
|
this.form?.addEventListener("midwest-autocomplete:change", this.render);
|
|
17
|
+
this.form?.addEventListener("midwest-color-picker:update", this.render);
|
|
17
18
|
}
|
|
18
19
|
disconnect() {
|
|
19
20
|
this.form?.removeEventListener("input", this.render);
|
|
20
21
|
this.form?.removeEventListener("change", this.render);
|
|
21
22
|
this.form?.removeEventListener("midwest-autocomplete:change", this.render);
|
|
23
|
+
this.form?.removeEventListener("midwest-color-picker:update", this.render);
|
|
22
24
|
}
|
|
23
25
|
get form() {
|
|
24
26
|
return this.element.closest("form");
|
|
@@ -36,15 +38,18 @@ class FormLiveSummary extends Controller {
|
|
|
36
38
|
}
|
|
37
39
|
const dl = document.createElement("dl");
|
|
38
40
|
dl.className = "midwest-form-live-summary-list";
|
|
39
|
-
fields.forEach(({ label, value }) => {
|
|
41
|
+
fields.forEach(({ label, value, swatchColor }) => {
|
|
40
42
|
const dt = Object.assign(document.createElement("dt"), {
|
|
41
43
|
className: "midwest-form-live-summary-term",
|
|
42
44
|
textContent: label
|
|
43
45
|
});
|
|
44
|
-
const dd =
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
const dd = document.createElement("dd");
|
|
47
|
+
dd.className = "midwest-form-live-summary-definition";
|
|
48
|
+
if (swatchColor !== void 0) {
|
|
49
|
+
dd.append(this.buildSwatch(swatchColor), value || "\u2014");
|
|
50
|
+
} else {
|
|
51
|
+
dd.textContent = value || "\u2014";
|
|
52
|
+
}
|
|
48
53
|
dl.append(dt, dd);
|
|
49
54
|
});
|
|
50
55
|
this.listTarget.replaceChildren(dl);
|
|
@@ -101,6 +106,18 @@ class FormLiveSummary extends Controller {
|
|
|
101
106
|
value: element.value
|
|
102
107
|
});
|
|
103
108
|
});
|
|
109
|
+
form.querySelectorAll('[data-midwest-color-picker-target="input"]').forEach((input) => {
|
|
110
|
+
if (this.element.contains(input))
|
|
111
|
+
return;
|
|
112
|
+
if (!input.name)
|
|
113
|
+
return;
|
|
114
|
+
const picker = input.closest(".midwest-color-picker");
|
|
115
|
+
const labelEl = picker?.querySelector(".midwest-form-group__top label") ?? this.form?.querySelector(`label[for="${CSS.escape(input.id)}"]`);
|
|
116
|
+
const label = labelEl?.textContent?.trim() || this.prettifyName(input.name.replace(/\[.*\]$/, ""));
|
|
117
|
+
const value = input.value;
|
|
118
|
+
const display = value.startsWith("#") ? value : value ? value.charAt(0).toUpperCase() + value.slice(1) : "\u2014";
|
|
119
|
+
fields.push({ label, value: display, swatchColor: value || void 0 });
|
|
120
|
+
});
|
|
104
121
|
form.querySelectorAll(".midwest-autocomplete").forEach((ac) => {
|
|
105
122
|
if (this.element.contains(ac))
|
|
106
123
|
return;
|
|
@@ -113,6 +130,22 @@ class FormLiveSummary extends Controller {
|
|
|
113
130
|
});
|
|
114
131
|
return fields;
|
|
115
132
|
}
|
|
133
|
+
// Builds a small inline colour swatch to display alongside a colour value.
|
|
134
|
+
// Hex values are applied as an inline background-color; named theme colours
|
|
135
|
+
// use a scoped theme-{color} class so the CSS variable cascade does the work.
|
|
136
|
+
// An empty/transparent value renders a diagonal-stripe placeholder.
|
|
137
|
+
buildSwatch(color) {
|
|
138
|
+
const swatch = document.createElement("span");
|
|
139
|
+
swatch.className = "midwest-form-live-summary-swatch";
|
|
140
|
+
if (color.startsWith("#")) {
|
|
141
|
+
swatch.style.backgroundColor = color;
|
|
142
|
+
} else if (color && color !== "transparent") {
|
|
143
|
+
swatch.classList.add(`theme-${color}`, "bg-theme-6");
|
|
144
|
+
} else {
|
|
145
|
+
swatch.classList.add("is-transparent");
|
|
146
|
+
}
|
|
147
|
+
return swatch;
|
|
148
|
+
}
|
|
116
149
|
// Finds the label associated with a specific element via `for` attribute,
|
|
117
150
|
// wrapping label, or falls back to a prettified version of the element name.
|
|
118
151
|
labelFor(el) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live_summary_component_controller.js","sources":["../../../../../../../../app/components/midwest/form/live_summary_component/live_summary_component_controller.ts"],"sourcesContent":["import { Controller } from '@hotwired/stimulus'\n\n// Input types that should never appear in a form live summary.\nconst SKIPPED_INPUT_TYPES = new Set([\n 'hidden', 'submit', 'reset', 'button', 'image'\n])\n\ninterface FieldSummary {\n label: string\n value: string\n}\n\nexport default class FormLiveSummary extends Controller<HTMLElement> {\n static targets = ['list']\n\n declare readonly listTarget: HTMLElement\n\n connect () {\n this.render()\n this.form?.addEventListener('input', this.render)\n this.form?.addEventListener('change', this.render)\n this.form?.addEventListener('midwest-autocomplete:change', this.render)\n }\n\n disconnect () {\n this.form?.removeEventListener('input', this.render)\n this.form?.removeEventListener('change', this.render)\n this.form?.removeEventListener('midwest-autocomplete:change', this.render)\n }\n\n private get form (): HTMLFormElement | null {\n return this.element.closest('form')\n }\n\n private render = () => {\n const fields = this.collectFields()\n\n if (fields.length === 0) {\n this.listTarget.replaceChildren(\n Object.assign(document.createElement('p'), {\n className: 'midwest-form-live-summary-empty',\n textContent: 'No values to display.'\n })\n )\n return\n }\n\n const dl = document.createElement('dl')\n dl.className = 'midwest-form-live-summary-list'\n\n fields.forEach(({ label, value }) => {\n const dt = Object.assign(document.createElement('dt'), {\n className: 'midwest-form-live-summary-term',\n textContent: label\n })\n const dd = Object.assign(document.createElement('dd'), {\n className: 'midwest-form-live-summary-definition',\n textContent: value || '—'\n })\n dl.append(dt, dd)\n })\n\n this.listTarget.replaceChildren(dl)\n }\n\n private collectFields (): FieldSummary[] {\n const form = this.form\n if (!form) return []\n\n const fields: FieldSummary[] = []\n const seenRadioNames = new Set<string>()\n\n Array.from(form.elements).forEach(el => {\n const element = el as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLButtonElement\n\n if (!element.name) return\n\n // Skip elements that live inside the summary itself.\n if (this.element.contains(element)) return\n\n // Skip all button elements regardless of how they are declared.\n if (element instanceof HTMLButtonElement) return\n\n if (element instanceof HTMLInputElement) {\n if (SKIPPED_INPUT_TYPES.has(element.type)) return\n\n if (element.type === 'radio') {\n if (seenRadioNames.has(element.name)) return\n seenRadioNames.add(element.name)\n\n const checked = form.querySelector<HTMLInputElement>(\n `input[type=\"radio\"][name=\"${CSS.escape(element.name)}\"]:checked`\n )\n if (!checked) return\n\n fields.push({\n label: this.groupLabelFor(element),\n value: this.labelFor(checked) || checked.value\n })\n return\n }\n\n if (element.type === 'checkbox') {\n fields.push({\n label: this.labelFor(element),\n value: element.checked ? 'Yes' : 'No'\n })\n return\n }\n }\n\n if (element instanceof HTMLSelectElement) {\n fields.push({\n label: this.labelFor(element),\n value: Array.from(element.selectedOptions).map(o => o.text).join(', ') || '—'\n })\n return\n }\n\n // Text-like inputs and textareas only reach this point.\n fields.push({\n label: this.labelFor(element),\n value: element.value\n })\n })\n\n // Autocomplete components use a nameless combobox input + hidden inputs —\n // neither surfaces through the standard form.elements loop. Query them\n // separately so their selected badge labels appear in the summary.\n // Note: labelFor(input) doesn't work here because InputComponent assigns\n // its own id to the <input>, which won't match the <label>'s for attribute.\n form.querySelectorAll<HTMLElement>('.midwest-autocomplete').forEach(ac => {\n if (this.element.contains(ac)) return\n if (!ac.querySelector('[role=\"combobox\"]')) return\n\n const labelEl = ac.querySelector<HTMLElement>('label')\n const label = labelEl?.textContent?.trim() ||\n this.prettifyName((ac.dataset.name ?? '').replace(/\\[.*\\]$/, ''))\n\n const selected = Array.from(ac.querySelectorAll<HTMLElement>('.midwest-autocomplete-tokens .value'))\n .map(el => el.textContent?.trim())\n .filter(Boolean)\n .join(', ')\n fields.push({ label, value: selected || '—' })\n })\n\n return fields\n }\n\n // Finds the label associated with a specific element via `for` attribute,\n // wrapping label, or falls back to a prettified version of the element name.\n private labelFor (el: Element): string {\n const id = (el as HTMLElement).id\n if (id) {\n const label = this.form?.querySelector(`label[for=\"${CSS.escape(id)}\"]`)\n if (label) return label.textContent?.trim() ?? ''\n }\n const wrapper = el.closest('label')\n if (wrapper) return wrapper.textContent?.trim() ?? ''\n return this.prettifyName((el as HTMLInputElement).name ?? '')\n }\n\n // For radio groups, prefer the containing fieldset legend over the\n // individual radio's label, then fall back to prettifying the name.\n private groupLabelFor (el: Element): string {\n const legend = el.closest('fieldset')?.querySelector('legend')\n if (legend) return legend.textContent?.trim() ?? ''\n return this.prettifyName((el as HTMLInputElement).name ?? '')\n }\n\n // Converts \"user[first_name]\" or \"first_name\" into \"First name\".\n private prettifyName (name: string): string {\n return name\n .replace(/\\[.*?\\]/g, '')\n .replace(/_/g, ' ')\n .replace(/^\\w/, c => c.toUpperCase())\n .trim()\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"live_summary_component_controller.js","sources":["../../../../../../../../app/components/midwest/form/live_summary_component/live_summary_component_controller.ts"],"sourcesContent":["import { Controller } from '@hotwired/stimulus'\n\n// Input types that should never appear in a form live summary.\nconst SKIPPED_INPUT_TYPES = new Set([\n 'hidden', 'submit', 'reset', 'button', 'image'\n])\n\ninterface FieldSummary {\n label: string\n value: string\n /** When set, a colour swatch is prepended to the value cell. */\n swatchColor?: string\n}\n\nexport default class FormLiveSummary extends Controller<HTMLElement> {\n static targets = ['list']\n\n declare readonly listTarget: HTMLElement\n\n connect () {\n this.render()\n this.form?.addEventListener('input', this.render)\n this.form?.addEventListener('change', this.render)\n this.form?.addEventListener('midwest-autocomplete:change', this.render)\n this.form?.addEventListener('midwest-color-picker:update', this.render)\n }\n\n disconnect () {\n this.form?.removeEventListener('input', this.render)\n this.form?.removeEventListener('change', this.render)\n this.form?.removeEventListener('midwest-autocomplete:change', this.render)\n this.form?.removeEventListener('midwest-color-picker:update', this.render)\n }\n\n private get form (): HTMLFormElement | null {\n return this.element.closest('form')\n }\n\n private render = () => {\n const fields = this.collectFields()\n\n if (fields.length === 0) {\n this.listTarget.replaceChildren(\n Object.assign(document.createElement('p'), {\n className: 'midwest-form-live-summary-empty',\n textContent: 'No values to display.'\n })\n )\n return\n }\n\n const dl = document.createElement('dl')\n dl.className = 'midwest-form-live-summary-list'\n\n fields.forEach(({ label, value, swatchColor }) => {\n const dt = Object.assign(document.createElement('dt'), {\n className: 'midwest-form-live-summary-term',\n textContent: label\n })\n const dd = document.createElement('dd')\n dd.className = 'midwest-form-live-summary-definition'\n\n if (swatchColor !== undefined) {\n dd.append(this.buildSwatch(swatchColor), value || '—')\n } else {\n dd.textContent = value || '—'\n }\n\n dl.append(dt, dd)\n })\n\n this.listTarget.replaceChildren(dl)\n }\n\n private collectFields (): FieldSummary[] {\n const form = this.form\n if (!form) return []\n\n const fields: FieldSummary[] = []\n const seenRadioNames = new Set<string>()\n\n Array.from(form.elements).forEach(el => {\n const element = el as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLButtonElement\n\n if (!element.name) return\n\n // Skip elements that live inside the summary itself.\n if (this.element.contains(element)) return\n\n // Skip all button elements regardless of how they are declared.\n if (element instanceof HTMLButtonElement) return\n\n if (element instanceof HTMLInputElement) {\n if (SKIPPED_INPUT_TYPES.has(element.type)) return\n\n if (element.type === 'radio') {\n if (seenRadioNames.has(element.name)) return\n seenRadioNames.add(element.name)\n\n const checked = form.querySelector<HTMLInputElement>(\n `input[type=\"radio\"][name=\"${CSS.escape(element.name)}\"]:checked`\n )\n if (!checked) return\n\n fields.push({\n label: this.groupLabelFor(element),\n value: this.labelFor(checked) || checked.value\n })\n return\n }\n\n if (element.type === 'checkbox') {\n fields.push({\n label: this.labelFor(element),\n value: element.checked ? 'Yes' : 'No'\n })\n return\n }\n }\n\n if (element instanceof HTMLSelectElement) {\n fields.push({\n label: this.labelFor(element),\n value: Array.from(element.selectedOptions).map(o => o.text).join(', ') || '—'\n })\n return\n }\n\n // Text-like inputs and textareas only reach this point.\n fields.push({\n label: this.labelFor(element),\n value: element.value\n })\n })\n\n // Color picker components store their value in a hidden input tagged with\n // data-midwest-color-picker-target=\"input\". The standard loop skips hidden\n // inputs, so query them separately and render with an inline colour swatch.\n form.querySelectorAll<HTMLInputElement>('[data-midwest-color-picker-target=\"input\"]').forEach(input => {\n if (this.element.contains(input)) return\n if (!input.name) return\n\n // Prefer the label inside the picker's own form-group header; fall back\n // to a label[for] match or a prettified version of the input name.\n const picker = input.closest<HTMLElement>('.midwest-color-picker')\n const labelEl = picker?.querySelector<HTMLElement>('.midwest-form-group__top label') ??\n this.form?.querySelector<HTMLElement>(`label[for=\"${CSS.escape(input.id)}\"]`)\n const label = labelEl?.textContent?.trim() ||\n this.prettifyName(input.name.replace(/\\[.*\\]$/, ''))\n\n const value = input.value\n const display = value.startsWith('#') ? value : (value ? value.charAt(0).toUpperCase() + value.slice(1) : '—')\n\n fields.push({ label, value: display, swatchColor: value || undefined })\n })\n\n // Autocomplete components use a nameless combobox input + hidden inputs —\n // neither surfaces through the standard form.elements loop. Query them\n // separately so their selected badge labels appear in the summary.\n // Note: labelFor(input) doesn't work here because InputComponent assigns\n // its own id to the <input>, which won't match the <label>'s for attribute.\n form.querySelectorAll<HTMLElement>('.midwest-autocomplete').forEach(ac => {\n if (this.element.contains(ac)) return\n if (!ac.querySelector('[role=\"combobox\"]')) return\n\n const labelEl = ac.querySelector<HTMLElement>('label')\n const label = labelEl?.textContent?.trim() ||\n this.prettifyName((ac.dataset.name ?? '').replace(/\\[.*\\]$/, ''))\n\n const selected = Array.from(ac.querySelectorAll<HTMLElement>('.midwest-autocomplete-tokens .value'))\n .map(el => el.textContent?.trim())\n .filter(Boolean)\n .join(', ')\n fields.push({ label, value: selected || '—' })\n })\n\n return fields\n }\n\n // Builds a small inline colour swatch to display alongside a colour value.\n // Hex values are applied as an inline background-color; named theme colours\n // use a scoped theme-{color} class so the CSS variable cascade does the work.\n // An empty/transparent value renders a diagonal-stripe placeholder.\n private buildSwatch (color: string): HTMLElement {\n const swatch = document.createElement('span')\n swatch.className = 'midwest-form-live-summary-swatch'\n\n if (color.startsWith('#')) {\n swatch.style.backgroundColor = color\n } else if (color && color !== 'transparent') {\n swatch.classList.add(`theme-${color}`, 'bg-theme-6')\n } else {\n swatch.classList.add('is-transparent')\n }\n\n return swatch\n }\n\n // Finds the label associated with a specific element via `for` attribute,\n // wrapping label, or falls back to a prettified version of the element name.\n private labelFor (el: Element): string {\n const id = (el as HTMLElement).id\n if (id) {\n const label = this.form?.querySelector(`label[for=\"${CSS.escape(id)}\"]`)\n if (label) return label.textContent?.trim() ?? ''\n }\n const wrapper = el.closest('label')\n if (wrapper) return wrapper.textContent?.trim() ?? ''\n return this.prettifyName((el as HTMLInputElement).name ?? '')\n }\n\n // For radio groups, prefer the containing fieldset legend over the\n // individual radio's label, then fall back to prettifying the name.\n private groupLabelFor (el: Element): string {\n const legend = el.closest('fieldset')?.querySelector('legend')\n if (legend) return legend.textContent?.trim() ?? ''\n return this.prettifyName((el as HTMLInputElement).name ?? '')\n }\n\n // Converts \"user[first_name]\" or \"first_name\" into \"First name\".\n private prettifyName (name: string): string {\n return name\n .replace(/\\[.*?\\]/g, '')\n .replace(/_/g, ' ')\n .replace(/^\\w/, c => c.toUpperCase())\n .trim()\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Controller } from '@hotwired/stimulus';
|
|
2
|
+
|
|
3
|
+
class HorizontalScroll extends Controller {
|
|
4
|
+
resizeObserver = null;
|
|
5
|
+
connect() {
|
|
6
|
+
this.updateOverflow();
|
|
7
|
+
this.element.addEventListener("scroll", this.updateOverflow);
|
|
8
|
+
this.resizeObserver = new ResizeObserver(this.updateOverflow);
|
|
9
|
+
this.resizeObserver.observe(this.element);
|
|
10
|
+
}
|
|
11
|
+
disconnect() {
|
|
12
|
+
this.element.removeEventListener("scroll", this.updateOverflow);
|
|
13
|
+
this.resizeObserver?.disconnect();
|
|
14
|
+
this.resizeObserver = null;
|
|
15
|
+
}
|
|
16
|
+
updateOverflow = () => {
|
|
17
|
+
const el = this.element;
|
|
18
|
+
el.classList.toggle("can-scroll-left", el.scrollLeft > 0);
|
|
19
|
+
el.classList.toggle("can-scroll-right", el.scrollLeft < el.scrollWidth - el.clientWidth - 1);
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { HorizontalScroll as default };
|
|
24
|
+
//# sourceMappingURL=horizontal_scroll_component_controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"horizontal_scroll_component_controller.js","sources":["../../../../../../../app/components/midwest/horizontal_scroll_component/horizontal_scroll_component_controller.ts"],"sourcesContent":["import { Controller } from '@hotwired/stimulus'\n\nexport default class HorizontalScroll extends Controller<HTMLElement> {\n private resizeObserver: ResizeObserver | null = null\n\n connect () {\n this.updateOverflow()\n this.element.addEventListener('scroll', this.updateOverflow)\n this.resizeObserver = new ResizeObserver(this.updateOverflow)\n this.resizeObserver.observe(this.element)\n }\n\n disconnect () {\n this.element.removeEventListener('scroll', this.updateOverflow)\n this.resizeObserver?.disconnect()\n this.resizeObserver = null\n }\n\n private updateOverflow = () => {\n const el = this.element\n el.classList.toggle('can-scroll-left', el.scrollLeft > 0)\n el.classList.toggle('can-scroll-right', el.scrollLeft < el.scrollWidth - el.clientWidth - 1)\n }\n}\n"],"names":[],"mappings":";;AAEA,MAAqB,yBAAyB,UAAwB,CAAA;AAAA,EAC5D,cAAwC,GAAA,IAAA,CAAA;AAAA,EAEhD,OAAW,GAAA;AACT,IAAA,IAAA,CAAK,cAAe,EAAA,CAAA;AACpB,IAAA,IAAA,CAAK,OAAQ,CAAA,gBAAA,CAAiB,QAAU,EAAA,IAAA,CAAK,cAAc,CAAA,CAAA;AAC3D,IAAA,IAAA,CAAK,cAAiB,GAAA,IAAI,cAAe,CAAA,IAAA,CAAK,cAAc,CAAA,CAAA;AAC5D,IAAK,IAAA,CAAA,cAAA,CAAe,OAAQ,CAAA,IAAA,CAAK,OAAO,CAAA,CAAA;AAAA,GAC1C;AAAA,EAEA,UAAc,GAAA;AACZ,IAAA,IAAA,CAAK,OAAQ,CAAA,mBAAA,CAAoB,QAAU,EAAA,IAAA,CAAK,cAAc,CAAA,CAAA;AAC9D,IAAA,IAAA,CAAK,gBAAgB,UAAW,EAAA,CAAA;AAChC,IAAA,IAAA,CAAK,cAAiB,GAAA,IAAA,CAAA;AAAA,GACxB;AAAA,EAEQ,iBAAiB,MAAM;AAC7B,IAAA,MAAM,KAAK,IAAK,CAAA,OAAA,CAAA;AAChB,IAAA,EAAA,CAAG,SAAU,CAAA,MAAA,CAAO,iBAAmB,EAAA,EAAA,CAAG,aAAa,CAAC,CAAA,CAAA;AACxD,IAAG,EAAA,CAAA,SAAA,CAAU,OAAO,kBAAoB,EAAA,EAAA,CAAG,aAAa,EAAG,CAAA,WAAA,GAAc,EAAG,CAAA,WAAA,GAAc,CAAC,CAAA,CAAA;AAAA,GAC7F,CAAA;AACF;;;;"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Controller } from '@hotwired/stimulus';
|
|
2
|
+
|
|
3
|
+
class Notification extends Controller {
|
|
4
|
+
container = null;
|
|
5
|
+
observer = null;
|
|
6
|
+
connect() {
|
|
7
|
+
this.container = this.element.closest("[popover]");
|
|
8
|
+
this.container?.showPopover?.();
|
|
9
|
+
requestAnimationFrame(() => {
|
|
10
|
+
this.element.classList.add("visible");
|
|
11
|
+
});
|
|
12
|
+
const frame = this.element.parentElement;
|
|
13
|
+
if (frame) {
|
|
14
|
+
this.observer = new MutationObserver(() => this.syncCountdown());
|
|
15
|
+
this.observer.observe(frame, { childList: true });
|
|
16
|
+
setTimeout(() => this.syncCountdown());
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
disconnect() {
|
|
20
|
+
this.observer?.disconnect();
|
|
21
|
+
this.observer = null;
|
|
22
|
+
if (this.container) {
|
|
23
|
+
const frame = this.container.querySelector("turbo-frame");
|
|
24
|
+
if (frame?.children.length === 0 && this.container.matches(":popover-open")) {
|
|
25
|
+
this.container.hidePopover?.();
|
|
26
|
+
}
|
|
27
|
+
this.container = null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
close() {
|
|
31
|
+
if (!this.element.classList.contains("animated")) {
|
|
32
|
+
this.element.remove();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
this.element.classList.add("closing");
|
|
36
|
+
setTimeout(() => {
|
|
37
|
+
this.element.remove();
|
|
38
|
+
}, 350);
|
|
39
|
+
}
|
|
40
|
+
syncCountdown() {
|
|
41
|
+
const timerEl = this.element.querySelector('[data-controller~="midwest-countdown-timer"]');
|
|
42
|
+
if (!timerEl)
|
|
43
|
+
return;
|
|
44
|
+
const timer = this.application.getControllerForElementAndIdentifier(
|
|
45
|
+
timerEl,
|
|
46
|
+
"midwest-countdown-timer"
|
|
47
|
+
);
|
|
48
|
+
if (!timer)
|
|
49
|
+
return;
|
|
50
|
+
const isTop = this.element === this.element.parentElement?.firstElementChild;
|
|
51
|
+
if (isTop) {
|
|
52
|
+
timer.resume();
|
|
53
|
+
} else {
|
|
54
|
+
timer.pause();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { Notification as default };
|
|
60
|
+
//# sourceMappingURL=notification_component_controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notification_component_controller.js","sources":["../../../../../../../app/components/midwest/notification_component/notification_component_controller.ts"],"sourcesContent":["import { Controller } from '@hotwired/stimulus'\n\ntype PopoverElement = HTMLElement & { showPopover?(): void; hidePopover?(): void }\n\ninterface PausableTimer {\n pause(): void\n resume(): void\n}\n\nexport default class Notification extends Controller<HTMLElement> {\n private container: PopoverElement | null = null\n private observer: MutationObserver | null = null\n\n connect () {\n this.container = this.element.closest<PopoverElement>('[popover]')\n this.container?.showPopover?.()\n\n requestAnimationFrame(() => {\n this.element.classList.add('visible')\n })\n\n const frame = this.element.parentElement\n if (frame) {\n this.observer = new MutationObserver(() => this.syncCountdown())\n this.observer.observe(frame, { childList: true })\n // Defer until nested controllers (e.g. CountdownTimer) have connected\n setTimeout(() => this.syncCountdown())\n }\n }\n\n disconnect () {\n this.observer?.disconnect()\n this.observer = null\n\n if (this.container) {\n const frame = this.container.querySelector('turbo-frame')\n if (frame?.children.length === 0 && this.container.matches(':popover-open')) {\n this.container.hidePopover?.()\n }\n this.container = null\n }\n }\n\n close () {\n if (!this.element.classList.contains('animated')) {\n this.element.remove()\n return\n }\n\n this.element.classList.add('closing')\n setTimeout(() => {\n this.element.remove()\n }, 350)\n }\n\n private syncCountdown (): void {\n const timerEl = this.element.querySelector<HTMLElement>('[data-controller~=\"midwest-countdown-timer\"]')\n if (!timerEl) return\n\n const timer = this.application.getControllerForElementAndIdentifier(\n timerEl, 'midwest-countdown-timer'\n ) as PausableTimer | null\n if (!timer) return\n\n const isTop = this.element === this.element.parentElement?.firstElementChild\n if (isTop) { timer.resume() } else { timer.pause() }\n }\n}\n"],"names":[],"mappings":";;AASA,MAAqB,qBAAqB,UAAwB,CAAA;AAAA,EACxD,SAAmC,GAAA,IAAA,CAAA;AAAA,EACnC,QAAoC,GAAA,IAAA,CAAA;AAAA,EAE5C,OAAW,GAAA;AACT,IAAA,IAAA,CAAK,SAAY,GAAA,IAAA,CAAK,OAAQ,CAAA,OAAA,CAAwB,WAAW,CAAA,CAAA;AACjE,IAAA,IAAA,CAAK,WAAW,WAAc,IAAA,CAAA;AAE9B,IAAA,qBAAA,CAAsB,MAAM;AAC1B,MAAK,IAAA,CAAA,OAAA,CAAQ,SAAU,CAAA,GAAA,CAAI,SAAS,CAAA,CAAA;AAAA,KACrC,CAAA,CAAA;AAED,IAAM,MAAA,KAAA,GAAQ,KAAK,OAAQ,CAAA,aAAA,CAAA;AAC3B,IAAA,IAAI,KAAO,EAAA;AACT,MAAA,IAAA,CAAK,WAAW,IAAI,gBAAA,CAAiB,MAAM,IAAA,CAAK,eAAe,CAAA,CAAA;AAC/D,MAAA,IAAA,CAAK,SAAS,OAAQ,CAAA,KAAA,EAAO,EAAE,SAAA,EAAW,MAAM,CAAA,CAAA;AAEhD,MAAW,UAAA,CAAA,MAAM,IAAK,CAAA,aAAA,EAAe,CAAA,CAAA;AAAA,KACvC;AAAA,GACF;AAAA,EAEA,UAAc,GAAA;AACZ,IAAA,IAAA,CAAK,UAAU,UAAW,EAAA,CAAA;AAC1B,IAAA,IAAA,CAAK,QAAW,GAAA,IAAA,CAAA;AAEhB,IAAA,IAAI,KAAK,SAAW,EAAA;AAClB,MAAA,MAAM,KAAQ,GAAA,IAAA,CAAK,SAAU,CAAA,aAAA,CAAc,aAAa,CAAA,CAAA;AACxD,MAAI,IAAA,KAAA,EAAO,SAAS,MAAW,KAAA,CAAA,IAAK,KAAK,SAAU,CAAA,OAAA,CAAQ,eAAe,CAAG,EAAA;AAC3E,QAAA,IAAA,CAAK,UAAU,WAAc,IAAA,CAAA;AAAA,OAC/B;AACA,MAAA,IAAA,CAAK,SAAY,GAAA,IAAA,CAAA;AAAA,KACnB;AAAA,GACF;AAAA,EAEA,KAAS,GAAA;AACP,IAAA,IAAI,CAAC,IAAK,CAAA,OAAA,CAAQ,SAAU,CAAA,QAAA,CAAS,UAAU,CAAG,EAAA;AAChD,MAAA,IAAA,CAAK,QAAQ,MAAO,EAAA,CAAA;AACpB,MAAA,OAAA;AAAA,KACF;AAEA,IAAK,IAAA,CAAA,OAAA,CAAQ,SAAU,CAAA,GAAA,CAAI,SAAS,CAAA,CAAA;AACpC,IAAA,UAAA,CAAW,MAAM;AACf,MAAA,IAAA,CAAK,QAAQ,MAAO,EAAA,CAAA;AAAA,OACnB,GAAG,CAAA,CAAA;AAAA,GACR;AAAA,EAEQ,aAAuB,GAAA;AAC7B,IAAA,MAAM,OAAU,GAAA,IAAA,CAAK,OAAQ,CAAA,aAAA,CAA2B,8CAA8C,CAAA,CAAA;AACtG,IAAA,IAAI,CAAC,OAAA;AAAS,MAAA,OAAA;AAEd,IAAM,MAAA,KAAA,GAAQ,KAAK,WAAY,CAAA,oCAAA;AAAA,MAC7B,OAAA;AAAA,MAAS,yBAAA;AAAA,KACX,CAAA;AACA,IAAA,IAAI,CAAC,KAAA;AAAO,MAAA,OAAA;AAEZ,IAAA,MAAM,KAAQ,GAAA,IAAA,CAAK,OAAY,KAAA,IAAA,CAAK,QAAQ,aAAe,EAAA,iBAAA,CAAA;AAC3D,IAAA,IAAI,KAAO,EAAA;AAAE,MAAA,KAAA,CAAM,MAAO,EAAA,CAAA;AAAA,KAAS,MAAA;AAAE,MAAA,KAAA,CAAM,KAAM,EAAA,CAAA;AAAA,KAAE;AAAA,GACrD;AACF;;;;"}
|
package/dist/javascript/collection/app/components/midwest/table_component/load_more_controller.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Controller } from '@hotwired/stimulus';
|
|
2
|
+
|
|
3
|
+
class LoadMore extends Controller {
|
|
4
|
+
static values = {
|
|
5
|
+
url: String,
|
|
6
|
+
tbodyId: String
|
|
7
|
+
};
|
|
8
|
+
observer = null;
|
|
9
|
+
loading = false;
|
|
10
|
+
connect() {
|
|
11
|
+
if (!this.urlValue)
|
|
12
|
+
return;
|
|
13
|
+
this.observer = new IntersectionObserver(
|
|
14
|
+
(entries) => {
|
|
15
|
+
if (entries[0].isIntersecting)
|
|
16
|
+
this.load();
|
|
17
|
+
},
|
|
18
|
+
// Start fetching 300px before the sentinel enters the viewport so rows
|
|
19
|
+
// appear before the user actually reaches the bottom.
|
|
20
|
+
{ rootMargin: "0px 0px 300px 0px" }
|
|
21
|
+
);
|
|
22
|
+
this.observer.observe(this.element);
|
|
23
|
+
}
|
|
24
|
+
disconnect() {
|
|
25
|
+
this.observer?.disconnect();
|
|
26
|
+
this.observer = null;
|
|
27
|
+
}
|
|
28
|
+
async load() {
|
|
29
|
+
if (this.loading)
|
|
30
|
+
return;
|
|
31
|
+
this.loading = true;
|
|
32
|
+
this.observer?.disconnect();
|
|
33
|
+
try {
|
|
34
|
+
const response = await fetch(this.urlValue, {
|
|
35
|
+
headers: {
|
|
36
|
+
Accept: "text/html",
|
|
37
|
+
"X-Requested-With": "XMLHttpRequest"
|
|
38
|
+
},
|
|
39
|
+
credentials: "same-origin"
|
|
40
|
+
});
|
|
41
|
+
if (!response.ok)
|
|
42
|
+
return;
|
|
43
|
+
const html = await response.text();
|
|
44
|
+
const doc = new DOMParser().parseFromString(html, "text/html");
|
|
45
|
+
const tbody = document.getElementById(this.tbodyIdValue);
|
|
46
|
+
const newRows = Array.from(
|
|
47
|
+
doc.querySelectorAll(".midwest-table-body tr")
|
|
48
|
+
);
|
|
49
|
+
if (!tbody || newRows.length === 0)
|
|
50
|
+
return;
|
|
51
|
+
this.element.remove();
|
|
52
|
+
newRows.forEach((row) => tbody.appendChild(document.importNode(row, true)));
|
|
53
|
+
} catch {
|
|
54
|
+
this.loading = false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { LoadMore as default };
|
|
60
|
+
//# sourceMappingURL=load_more_controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load_more_controller.js","sources":["../../../../../../../app/components/midwest/table_component/load_more_controller.ts"],"sourcesContent":["import { Controller } from '@hotwired/stimulus'\n\n// Enables infinite scroll on a `midwest-table` by observing a sentinel <tr>\n// element at the bottom of the tbody. When the sentinel scrolls near the\n// viewport, this controller fetches the next-page URL, extracts the new <tr>\n// elements from the returned HTML, and appends them (plus the new sentinel,\n// if present) to the current tbody — replacing itself in the process.\n//\n// Values\n// url — URL for the next page (built by TablePresenter#next_page_url)\n// tbodyId — DOM id of the <tbody> to append rows into\nexport default class LoadMore extends Controller<HTMLTableRowElement> {\n static values = {\n url: String,\n tbodyId: String,\n }\n\n declare urlValue: string\n declare tbodyIdValue: string\n\n private observer: IntersectionObserver | null = null\n private loading = false\n\n connect () {\n // No URL means this is the last page — nothing to load.\n if (!this.urlValue) return\n\n this.observer = new IntersectionObserver(\n (entries) => { if (entries[0].isIntersecting) this.load() },\n // Start fetching 300px before the sentinel enters the viewport so rows\n // appear before the user actually reaches the bottom.\n { rootMargin: '0px 0px 300px 0px' }\n )\n this.observer.observe(this.element)\n }\n\n disconnect () {\n this.observer?.disconnect()\n this.observer = null\n }\n\n private async load () {\n if (this.loading) return\n this.loading = true\n this.observer?.disconnect()\n\n try {\n const response = await fetch(this.urlValue, {\n headers: {\n Accept: 'text/html',\n 'X-Requested-With': 'XMLHttpRequest',\n },\n credentials: 'same-origin',\n })\n\n if (!response.ok) return\n\n const html = await response.text()\n const doc = new DOMParser().parseFromString(html, 'text/html')\n const tbody = document.getElementById(this.tbodyIdValue)\n const newRows = Array.from(\n doc.querySelectorAll<HTMLTableRowElement>('.midwest-table-body tr')\n )\n\n if (!tbody || newRows.length === 0) return\n\n // Remove the current sentinel before appending new rows so its position\n // doesn't shift the table layout during the DOM update.\n this.element.remove()\n\n // Import each row (including any new sentinel for the next page) into the\n // current document and append it to the live tbody.\n newRows.forEach(row => tbody.appendChild(document.importNode(row, true)))\n } catch {\n // Silently fail — the table remains fully functional, just without\n // auto-loading more rows.\n this.loading = false\n }\n }\n}\n"],"names":[],"mappings":";;AAWA,MAAqB,iBAAiB,UAAgC,CAAA;AAAA,EACpE,OAAO,MAAS,GAAA;AAAA,IACd,GAAS,EAAA,MAAA;AAAA,IACT,OAAS,EAAA,MAAA;AAAA,GACX,CAAA;AAAA,EAKQ,QAAwC,GAAA,IAAA,CAAA;AAAA,EACxC,OAAU,GAAA,KAAA,CAAA;AAAA,EAElB,OAAW,GAAA;AAET,IAAA,IAAI,CAAC,IAAK,CAAA,QAAA;AAAU,MAAA,OAAA;AAEpB,IAAA,IAAA,CAAK,WAAW,IAAI,oBAAA;AAAA,MAClB,CAAC,OAAY,KAAA;AAAE,QAAI,IAAA,OAAA,CAAQ,CAAC,CAAE,CAAA,cAAA;AAAgB,UAAA,IAAA,CAAK,IAAK,EAAA,CAAA;AAAA,OAAE;AAAA;AAAA;AAAA,MAG1D,EAAE,YAAY,mBAAoB,EAAA;AAAA,KACpC,CAAA;AACA,IAAK,IAAA,CAAA,QAAA,CAAS,OAAQ,CAAA,IAAA,CAAK,OAAO,CAAA,CAAA;AAAA,GACpC;AAAA,EAEA,UAAc,GAAA;AACZ,IAAA,IAAA,CAAK,UAAU,UAAW,EAAA,CAAA;AAC1B,IAAA,IAAA,CAAK,QAAW,GAAA,IAAA,CAAA;AAAA,GAClB;AAAA,EAEA,MAAc,IAAQ,GAAA;AACpB,IAAA,IAAI,IAAK,CAAA,OAAA;AAAS,MAAA,OAAA;AAClB,IAAA,IAAA,CAAK,OAAU,GAAA,IAAA,CAAA;AACf,IAAA,IAAA,CAAK,UAAU,UAAW,EAAA,CAAA;AAE1B,IAAI,IAAA;AACF,MAAA,MAAM,QAAW,GAAA,MAAM,KAAM,CAAA,IAAA,CAAK,QAAU,EAAA;AAAA,QAC1C,OAAS,EAAA;AAAA,UACP,MAAmB,EAAA,WAAA;AAAA,UACnB,kBAAoB,EAAA,gBAAA;AAAA,SACtB;AAAA,QACA,WAAa,EAAA,aAAA;AAAA,OACd,CAAA,CAAA;AAED,MAAA,IAAI,CAAC,QAAS,CAAA,EAAA;AAAI,QAAA,OAAA;AAElB,MAAM,MAAA,IAAA,GAAU,MAAM,QAAA,CAAS,IAAK,EAAA,CAAA;AACpC,MAAA,MAAM,MAAU,IAAI,SAAA,EAAY,CAAA,eAAA,CAAgB,MAAM,WAAW,CAAA,CAAA;AACjE,MAAA,MAAM,KAAU,GAAA,QAAA,CAAS,cAAe,CAAA,IAAA,CAAK,YAAY,CAAA,CAAA;AACzD,MAAA,MAAM,UAAU,KAAM,CAAA,IAAA;AAAA,QACpB,GAAA,CAAI,iBAAsC,wBAAwB,CAAA;AAAA,OACpE,CAAA;AAEA,MAAI,IAAA,CAAC,KAAS,IAAA,OAAA,CAAQ,MAAW,KAAA,CAAA;AAAG,QAAA,OAAA;AAIpC,MAAA,IAAA,CAAK,QAAQ,MAAO,EAAA,CAAA;AAIpB,MAAQ,OAAA,CAAA,OAAA,CAAQ,SAAO,KAAM,CAAA,WAAA,CAAY,SAAS,UAAW,CAAA,GAAA,EAAK,IAAI,CAAC,CAAC,CAAA,CAAA;AAAA,KACxE,CAAA,MAAA;AAGA,MAAA,IAAA,CAAK,OAAU,GAAA,KAAA,CAAA;AAAA,KACjB;AAAA,GACF;AACF;;;;"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Controller } from '@hotwired/stimulus';
|
|
2
|
+
|
|
3
|
+
class Table extends Controller {
|
|
4
|
+
static targets = ["selectAll", "rowCheckbox"];
|
|
5
|
+
toggleAll() {
|
|
6
|
+
const checked = this.selectAllTarget.checked;
|
|
7
|
+
this.rowCheckboxTargets.forEach((cb) => {
|
|
8
|
+
cb.checked = checked;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
updateSelectAll() {
|
|
12
|
+
if (!this.hasSelectAllTarget)
|
|
13
|
+
return;
|
|
14
|
+
const total = this.rowCheckboxTargets.length;
|
|
15
|
+
const checkedCount = this.rowCheckboxTargets.filter((cb) => cb.checked).length;
|
|
16
|
+
if (checkedCount === 0) {
|
|
17
|
+
this.selectAllTarget.checked = false;
|
|
18
|
+
this.selectAllTarget.indeterminate = false;
|
|
19
|
+
} else if (checkedCount === total) {
|
|
20
|
+
this.selectAllTarget.checked = true;
|
|
21
|
+
this.selectAllTarget.indeterminate = false;
|
|
22
|
+
} else {
|
|
23
|
+
this.selectAllTarget.checked = false;
|
|
24
|
+
this.selectAllTarget.indeterminate = true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export { Table as default };
|
|
30
|
+
//# sourceMappingURL=table_component_controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"table_component_controller.js","sources":["../../../../../../../app/components/midwest/table_component/table_component_controller.ts"],"sourcesContent":["import { Controller } from '@hotwired/stimulus'\n\nexport default class Table extends Controller<HTMLElement> {\n static targets = ['selectAll', 'rowCheckbox']\n\n declare readonly selectAllTarget: HTMLInputElement\n declare readonly rowCheckboxTargets: HTMLInputElement[]\n declare readonly hasSelectAllTarget: boolean\n\n toggleAll () {\n const checked = this.selectAllTarget.checked\n this.rowCheckboxTargets.forEach(cb => { cb.checked = checked })\n }\n\n updateSelectAll () {\n if (!this.hasSelectAllTarget) return\n\n const total = this.rowCheckboxTargets.length\n const checkedCount = this.rowCheckboxTargets.filter(cb => cb.checked).length\n\n if (checkedCount === 0) {\n this.selectAllTarget.checked = false\n this.selectAllTarget.indeterminate = false\n } else if (checkedCount === total) {\n this.selectAllTarget.checked = true\n this.selectAllTarget.indeterminate = false\n } else {\n this.selectAllTarget.checked = false\n this.selectAllTarget.indeterminate = true\n }\n }\n}\n"],"names":[],"mappings":";;AAEA,MAAqB,cAAc,UAAwB,CAAA;AAAA,EACzD,OAAO,OAAA,GAAU,CAAC,WAAA,EAAa,aAAa,CAAA,CAAA;AAAA,EAM5C,SAAa,GAAA;AACX,IAAM,MAAA,OAAA,GAAU,KAAK,eAAgB,CAAA,OAAA,CAAA;AACrC,IAAK,IAAA,CAAA,kBAAA,CAAmB,QAAQ,CAAM,EAAA,KAAA;AAAE,MAAA,EAAA,CAAG,OAAU,GAAA,OAAA,CAAA;AAAA,KAAS,CAAA,CAAA;AAAA,GAChE;AAAA,EAEA,eAAmB,GAAA;AACjB,IAAA,IAAI,CAAC,IAAK,CAAA,kBAAA;AAAoB,MAAA,OAAA;AAE9B,IAAM,MAAA,KAAA,GAAQ,KAAK,kBAAmB,CAAA,MAAA,CAAA;AACtC,IAAA,MAAM,eAAe,IAAK,CAAA,kBAAA,CAAmB,OAAO,CAAM,EAAA,KAAA,EAAA,CAAG,OAAO,CAAE,CAAA,MAAA,CAAA;AAEtE,IAAA,IAAI,iBAAiB,CAAG,EAAA;AACtB,MAAA,IAAA,CAAK,gBAAgB,OAAU,GAAA,KAAA,CAAA;AAC/B,MAAA,IAAA,CAAK,gBAAgB,aAAgB,GAAA,KAAA,CAAA;AAAA,KACvC,MAAA,IAAW,iBAAiB,KAAO,EAAA;AACjC,MAAA,IAAA,CAAK,gBAAgB,OAAU,GAAA,IAAA,CAAA;AAC/B,MAAA,IAAA,CAAK,gBAAgB,aAAgB,GAAA,KAAA,CAAA;AAAA,KAChC,MAAA;AACL,MAAA,IAAA,CAAK,gBAAgB,OAAU,GAAA,KAAA,CAAA;AAC/B,MAAA,IAAA,CAAK,gBAAgB,aAAgB,GAAA,IAAA,CAAA;AAAA,KACvC;AAAA,GACF;AACF;;;;"}
|