@unabridged/midwest 0.24.4 → 0.26.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 +6 -0
- package/app/assets/javascript/midwest.js +642 -24
- package/app/assets/javascript/midwest.js.map +1 -1
- package/app/assets/stylesheets/midwest.css +1 -1
- package/app/assets/stylesheets/midwest.tailwind.css +13 -1
- package/dist/css/midwest.css +1 -1
- package/dist/javascript/collection/app/assets/javascript/midwest/index.js +6 -0
- package/dist/javascript/collection/app/assets/javascript/midwest/index.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/assets_component/assets_component_controller.js +47 -0
- package/dist/javascript/collection/app/components/midwest/assets_component/assets_component_controller.js.map +1 -0
- package/dist/javascript/collection/app/components/midwest/chart_component/chart_component_controller.js +122 -2
- package/dist/javascript/collection/app/components/midwest/chart_component/chart_component_controller.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/context_menu_component/context_menu_component_controller.js +71 -0
- package/dist/javascript/collection/app/components/midwest/context_menu_component/context_menu_component_controller.js.map +1 -0
- package/dist/javascript/collection/app/components/midwest/dialog_component/dialog_component_controller.js +33 -1
- package/dist/javascript/collection/app/components/midwest/dialog_component/dialog_component_controller.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/form/form_controller.js +37 -3
- package/dist/javascript/collection/app/components/midwest/form/form_controller.js.map +1 -1
- package/dist/javascript/collection/app/components/midwest/split_view_component/split_view_component_controller.js +99 -0
- package/dist/javascript/collection/app/components/midwest/split_view_component/split_view_component_controller.js.map +1 -0
- package/dist/javascript/collection/app/components/midwest/table_component/table_component_controller.js +239 -15
- package/dist/javascript/collection/app/components/midwest/table_component/table_component_controller.js.map +1 -1
- package/dist/javascript/midwest.d.ts +16 -1
- package/dist/javascript/midwest.js +642 -24
- package/dist/javascript/midwest.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form_controller.js","sources":["../../../../../../../app/components/midwest/form/form_controller.ts"],"sourcesContent":["\nimport { Controller } from '@hotwired/stimulus'\n\nexport default class Form extends Controller<HTMLFormElement> {\n connect () {\n this.element.addEventListener('submit', this.handleSubmit)\n this.element.addEventListener('turbo:submit-end', this.handleTurboEnd)\n }\n\n disconnect () {\n this.element.removeEventListener('submit', this.handleSubmit)\n this.element.removeEventListener('turbo:submit-end', this.handleTurboEnd)\n }\n\n private handleSubmit = () => {\n this.submitButtons.forEach(btn => {\n btn.classList.add('loading')\n btn.disabled = true\n })\n }\n\n // Restore buttons
|
|
1
|
+
{"version":3,"file":"form_controller.js","sources":["../../../../../../../app/components/midwest/form/form_controller.ts"],"sourcesContent":["\nimport { Controller } from '@hotwired/stimulus'\n\nexport default class Form extends Controller<HTMLFormElement> {\n static values = {\n // Auto-submit the form (debounced) whenever a field changes.\n autosave: { type: Boolean, default: false },\n autosaveDelay: { type: Number, default: 600 },\n // Emit success/error events and keep buttons interactive after an in-page\n // (non-navigating) Turbo submission.\n ajax: { type: Boolean, default: false }\n }\n\n declare autosaveValue: boolean\n declare autosaveDelayValue: number\n declare ajaxValue: boolean\n\n private autosaveTimer?: ReturnType<typeof setTimeout>\n\n connect () {\n this.element.addEventListener('submit', this.handleSubmit)\n this.element.addEventListener('turbo:submit-end', this.handleTurboEnd)\n if (this.autosaveValue) {\n this.element.addEventListener('input', this.scheduleAutosave)\n this.element.addEventListener('change', this.scheduleAutosave)\n }\n }\n\n disconnect () {\n this.element.removeEventListener('submit', this.handleSubmit)\n this.element.removeEventListener('turbo:submit-end', this.handleTurboEnd)\n this.element.removeEventListener('input', this.scheduleAutosave)\n this.element.removeEventListener('change', this.scheduleAutosave)\n if (this.autosaveTimer != null) clearTimeout(this.autosaveTimer)\n }\n\n private handleSubmit = () => {\n this.submitButtons.forEach(btn => {\n btn.classList.add('loading')\n btn.disabled = true\n })\n }\n\n private handleTurboEnd = (event: Event) => {\n const detail = (event as CustomEvent).detail\n const success: boolean = detail?.success ?? false\n\n // Restore buttons when the submission failed (e.g. a Turbo Frame validation\n // error re-renders the form without a page transition), or whenever the form\n // stays on the page — ajax and autosave never navigate away, so a stuck\n // \"loading\" button would otherwise persist.\n if (!success || this.ajaxValue || this.autosaveValue) {\n this.submitButtons.forEach(btn => {\n btn.classList.remove('loading')\n btn.disabled = false\n })\n }\n\n // Let app code react to an in-page submission without a navigation.\n if (this.ajaxValue) {\n this.dispatch(success ? 'success' : 'error', { detail })\n }\n }\n\n // Debounce field changes into a single background submission. `requestSubmit`\n // runs constraint validation and fires the `submit` event, so buttons pick up\n // their loading state and Turbo handles the request just like a manual submit.\n private scheduleAutosave = () => {\n if (this.autosaveTimer != null) clearTimeout(this.autosaveTimer)\n this.autosaveTimer = setTimeout(() => {\n if (typeof this.element.requestSubmit === 'function') {\n this.element.requestSubmit()\n } else {\n this.element.submit()\n }\n }, this.autosaveDelayValue)\n }\n\n private get submitButtons (): HTMLButtonElement[] {\n return Array.from(\n this.element.querySelectorAll<HTMLButtonElement>('button[type=\"submit\"], button:not([type])')\n )\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Controller } from '@hotwired/stimulus';
|
|
2
|
+
|
|
3
|
+
class SplitView extends Controller {
|
|
4
|
+
static targets = ["primary", "divider"];
|
|
5
|
+
static values = {
|
|
6
|
+
orientation: { type: String, default: "horizontal" },
|
|
7
|
+
size: { type: Number, default: 50 },
|
|
8
|
+
min: { type: Number, default: 10 },
|
|
9
|
+
max: { type: Number, default: 90 },
|
|
10
|
+
storageKey: { type: String, default: "" }
|
|
11
|
+
};
|
|
12
|
+
current = 50;
|
|
13
|
+
boundMove = (event) => this.onPointerMove(event);
|
|
14
|
+
boundUp = () => this.stopDrag();
|
|
15
|
+
connect() {
|
|
16
|
+
this.current = this.restore() ?? this.sizeValue;
|
|
17
|
+
this.apply(this.current);
|
|
18
|
+
}
|
|
19
|
+
startDrag(event) {
|
|
20
|
+
if (event.button !== 0)
|
|
21
|
+
return;
|
|
22
|
+
event.preventDefault();
|
|
23
|
+
this.element.classList.add("is-dragging");
|
|
24
|
+
window.addEventListener("pointermove", this.boundMove);
|
|
25
|
+
window.addEventListener("pointerup", this.boundUp);
|
|
26
|
+
}
|
|
27
|
+
onPointerMove(event) {
|
|
28
|
+
const rect = this.element.getBoundingClientRect();
|
|
29
|
+
const pct = this.orientationValue === "vertical" ? (event.clientY - rect.top) / rect.height * 100 : (event.clientX - rect.left) / rect.width * 100;
|
|
30
|
+
this.apply(pct);
|
|
31
|
+
}
|
|
32
|
+
stopDrag() {
|
|
33
|
+
this.element.classList.remove("is-dragging");
|
|
34
|
+
window.removeEventListener("pointermove", this.boundMove);
|
|
35
|
+
window.removeEventListener("pointerup", this.boundUp);
|
|
36
|
+
this.persist();
|
|
37
|
+
}
|
|
38
|
+
onKeydown(event) {
|
|
39
|
+
const vertical = this.orientationValue === "vertical";
|
|
40
|
+
if (event.key === "Home") {
|
|
41
|
+
event.preventDefault();
|
|
42
|
+
this.setAndPersist(this.minValue);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (event.key === "End") {
|
|
46
|
+
event.preventDefault();
|
|
47
|
+
this.setAndPersist(this.maxValue);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const increaseKey = vertical ? "ArrowDown" : "ArrowRight";
|
|
51
|
+
const decreaseKey = vertical ? "ArrowUp" : "ArrowLeft";
|
|
52
|
+
if (event.key !== increaseKey && event.key !== decreaseKey)
|
|
53
|
+
return;
|
|
54
|
+
event.preventDefault();
|
|
55
|
+
const step = event.shiftKey ? 10 : 2;
|
|
56
|
+
this.setAndPersist(this.current + (event.key === increaseKey ? step : -step));
|
|
57
|
+
}
|
|
58
|
+
// Double-click restores the initial size.
|
|
59
|
+
reset() {
|
|
60
|
+
this.setAndPersist(this.sizeValue);
|
|
61
|
+
}
|
|
62
|
+
setAndPersist(pct) {
|
|
63
|
+
this.apply(pct);
|
|
64
|
+
this.persist();
|
|
65
|
+
}
|
|
66
|
+
apply(pct) {
|
|
67
|
+
this.current = Math.min(this.maxValue, Math.max(this.minValue, pct));
|
|
68
|
+
this.element.style.setProperty("--split-size", `${this.current}%`);
|
|
69
|
+
this.dividerTarget.setAttribute("aria-valuenow", String(Math.round(this.current)));
|
|
70
|
+
}
|
|
71
|
+
persist() {
|
|
72
|
+
if (!this.storageKeyValue)
|
|
73
|
+
return;
|
|
74
|
+
try {
|
|
75
|
+
window.localStorage.setItem(this.storageKeyValue, String(this.current));
|
|
76
|
+
} catch {
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
restore() {
|
|
80
|
+
if (!this.storageKeyValue)
|
|
81
|
+
return null;
|
|
82
|
+
try {
|
|
83
|
+
const raw = window.localStorage.getItem(this.storageKeyValue);
|
|
84
|
+
if (raw === null)
|
|
85
|
+
return null;
|
|
86
|
+
const value = Number.parseFloat(raw);
|
|
87
|
+
return Number.isFinite(value) ? value : null;
|
|
88
|
+
} catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
disconnect() {
|
|
93
|
+
window.removeEventListener("pointermove", this.boundMove);
|
|
94
|
+
window.removeEventListener("pointerup", this.boundUp);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export { SplitView as default };
|
|
99
|
+
//# sourceMappingURL=split_view_component_controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"split_view_component_controller.js","sources":["../../../../../../../app/components/midwest/split_view_component/split_view_component_controller.ts"],"sourcesContent":["import { Controller } from '@hotwired/stimulus'\n\n// Resizable two-pane split view. Dragging the divider (or arrow keys when it is\n// focused) resizes the primary pane; the size is a percentage of the container\n// and, when a storage key is set, persisted to localStorage across reloads.\nexport default class SplitView extends Controller<HTMLElement> {\n static targets = ['primary', 'divider']\n static values = {\n orientation: { type: String, default: 'horizontal' },\n size: { type: Number, default: 50 },\n min: { type: Number, default: 10 },\n max: { type: Number, default: 90 },\n storageKey: { type: String, default: '' },\n }\n\n declare readonly dividerTarget: HTMLElement\n declare orientationValue: string\n declare sizeValue: number\n declare minValue: number\n declare maxValue: number\n declare storageKeyValue: string\n\n private current = 50\n private boundMove = (event: PointerEvent) => this.onPointerMove(event)\n private boundUp = () => this.stopDrag()\n\n connect() {\n this.current = this.restore() ?? this.sizeValue\n this.apply(this.current)\n }\n\n startDrag(event: PointerEvent) {\n if (event.button !== 0) return\n event.preventDefault()\n this.element.classList.add('is-dragging')\n window.addEventListener('pointermove', this.boundMove)\n window.addEventListener('pointerup', this.boundUp)\n }\n\n private onPointerMove(event: PointerEvent) {\n const rect = this.element.getBoundingClientRect()\n const pct =\n this.orientationValue === 'vertical'\n ? ((event.clientY - rect.top) / rect.height) * 100\n : ((event.clientX - rect.left) / rect.width) * 100\n this.apply(pct)\n }\n\n private stopDrag() {\n this.element.classList.remove('is-dragging')\n window.removeEventListener('pointermove', this.boundMove)\n window.removeEventListener('pointerup', this.boundUp)\n this.persist()\n }\n\n onKeydown(event: KeyboardEvent) {\n const vertical = this.orientationValue === 'vertical'\n\n if (event.key === 'Home') {\n event.preventDefault()\n this.setAndPersist(this.minValue)\n return\n }\n if (event.key === 'End') {\n event.preventDefault()\n this.setAndPersist(this.maxValue)\n return\n }\n\n const increaseKey = vertical ? 'ArrowDown' : 'ArrowRight'\n const decreaseKey = vertical ? 'ArrowUp' : 'ArrowLeft'\n if (event.key !== increaseKey && event.key !== decreaseKey) return\n\n event.preventDefault()\n const step = event.shiftKey ? 10 : 2\n this.setAndPersist(this.current + (event.key === increaseKey ? step : -step))\n }\n\n // Double-click restores the initial size.\n reset() {\n this.setAndPersist(this.sizeValue)\n }\n\n private setAndPersist(pct: number) {\n this.apply(pct)\n this.persist()\n }\n\n private apply(pct: number) {\n this.current = Math.min(this.maxValue, Math.max(this.minValue, pct))\n this.element.style.setProperty('--split-size', `${this.current}%`)\n this.dividerTarget.setAttribute('aria-valuenow', String(Math.round(this.current)))\n }\n\n private persist() {\n if (!this.storageKeyValue) return\n try {\n window.localStorage.setItem(this.storageKeyValue, String(this.current))\n } catch {\n /* storage unavailable (private mode / quota) — ignore */\n }\n }\n\n private restore(): number | null {\n if (!this.storageKeyValue) return null\n try {\n const raw = window.localStorage.getItem(this.storageKeyValue)\n if (raw === null) return null\n const value = Number.parseFloat(raw)\n return Number.isFinite(value) ? value : null\n } catch {\n return null\n }\n }\n\n disconnect() {\n window.removeEventListener('pointermove', this.boundMove)\n window.removeEventListener('pointerup', this.boundUp)\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,16 +1,69 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
3
|
class Table extends Controller {
|
|
4
|
-
static targets = ["selectAll", "rowCheckbox"];
|
|
4
|
+
static targets = ["selectAll", "rowCheckbox", "editable"];
|
|
5
|
+
static values = {
|
|
6
|
+
resizable: { type: Boolean, default: false },
|
|
7
|
+
resizeStorageKey: { type: String, default: "" }
|
|
8
|
+
};
|
|
9
|
+
static MIN_COLUMN_WIDTH = 48;
|
|
10
|
+
// Active resize gesture state.
|
|
11
|
+
_resizeTh = null;
|
|
12
|
+
_resizeStartX = 0;
|
|
13
|
+
_resizeStartWidth = 0;
|
|
14
|
+
connect() {
|
|
15
|
+
if (this.resizableValue)
|
|
16
|
+
this.initResize();
|
|
17
|
+
}
|
|
18
|
+
// ─── selection ─────────────────────────────────────────────────────────────
|
|
5
19
|
toggleAll() {
|
|
6
20
|
const checked = this.selectAllTarget.checked;
|
|
7
21
|
this.rowCheckboxTargets.forEach((cb) => {
|
|
8
22
|
cb.checked = checked;
|
|
9
23
|
});
|
|
10
24
|
}
|
|
25
|
+
updateSelectAll() {
|
|
26
|
+
if (!this.hasSelectAllTarget)
|
|
27
|
+
return;
|
|
28
|
+
const total = this.rowCheckboxTargets.length;
|
|
29
|
+
const checkedCount = this.rowCheckboxTargets.filter((cb) => cb.checked).length;
|
|
30
|
+
if (checkedCount === 0) {
|
|
31
|
+
this.selectAllTarget.checked = false;
|
|
32
|
+
this.selectAllTarget.indeterminate = false;
|
|
33
|
+
} else if (checkedCount === total) {
|
|
34
|
+
this.selectAllTarget.checked = true;
|
|
35
|
+
this.selectAllTarget.indeterminate = false;
|
|
36
|
+
} else {
|
|
37
|
+
this.selectAllTarget.checked = false;
|
|
38
|
+
this.selectAllTarget.indeterminate = true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Shift-clicking a sort header extends the multi-column sort chain instead of
|
|
42
|
+
// replacing it: follow the header's data-multi-sort-url. A plain click falls
|
|
43
|
+
// through to the anchor's single-sort href. Navigation stays frame-scoped by
|
|
44
|
+
// driving the enclosing <turbo-frame>'s src when present.
|
|
45
|
+
sortColumn(event) {
|
|
46
|
+
if (!event.shiftKey)
|
|
47
|
+
return;
|
|
48
|
+
const link = event.currentTarget;
|
|
49
|
+
const url = link.dataset.multiSortUrl;
|
|
50
|
+
if (!url)
|
|
51
|
+
return;
|
|
52
|
+
event.preventDefault();
|
|
53
|
+
const frame = link.closest("turbo-frame");
|
|
54
|
+
if (frame != null) {
|
|
55
|
+
frame.src = url;
|
|
56
|
+
} else {
|
|
57
|
+
const Turbo = window.Turbo;
|
|
58
|
+
if (Turbo?.visit)
|
|
59
|
+
Turbo.visit(url);
|
|
60
|
+
else
|
|
61
|
+
window.location.href = url;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
11
64
|
navigateRow(event) {
|
|
12
65
|
const target = event.target;
|
|
13
|
-
if (target.closest('a, button, input, select, textarea, [role="button"]'))
|
|
66
|
+
if (target.closest('a, button, input, select, textarea, [role="button"], .editable'))
|
|
14
67
|
return;
|
|
15
68
|
const row = event.currentTarget;
|
|
16
69
|
const href = row.dataset.href;
|
|
@@ -27,22 +80,193 @@ class Table extends Controller {
|
|
|
27
80
|
}
|
|
28
81
|
}
|
|
29
82
|
}
|
|
30
|
-
|
|
31
|
-
|
|
83
|
+
// ─── column resizing ────────────────────────────────────────────────────────
|
|
84
|
+
// Freeze the current (auto-laid-out) column widths as explicit values, switch
|
|
85
|
+
// to a fixed layout so drags don't reflow other columns, then apply any widths
|
|
86
|
+
// saved from a previous session.
|
|
87
|
+
initResize() {
|
|
88
|
+
const headers = this.headerCells();
|
|
89
|
+
if (headers.length === 0)
|
|
32
90
|
return;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
91
|
+
headers.forEach((th) => {
|
|
92
|
+
th.style.width = `${th.offsetWidth}px`;
|
|
93
|
+
});
|
|
94
|
+
this.table?.classList.add("is-fixed");
|
|
95
|
+
this.restoreWidths();
|
|
96
|
+
}
|
|
97
|
+
startResize(event) {
|
|
98
|
+
const handle = event.target;
|
|
99
|
+
const th = handle.closest("th");
|
|
100
|
+
if (th == null)
|
|
101
|
+
return;
|
|
102
|
+
event.preventDefault();
|
|
103
|
+
this._resizeTh = th;
|
|
104
|
+
this._resizeStartX = event.clientX;
|
|
105
|
+
this._resizeStartWidth = th.offsetWidth;
|
|
106
|
+
this.element.classList.add("is-resizing");
|
|
107
|
+
window.addEventListener("pointermove", this.onResizeMove);
|
|
108
|
+
window.addEventListener("pointerup", this.onResizeEnd);
|
|
109
|
+
}
|
|
110
|
+
// Double-clicking a handle clears that column's explicit width so it auto-sizes
|
|
111
|
+
// to its content again.
|
|
112
|
+
autoSizeColumn(event) {
|
|
113
|
+
const th = event.target.closest("th");
|
|
114
|
+
if (th == null)
|
|
115
|
+
return;
|
|
116
|
+
th.style.removeProperty("width");
|
|
117
|
+
this.persistWidths();
|
|
118
|
+
}
|
|
119
|
+
onResizeMove = (event) => {
|
|
120
|
+
if (this._resizeTh == null)
|
|
121
|
+
return;
|
|
122
|
+
const delta = event.clientX - this._resizeStartX;
|
|
123
|
+
const width = Math.max(Table.MIN_COLUMN_WIDTH, this._resizeStartWidth + delta);
|
|
124
|
+
this._resizeTh.style.width = `${width}px`;
|
|
125
|
+
};
|
|
126
|
+
onResizeEnd = () => {
|
|
127
|
+
this._resizeTh = null;
|
|
128
|
+
this.element.classList.remove("is-resizing");
|
|
129
|
+
window.removeEventListener("pointermove", this.onResizeMove);
|
|
130
|
+
window.removeEventListener("pointerup", this.onResizeEnd);
|
|
131
|
+
this.persistWidths();
|
|
132
|
+
};
|
|
133
|
+
persistWidths() {
|
|
134
|
+
if (!this.resizeStorageKeyValue)
|
|
135
|
+
return;
|
|
136
|
+
const widths = {};
|
|
137
|
+
this.headerCells().forEach((th) => {
|
|
138
|
+
const index = th.dataset.colIndex;
|
|
139
|
+
if (index != null && th.style.width)
|
|
140
|
+
widths[index] = th.style.width;
|
|
141
|
+
});
|
|
142
|
+
try {
|
|
143
|
+
window.localStorage.setItem(this.storageKey, JSON.stringify(widths));
|
|
144
|
+
} catch {
|
|
44
145
|
}
|
|
45
146
|
}
|
|
147
|
+
restoreWidths() {
|
|
148
|
+
if (!this.resizeStorageKeyValue)
|
|
149
|
+
return;
|
|
150
|
+
let widths = {};
|
|
151
|
+
try {
|
|
152
|
+
widths = JSON.parse(window.localStorage.getItem(this.storageKey) ?? "{}");
|
|
153
|
+
} catch {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
this.headerCells().forEach((th) => {
|
|
157
|
+
const saved = th.dataset.colIndex != null ? widths[th.dataset.colIndex] : void 0;
|
|
158
|
+
if (saved)
|
|
159
|
+
th.style.width = saved;
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
get storageKey() {
|
|
163
|
+
return `midwest-table:${this.resizeStorageKeyValue}`;
|
|
164
|
+
}
|
|
165
|
+
headerCells() {
|
|
166
|
+
return Array.from(this.element.querySelectorAll("thead th[data-col-index]"));
|
|
167
|
+
}
|
|
168
|
+
// ─── inline editing ─────────────────────────────────────────────────────────
|
|
169
|
+
// Start editing when Enter or F2 is pressed on a focused editable cell. Ignore
|
|
170
|
+
// keydowns bubbling up from the edit input itself (its own handler owns those).
|
|
171
|
+
editCellKeydown(event) {
|
|
172
|
+
if (event.target !== event.currentTarget)
|
|
173
|
+
return;
|
|
174
|
+
if (event.key !== "Enter" && event.key !== "F2")
|
|
175
|
+
return;
|
|
176
|
+
const cell = event.currentTarget;
|
|
177
|
+
if (cell.dataset.editing === "true")
|
|
178
|
+
return;
|
|
179
|
+
event.preventDefault();
|
|
180
|
+
this.startEditing(cell);
|
|
181
|
+
}
|
|
182
|
+
editCell(event) {
|
|
183
|
+
this.startEditing(event.currentTarget);
|
|
184
|
+
}
|
|
185
|
+
startEditing(cell) {
|
|
186
|
+
if (cell.dataset.editing === "true")
|
|
187
|
+
return;
|
|
188
|
+
cell.dataset.editing = "true";
|
|
189
|
+
const input = document.createElement("input");
|
|
190
|
+
input.type = "text";
|
|
191
|
+
input.className = "midwest-table-edit-input";
|
|
192
|
+
input.value = cell.dataset.value ?? cell.textContent?.trim() ?? "";
|
|
193
|
+
cell.textContent = "";
|
|
194
|
+
cell.appendChild(input);
|
|
195
|
+
input.focus();
|
|
196
|
+
input.select();
|
|
197
|
+
input.addEventListener("keydown", (e) => {
|
|
198
|
+
if (e.key === "Enter") {
|
|
199
|
+
e.preventDefault();
|
|
200
|
+
this.commitEdit(cell, input);
|
|
201
|
+
} else if (e.key === "Escape") {
|
|
202
|
+
e.preventDefault();
|
|
203
|
+
this.cancelEdit(cell);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
input.addEventListener("blur", () => {
|
|
207
|
+
this.commitEdit(cell, input);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
commitEdit(cell, input) {
|
|
211
|
+
if (cell.dataset.editing !== "true")
|
|
212
|
+
return;
|
|
213
|
+
const newValue = input.value;
|
|
214
|
+
const previousValue = cell.dataset.value ?? "";
|
|
215
|
+
this.finishEditing(cell, newValue);
|
|
216
|
+
if (newValue === previousValue)
|
|
217
|
+
return;
|
|
218
|
+
this.dispatch("edit", {
|
|
219
|
+
bubbles: true,
|
|
220
|
+
detail: {
|
|
221
|
+
field: cell.dataset.field,
|
|
222
|
+
value: newValue,
|
|
223
|
+
previousValue,
|
|
224
|
+
rowId: cell.closest("tr")?.dataset.rowId,
|
|
225
|
+
cell
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
this.persistEdit(cell, newValue);
|
|
229
|
+
}
|
|
230
|
+
cancelEdit(cell) {
|
|
231
|
+
if (cell.dataset.editing !== "true")
|
|
232
|
+
return;
|
|
233
|
+
this.finishEditing(cell, cell.dataset.value ?? "");
|
|
234
|
+
}
|
|
235
|
+
// Replace the input with the resolved text, record the value, and return
|
|
236
|
+
// focus to the cell for keyboard users.
|
|
237
|
+
finishEditing(cell, value) {
|
|
238
|
+
cell.dataset.editing = "false";
|
|
239
|
+
cell.dataset.value = value;
|
|
240
|
+
cell.textContent = value;
|
|
241
|
+
cell.focus();
|
|
242
|
+
}
|
|
243
|
+
// PATCH the new value when the cell declares an edit URL. Persistence is
|
|
244
|
+
// otherwise the host app's responsibility via the midwest-table:edit event.
|
|
245
|
+
persistEdit(cell, value) {
|
|
246
|
+
const url = cell.dataset.editUrl;
|
|
247
|
+
if (!url)
|
|
248
|
+
return;
|
|
249
|
+
const token = document.querySelector('meta[name="csrf-token"]')?.getAttribute("content") ?? "";
|
|
250
|
+
void fetch(url, {
|
|
251
|
+
method: "PATCH",
|
|
252
|
+
headers: {
|
|
253
|
+
"Content-Type": "application/json",
|
|
254
|
+
"X-CSRF-Token": token,
|
|
255
|
+
Accept: "text/vnd.turbo-stream.html, application/json"
|
|
256
|
+
},
|
|
257
|
+
body: JSON.stringify({ field: cell.dataset.field, value })
|
|
258
|
+
}).then(async (response) => {
|
|
259
|
+
const contentType = response.headers.get("Content-Type") ?? "";
|
|
260
|
+
if (contentType.includes("turbo-stream")) {
|
|
261
|
+
const Turbo = window.Turbo;
|
|
262
|
+
Turbo?.renderStreamMessage?.(await response.text());
|
|
263
|
+
}
|
|
264
|
+
}).catch(() => {
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
get table() {
|
|
268
|
+
return this.element.querySelector("table");
|
|
269
|
+
}
|
|
46
270
|
}
|
|
47
271
|
|
|
48
272
|
export { Table as default };
|
|
@@ -1 +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 navigateRow (event: MouseEvent) {\n const target = event.target as HTMLElement\n if (target.closest('a, button, input, select, textarea, [role=\"button\"]')) return\n\n const row = event.currentTarget as HTMLElement\n const href = row.dataset.href\n if (!href) return\n\n if (event.metaKey || event.ctrlKey) {\n window.open(href, '_blank')\n } else {\n const Turbo = (window as any).Turbo\n if (Turbo?.visit) {\n Turbo.visit(href)\n } else {\n window.location.href = href\n }\n }\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,YAAa,KAAmB,EAAA;AAC9B,IAAA,MAAM,SAAS,KAAM,CAAA,MAAA,CAAA;AACrB,IAAI,IAAA,MAAA,CAAO,QAAQ,qDAAqD,CAAA;AAAG,MAAA,OAAA;AAE3E,IAAA,MAAM,MAAM,KAAM,CAAA,aAAA,CAAA;AAClB,IAAM,MAAA,IAAA,GAAO,IAAI,OAAQ,CAAA,IAAA,CAAA;AACzB,IAAA,IAAI,CAAC,IAAA;AAAM,MAAA,OAAA;AAEX,IAAI,IAAA,KAAA,CAAM,OAAW,IAAA,KAAA,CAAM,OAAS,EAAA;AAClC,MAAO,MAAA,CAAA,IAAA,CAAK,MAAM,QAAQ,CAAA,CAAA;AAAA,KACrB,MAAA;AACL,MAAA,MAAM,QAAS,MAAe,CAAA,KAAA,CAAA;AAC9B,MAAA,IAAI,OAAO,KAAO,EAAA;AAChB,QAAA,KAAA,CAAM,MAAM,IAAI,CAAA,CAAA;AAAA,OACX,MAAA;AACL,QAAA,MAAA,CAAO,SAAS,IAAO,GAAA,IAAA,CAAA;AAAA,OACzB;AAAA,KACF;AAAA,GACF;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;;;;"}
|
|
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', 'editable']\n static values = {\n resizable: { type: Boolean, default: false },\n resizeStorageKey: { type: String, default: '' }\n }\n\n declare readonly selectAllTarget: HTMLInputElement\n declare readonly rowCheckboxTargets: HTMLInputElement[]\n declare readonly hasSelectAllTarget: boolean\n declare resizableValue: boolean\n declare resizeStorageKeyValue: string\n\n private static readonly MIN_COLUMN_WIDTH = 48\n\n // Active resize gesture state.\n private _resizeTh: HTMLTableCellElement | null = null\n private _resizeStartX = 0\n private _resizeStartWidth = 0\n\n connect (): void {\n if (this.resizableValue) this.initResize()\n }\n\n // ─── selection ─────────────────────────────────────────────────────────────\n\n toggleAll (): void {\n const checked = this.selectAllTarget.checked\n this.rowCheckboxTargets.forEach(cb => { cb.checked = checked })\n }\n\n updateSelectAll (): void {\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 // Shift-clicking a sort header extends the multi-column sort chain instead of\n // replacing it: follow the header's data-multi-sort-url. A plain click falls\n // through to the anchor's single-sort href. Navigation stays frame-scoped by\n // driving the enclosing <turbo-frame>'s src when present.\n sortColumn (event: MouseEvent): void {\n if (!event.shiftKey) return\n const link = event.currentTarget as HTMLAnchorElement\n const url = link.dataset.multiSortUrl\n if (!url) return\n\n event.preventDefault()\n const frame = link.closest('turbo-frame') as (HTMLElement & { src: string }) | null\n if (frame != null) {\n frame.src = url\n } else {\n const Turbo = (window as any).Turbo\n if (Turbo?.visit) Turbo.visit(url)\n else window.location.href = url\n }\n }\n\n navigateRow (event: MouseEvent): void {\n const target = event.target as HTMLElement\n if (target.closest('a, button, input, select, textarea, [role=\"button\"], .editable')) return\n\n const row = event.currentTarget as HTMLElement\n const href = row.dataset.href\n if (!href) return\n\n if (event.metaKey || event.ctrlKey) {\n window.open(href, '_blank')\n } else {\n const Turbo = (window as any).Turbo\n if (Turbo?.visit) {\n Turbo.visit(href)\n } else {\n window.location.href = href\n }\n }\n }\n\n // ─── column resizing ────────────────────────────────────────────────────────\n\n // Freeze the current (auto-laid-out) column widths as explicit values, switch\n // to a fixed layout so drags don't reflow other columns, then apply any widths\n // saved from a previous session.\n private initResize (): void {\n const headers = this.headerCells()\n if (headers.length === 0) return\n\n headers.forEach((th) => { th.style.width = `${th.offsetWidth}px` })\n this.table?.classList.add('is-fixed')\n this.restoreWidths()\n }\n\n startResize (event: PointerEvent): void {\n const handle = event.target as HTMLElement\n const th = handle.closest('th') as HTMLTableCellElement | null\n if (th == null) return\n\n event.preventDefault()\n this._resizeTh = th\n this._resizeStartX = event.clientX\n this._resizeStartWidth = th.offsetWidth\n this.element.classList.add('is-resizing')\n\n window.addEventListener('pointermove', this.onResizeMove)\n window.addEventListener('pointerup', this.onResizeEnd)\n }\n\n // Double-clicking a handle clears that column's explicit width so it auto-sizes\n // to its content again.\n autoSizeColumn (event: Event): void {\n const th = (event.target as HTMLElement).closest('th') as HTMLTableCellElement | null\n if (th == null) return\n th.style.removeProperty('width')\n this.persistWidths()\n }\n\n private onResizeMove = (event: PointerEvent): void => {\n if (this._resizeTh == null) return\n const delta = event.clientX - this._resizeStartX\n const width = Math.max(Table.MIN_COLUMN_WIDTH, this._resizeStartWidth + delta)\n this._resizeTh.style.width = `${width}px`\n }\n\n private onResizeEnd = (): void => {\n this._resizeTh = null\n this.element.classList.remove('is-resizing')\n window.removeEventListener('pointermove', this.onResizeMove)\n window.removeEventListener('pointerup', this.onResizeEnd)\n this.persistWidths()\n }\n\n private persistWidths (): void {\n if (!this.resizeStorageKeyValue) return\n const widths: Record<string, string> = {}\n this.headerCells().forEach((th) => {\n const index = th.dataset.colIndex\n if (index != null && th.style.width) widths[index] = th.style.width\n })\n try {\n window.localStorage.setItem(this.storageKey, JSON.stringify(widths))\n } catch { /* storage unavailable — resizing still works for this session */ }\n }\n\n private restoreWidths (): void {\n if (!this.resizeStorageKeyValue) return\n let widths: Record<string, string> = {}\n try {\n widths = JSON.parse(window.localStorage.getItem(this.storageKey) ?? '{}')\n } catch { return }\n\n this.headerCells().forEach((th) => {\n const saved = th.dataset.colIndex != null ? widths[th.dataset.colIndex] : undefined\n if (saved) th.style.width = saved\n })\n }\n\n private get storageKey (): string {\n return `midwest-table:${this.resizeStorageKeyValue}`\n }\n\n private headerCells (): HTMLTableCellElement[] {\n return Array.from(this.element.querySelectorAll<HTMLTableCellElement>('thead th[data-col-index]'))\n }\n\n // ─── inline editing ─────────────────────────────────────────────────────────\n\n // Start editing when Enter or F2 is pressed on a focused editable cell. Ignore\n // keydowns bubbling up from the edit input itself (its own handler owns those).\n editCellKeydown (event: KeyboardEvent): void {\n if (event.target !== event.currentTarget) return\n if (event.key !== 'Enter' && event.key !== 'F2') return\n const cell = event.currentTarget as HTMLElement\n if (cell.dataset.editing === 'true') return\n event.preventDefault()\n this.startEditing(cell)\n }\n\n editCell (event: Event): void {\n this.startEditing(event.currentTarget as HTMLElement)\n }\n\n private startEditing (cell: HTMLElement): void {\n if (cell.dataset.editing === 'true') return\n cell.dataset.editing = 'true'\n\n const input = document.createElement('input')\n input.type = 'text'\n input.className = 'midwest-table-edit-input'\n input.value = cell.dataset.value ?? cell.textContent?.trim() ?? ''\n\n cell.textContent = ''\n cell.appendChild(input)\n input.focus()\n input.select()\n\n input.addEventListener('keydown', (e: KeyboardEvent) => {\n if (e.key === 'Enter') {\n e.preventDefault()\n this.commitEdit(cell, input)\n } else if (e.key === 'Escape') {\n e.preventDefault()\n this.cancelEdit(cell)\n }\n })\n input.addEventListener('blur', () => { this.commitEdit(cell, input) })\n }\n\n private commitEdit (cell: HTMLElement, input: HTMLInputElement): void {\n if (cell.dataset.editing !== 'true') return\n const newValue = input.value\n const previousValue = cell.dataset.value ?? ''\n this.finishEditing(cell, newValue)\n\n if (newValue === previousValue) return\n\n this.dispatch('edit', {\n bubbles: true,\n detail: {\n field: cell.dataset.field,\n value: newValue,\n previousValue,\n rowId: cell.closest('tr')?.dataset.rowId,\n cell\n }\n })\n this.persistEdit(cell, newValue)\n }\n\n private cancelEdit (cell: HTMLElement): void {\n if (cell.dataset.editing !== 'true') return\n this.finishEditing(cell, cell.dataset.value ?? '')\n }\n\n // Replace the input with the resolved text, record the value, and return\n // focus to the cell for keyboard users.\n private finishEditing (cell: HTMLElement, value: string): void {\n cell.dataset.editing = 'false'\n cell.dataset.value = value\n cell.textContent = value\n cell.focus()\n }\n\n // PATCH the new value when the cell declares an edit URL. Persistence is\n // otherwise the host app's responsibility via the midwest-table:edit event.\n private persistEdit (cell: HTMLElement, value: string): void {\n const url = cell.dataset.editUrl\n if (!url) return\n\n const token = document.querySelector('meta[name=\"csrf-token\"]')?.getAttribute('content') ?? ''\n void fetch(url, {\n method: 'PATCH',\n headers: {\n 'Content-Type': 'application/json',\n 'X-CSRF-Token': token,\n Accept: 'text/vnd.turbo-stream.html, application/json'\n },\n body: JSON.stringify({ field: cell.dataset.field, value })\n }).then(async (response) => {\n const contentType = response.headers.get('Content-Type') ?? ''\n if (contentType.includes('turbo-stream')) {\n const Turbo = (window as any).Turbo\n Turbo?.renderStreamMessage?.(await response.text())\n }\n }).catch(() => { /* network error — the app can reconcile from the edit event */ })\n }\n\n private get table (): HTMLTableElement | null {\n return this.element.querySelector('table')\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -62,12 +62,20 @@ declare class Chart extends Controller<HTMLElement> {
|
|
|
62
62
|
type: BooleanConstructor;
|
|
63
63
|
default: boolean;
|
|
64
64
|
};
|
|
65
|
+
filename: {
|
|
66
|
+
type: StringConstructor;
|
|
67
|
+
default: string;
|
|
68
|
+
};
|
|
65
69
|
};
|
|
66
70
|
readonly tooltipTarget: HTMLElement;
|
|
67
71
|
readonly hasTooltipTarget: boolean;
|
|
68
|
-
readonly svgTarget:
|
|
72
|
+
readonly svgTarget: SVGSVGElement;
|
|
69
73
|
readonly hasSvgTarget: boolean;
|
|
74
|
+
readonly dataTarget: HTMLElement;
|
|
75
|
+
readonly hasDataTarget: boolean;
|
|
70
76
|
tooltipEnabledValue: boolean;
|
|
77
|
+
filenameValue: string;
|
|
78
|
+
private static readonly STYLE_PROPS;
|
|
71
79
|
connect(): void;
|
|
72
80
|
disconnect(): void;
|
|
73
81
|
private attachListeners;
|
|
@@ -77,6 +85,13 @@ declare class Chart extends Controller<HTMLElement> {
|
|
|
77
85
|
private hideTooltip;
|
|
78
86
|
private positionAtPointer;
|
|
79
87
|
private positionAtElement;
|
|
88
|
+
exportSvg(): void;
|
|
89
|
+
exportPng(): void;
|
|
90
|
+
exportCsv(): void;
|
|
91
|
+
private csvCell;
|
|
92
|
+
private serializeSvg;
|
|
93
|
+
private inlineStyles;
|
|
94
|
+
private downloadBlob;
|
|
80
95
|
}
|
|
81
96
|
|
|
82
97
|
declare function registerMidwestControllers(application: any): void;
|