mirror of
https://github.com/we-promise/sure.git
synced 2026-05-30 15:59:02 +00:00
fix(goals JS): listener cleanup on color/icon picker + debounce filter URL sync
- color_icon_picker: listeners were attached in initialize() with inline arrows, so they had no removable reference and no disconnect() cleanup. Every Turbo navigation that re-rendered the picker stacked another listener on the same node and left the Pickr instance alive. Move attachment to connect(), store bound references, and clean up in disconnect() (including destroyAndRemove on the Pickr). - goals_filter: replaceState fired on every keystroke. Debounce the URL sync 200 ms so a typing burst collapses into a single update. The visible filtering stays real-time. Clear the timer in disconnect() so a pending sync doesn't fire on an unmounted controller.
This commit is contained in:
@@ -22,31 +22,42 @@ export default class extends Controller {
|
||||
presetColors: Array,
|
||||
};
|
||||
|
||||
initialize() {
|
||||
this.pickerBtnTarget.addEventListener("click", () => {
|
||||
this.showPaletteSection();
|
||||
});
|
||||
|
||||
this.colorInputTarget.addEventListener("input", (e) => {
|
||||
this.picker.setColor(e.target.value);
|
||||
});
|
||||
|
||||
this.detailsTarget.addEventListener("toggle", (e) => {
|
||||
connect() {
|
||||
// Bound references stored on the instance so disconnect() can remove
|
||||
// them. Without this, every Turbo navigation that re-renders the
|
||||
// picker stacks another listener on the same node.
|
||||
this._onPickerBtnClick = () => this.showPaletteSection();
|
||||
this._onColorInputInput = (e) => this.picker?.setColor(e.target.value);
|
||||
this._onDetailsToggle = (e) => {
|
||||
if (!this.colorInputTarget.checkValidity()) {
|
||||
e.preventDefault();
|
||||
this.colorInputTarget.reportValidity();
|
||||
e.target.open = true;
|
||||
}
|
||||
this.updatePopupPosition()
|
||||
});
|
||||
this.updatePopupPosition();
|
||||
};
|
||||
|
||||
this.pickerBtnTarget.addEventListener("click", this._onPickerBtnClick);
|
||||
this.colorInputTarget.addEventListener("input", this._onColorInputInput);
|
||||
this.detailsTarget.addEventListener("toggle", this._onDetailsToggle);
|
||||
document.addEventListener("mousedown", this.handleOutsideClick);
|
||||
|
||||
this.selectedIcon = null;
|
||||
|
||||
if (!this.presetColorsValue.includes(this.colorInputTarget.value)) {
|
||||
this.colorPickerRadioBtnTarget.checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("mousedown", this.handleOutsideClick);
|
||||
disconnect() {
|
||||
this.pickerBtnTarget.removeEventListener("click", this._onPickerBtnClick);
|
||||
this.colorInputTarget.removeEventListener("input", this._onColorInputInput);
|
||||
this.detailsTarget.removeEventListener("toggle", this._onDetailsToggle);
|
||||
document.removeEventListener("mousedown", this.handleOutsideClick);
|
||||
if (this.picker) {
|
||||
this.picker.destroyAndRemove();
|
||||
this.picker = null;
|
||||
}
|
||||
}
|
||||
|
||||
initPicker() {
|
||||
|
||||
@@ -32,6 +32,10 @@ export default class extends Controller {
|
||||
}
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
clearTimeout(this._urlSyncTimer);
|
||||
}
|
||||
|
||||
filter() {
|
||||
const query = this.hasInputTarget
|
||||
? this.inputTarget.value.toLocaleLowerCase().trim()
|
||||
@@ -60,7 +64,16 @@ export default class extends Controller {
|
||||
}
|
||||
|
||||
this.updateEmptyState(visible, query, active);
|
||||
this.#syncUrl();
|
||||
this.#scheduleUrlSync();
|
||||
}
|
||||
|
||||
// Debounced wrapper. Firing replaceState on every keystroke is wasteful
|
||||
// and produced visible jank on slow CPUs; deferring 200 ms collapses a
|
||||
// typing burst into a single URL update without losing back-button
|
||||
// fidelity (replaceState doesn't create history entries anyway).
|
||||
#scheduleUrlSync() {
|
||||
clearTimeout(this._urlSyncTimer);
|
||||
this._urlSyncTimer = setTimeout(() => this.#syncUrl(), 200);
|
||||
}
|
||||
|
||||
#hydrateFromUrl() {
|
||||
|
||||
Reference in New Issue
Block a user