mirror of
https://github.com/sissbruecker/linkding.git
synced 2026-02-27 22:43:15 +08:00
Fix web component initialization timing
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
class BookmarkPage extends HTMLElement {
|
||||
connectedCallback() {
|
||||
import { HeadlessElement } from "../utils/element.js";
|
||||
|
||||
class BookmarkPage extends HeadlessElement {
|
||||
init() {
|
||||
this.update = this.update.bind(this);
|
||||
this.onToggleNotes = this.onToggleNotes.bind(this);
|
||||
this.onToggleBulkEdit = this.onToggleBulkEdit.bind(this);
|
||||
@@ -17,13 +19,11 @@ class BookmarkPage extends HTMLElement {
|
||||
}
|
||||
|
||||
update() {
|
||||
requestAnimationFrame(() => {
|
||||
const items = this.querySelectorAll("ul.bookmark-list > li");
|
||||
this.updateTooltips(items);
|
||||
this.updateNotesToggles(items, this.oldItems);
|
||||
this.updateBulkEdit(items, this.oldItems);
|
||||
this.oldItems = items;
|
||||
});
|
||||
const items = this.querySelectorAll("ul.bookmark-list > li");
|
||||
this.updateTooltips(items);
|
||||
this.updateNotesToggles(items, this.oldItems);
|
||||
this.updateBulkEdit(items, this.oldItems);
|
||||
this.oldItems = items;
|
||||
}
|
||||
|
||||
updateTooltips(items) {
|
||||
|
||||
@@ -1,28 +1,19 @@
|
||||
class ClearButton extends HTMLElement {
|
||||
connectedCallback() {
|
||||
requestAnimationFrame(() => {
|
||||
this.field = document.getElementById(this.dataset.for);
|
||||
if (!this.field) {
|
||||
console.error(`Field with ID ${this.dataset.for} not found`);
|
||||
return;
|
||||
}
|
||||
this.update = this.update.bind(this);
|
||||
this.clear = this.clear.bind(this);
|
||||
import { HeadlessElement } from "../utils/element";
|
||||
|
||||
this.addEventListener("click", this.clear);
|
||||
this.field.addEventListener("input", this.update);
|
||||
this.field.addEventListener("value-changed", this.update);
|
||||
this.update();
|
||||
});
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
class ClearButton extends HeadlessElement {
|
||||
init() {
|
||||
this.field = document.getElementById(this.dataset.for);
|
||||
if (!this.field) {
|
||||
console.error(`Field with ID ${this.dataset.for} not found`);
|
||||
return;
|
||||
}
|
||||
this.removeEventListener("click", this.clear);
|
||||
this.field.removeEventListener("input", this.update);
|
||||
this.field.removeEventListener("value-changed", this.update);
|
||||
this.update = this.update.bind(this);
|
||||
this.clear = this.clear.bind(this);
|
||||
|
||||
this.addEventListener("click", this.clear);
|
||||
this.field.addEventListener("input", this.update);
|
||||
this.field.addEventListener("value-changed", this.update);
|
||||
this.update();
|
||||
}
|
||||
|
||||
update() {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
class Dropdown extends HTMLElement {
|
||||
import { HeadlessElement } from "../utils/element.js";
|
||||
|
||||
class Dropdown extends HeadlessElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.opened = false;
|
||||
@@ -8,26 +10,20 @@ class Dropdown extends HTMLElement {
|
||||
this.onFocusOut = this.onFocusOut.bind(this);
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
// Defer setup to next frame when children are available in the DOM
|
||||
requestAnimationFrame(() => {
|
||||
// Prevent opening the dropdown automatically on focus, so that it only
|
||||
// opens on click when JS is enabled
|
||||
this.style.setProperty("--dropdown-focus-display", "none");
|
||||
this.addEventListener("keydown", this.onEscape);
|
||||
this.addEventListener("focusout", this.onFocusOut);
|
||||
init() {
|
||||
// Prevent opening the dropdown automatically on focus, so that it only
|
||||
// opens on click when JS is enabled
|
||||
this.style.setProperty("--dropdown-focus-display", "none");
|
||||
this.addEventListener("keydown", this.onEscape);
|
||||
this.addEventListener("focusout", this.onFocusOut);
|
||||
|
||||
this.toggle = this.querySelector(".dropdown-toggle");
|
||||
this.toggle.setAttribute("aria-expanded", "false");
|
||||
this.toggle.addEventListener("click", this.onClick);
|
||||
});
|
||||
this.toggle = this.querySelector(".dropdown-toggle");
|
||||
this.toggle.setAttribute("aria-expanded", "false");
|
||||
this.toggle.addEventListener("click", this.onClick);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.close();
|
||||
this.toggle?.removeEventListener("click", this.onClick);
|
||||
this.removeEventListener("keydown", this.onEscape);
|
||||
this.removeEventListener("focusout", this.onFocusOut);
|
||||
}
|
||||
|
||||
open() {
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import { html, render } from "lit";
|
||||
import { Modal } from "./modal.js";
|
||||
import { HeadlessElement } from "../utils/element.js";
|
||||
import { isKeyboardActive } from "../utils/focus.js";
|
||||
|
||||
class FilterDrawerTrigger extends HTMLElement {
|
||||
connectedCallback() {
|
||||
class FilterDrawerTrigger extends HeadlessElement {
|
||||
init() {
|
||||
this.onClick = this.onClick.bind(this);
|
||||
this.addEventListener("click", this.onClick.bind(this));
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.removeEventListener("click", this.onClick.bind(this));
|
||||
}
|
||||
|
||||
onClick() {
|
||||
const modal = document.createElement("ld-filter-drawer");
|
||||
document.body.querySelector(".modals").appendChild(modal);
|
||||
@@ -67,8 +64,8 @@ class FilterDrawer extends Modal {
|
||||
this.getBoundingClientRect();
|
||||
// Add active class to start slide-in animation
|
||||
requestAnimationFrame(() => this.classList.add("active"));
|
||||
// Call super after rendering to ensure elements are available
|
||||
super.connectedCallback();
|
||||
// Call super.init() after rendering to ensure elements are available
|
||||
super.init();
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
|
||||
@@ -1,28 +1,26 @@
|
||||
class Form extends HTMLElement {
|
||||
import { HeadlessElement } from "../utils/element.js";
|
||||
|
||||
class Form extends HeadlessElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.onKeyDown = this.onKeyDown.bind(this);
|
||||
this.onChange = this.onChange.bind(this);
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
init() {
|
||||
this.addEventListener("keydown", this.onKeyDown);
|
||||
this.addEventListener("change", this.onChange);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if (this.hasAttribute("data-form-reset")) {
|
||||
// Resets form controls to their initial values before Turbo caches the DOM.
|
||||
// Useful for filter forms where navigating back would otherwise still show
|
||||
// values from after the form submission, which means the filters would be out
|
||||
// of sync with the URL.
|
||||
this.initFormReset();
|
||||
}
|
||||
});
|
||||
if (this.hasAttribute("data-form-reset")) {
|
||||
// Resets form controls to their initial values before Turbo caches the DOM.
|
||||
// Useful for filter forms where navigating back would otherwise still show
|
||||
// values from after the form submission, which means the filters would be out
|
||||
// of sync with the URL.
|
||||
this.initFormReset();
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.removeEventListener("keydown", this.onKeyDown);
|
||||
this.removeEventListener("change", this.onChange);
|
||||
if (this.hasAttribute("data-form-reset")) {
|
||||
this.resetForm();
|
||||
}
|
||||
|
||||
@@ -1,29 +1,23 @@
|
||||
import { FocusTrapController } from "../utils/focus.js";
|
||||
import { HeadlessElement } from "../utils/element.js";
|
||||
|
||||
export class Modal extends HTMLElement {
|
||||
connectedCallback() {
|
||||
requestAnimationFrame(() => {
|
||||
this.onClose = this.onClose.bind(this);
|
||||
this.onKeyDown = this.onKeyDown.bind(this);
|
||||
export class Modal extends HeadlessElement {
|
||||
init() {
|
||||
this.onClose = this.onClose.bind(this);
|
||||
this.onKeyDown = this.onKeyDown.bind(this);
|
||||
|
||||
this.querySelectorAll("[data-close-modal]").forEach((btn) => {
|
||||
btn.addEventListener("click", this.onClose);
|
||||
});
|
||||
document.addEventListener("keydown", this.onKeyDown);
|
||||
|
||||
this.setupScrollLock();
|
||||
this.focusTrap = new FocusTrapController(
|
||||
this.querySelector(".modal-container"),
|
||||
);
|
||||
this.querySelectorAll("[data-close-modal]").forEach((btn) => {
|
||||
btn.addEventListener("click", this.onClose);
|
||||
});
|
||||
this.addEventListener("keydown", this.onKeyDown);
|
||||
|
||||
this.setupScrollLock();
|
||||
this.focusTrap = new FocusTrapController(
|
||||
this.querySelector(".modal-container"),
|
||||
);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.querySelectorAll("[data-close-modal]").forEach((btn) => {
|
||||
btn.removeEventListener("click", this.onClose);
|
||||
});
|
||||
document.removeEventListener("keydown", this.onKeyDown);
|
||||
|
||||
this.removeScrollLock();
|
||||
this.focusTrap.destroy();
|
||||
}
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
class UploadButton extends HTMLElement {
|
||||
connectedCallback() {
|
||||
import { HeadlessElement } from "../utils/element.js";
|
||||
|
||||
class UploadButton extends HeadlessElement {
|
||||
init() {
|
||||
this.onClick = this.onClick.bind(this);
|
||||
this.onChange = this.onChange.bind(this);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
this.button = this.querySelector('button[type="submit"]');
|
||||
this.button.addEventListener("click", this.onClick);
|
||||
this.button = this.querySelector('button[type="submit"]');
|
||||
this.button.addEventListener("click", this.onClick);
|
||||
|
||||
this.fileInput = this.querySelector('input[type="file"]');
|
||||
this.fileInput.addEventListener("change", this.onChange);
|
||||
});
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.button.removeEventListener("click", this.onClick);
|
||||
this.fileInput.removeEventListener("change", this.onChange);
|
||||
this.fileInput = this.querySelector('input[type="file"]');
|
||||
this.fileInput.addEventListener("change", this.onChange);
|
||||
}
|
||||
|
||||
onClick(event) {
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
import { LitElement } from "lit";
|
||||
|
||||
/**
|
||||
* Base class for custom elements that wrap existing server-rendered DOM.
|
||||
*
|
||||
* Handles timing issues where connectedCallback fires before child elements
|
||||
* are parsed during initial page load. With Turbo navigation, children are
|
||||
* always available, but on fresh page loads they may not be.
|
||||
*
|
||||
* Subclasses should override init() instead of connectedCallback().
|
||||
*/
|
||||
export class HeadlessElement extends HTMLElement {
|
||||
connectedCallback() {
|
||||
if (this.__initialized) {
|
||||
return;
|
||||
}
|
||||
this.__initialized = true;
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("turbo:load", () => this.init(), {
|
||||
once: true,
|
||||
});
|
||||
} else {
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
// Override in subclass
|
||||
}
|
||||
}
|
||||
|
||||
let isTopFrameVisit = false;
|
||||
|
||||
document.addEventListener("turbo:visit", (event) => {
|
||||
|
||||
Reference in New Issue
Block a user