Fix bad constructor when adding attrs that are not part of the parent and fix tom-select on safari

This commit is contained in:
Antoine Bartuccio 2024-10-16 14:59:02 +02:00
parent 677ff51ea5
commit 66dceefcf0
5 changed files with 26 additions and 21 deletions

View File

@ -1,15 +1,16 @@
import "tom-select/dist/css/tom-select.css"; import "tom-select/dist/css/tom-select.css";
import { InheritedComponent } from "#core:utils/web-components";
import TomSelect from "tom-select"; import TomSelect from "tom-select";
import type { TomItem, TomLoadCallback, TomOption } from "tom-select/dist/types/types"; import type { TomItem, TomLoadCallback, TomOption } from "tom-select/dist/types/types";
import type { escape_html } from "tom-select/dist/types/utils"; import type { escape_html } from "tom-select/dist/types/utils";
import { type UserProfileSchema, userSearchUsers } from "#openapi"; import { type UserProfileSchema, userSearchUsers } from "#openapi";
export class AjaxSelect extends HTMLSelectElement { export class AjaxSelect extends InheritedComponent<"select"> {
widget: TomSelect; widget: TomSelect;
filter?: <T>(items: T[]) => T[]; filter?: <T>(items: T[]) => T[];
constructor() { constructor() {
super(); super("select");
window.addEventListener("DOMContentLoaded", () => { window.addEventListener("DOMContentLoaded", () => {
this.loadTomSelect(); this.loadTomSelect();
@ -17,25 +18,25 @@ export class AjaxSelect extends HTMLSelectElement {
} }
loadTomSelect() { loadTomSelect() {
const minCharNumberForsearch = 2; const minCharNumberForSearch = 2;
let maxItems = 1; let maxItems = 1;
if (this.multiple) { if (this.node.multiple) {
maxItems = Number.parseInt(this.dataset.max) ?? null; maxItems = Number.parseInt(this.node.dataset.max) ?? null;
} }
this.widget = new TomSelect(this, { this.widget = new TomSelect(this.node, {
hideSelected: true, hideSelected: true,
diacritics: true, diacritics: true,
duplicates: false, duplicates: false,
maxItems: maxItems, maxItems: maxItems,
loadThrottle: Number.parseInt(this.dataset.delay) ?? null, loadThrottle: Number.parseInt(this.node.dataset.delay) ?? null,
valueField: "id", valueField: "id",
labelField: "display_name", labelField: "display_name",
searchField: ["display_name", "nick_name", "first_name", "last_name"], searchField: ["display_name", "nick_name", "first_name", "last_name"],
placeholder: this.dataset.placeholder ?? "", placeholder: this.node.dataset.placeholder ?? "",
shouldLoad: (query: string) => { shouldLoad: (query: string) => {
return query.length >= minCharNumberForsearch; // Avoid launching search with less than 2 characters return query.length >= minCharNumberForSearch; // Avoid launching search with less than 2 characters
}, },
load: (query: string, callback: TomLoadCallback) => { load: (query: string, callback: TomLoadCallback) => {
userSearchUsers({ userSearchUsers({
@ -70,7 +71,7 @@ export class AjaxSelect extends HTMLSelectElement {
}, },
// biome-ignore lint/style/useNamingConvention: that's how it's defined // biome-ignore lint/style/useNamingConvention: that's how it's defined
not_loading: (data: TomOption, _sanitize: typeof escape_html) => { not_loading: (data: TomOption, _sanitize: typeof escape_html) => {
return `<div class="no-results">${interpolate(gettext("You need to type %(number)s more characters"), { number: minCharNumberForsearch - data.input.length }, true)}</div>`; return `<div class="no-results">${interpolate(gettext("You need to type %(number)s more characters"), { number: minCharNumberForSearch - data.input.length }, true)}</div>`;
}, },
// biome-ignore lint/style/useNamingConvention: that's how it's defined // biome-ignore lint/style/useNamingConvention: that's how it's defined
no_results: (_data: TomOption, _sanitize: typeof escape_html) => { no_results: (_data: TomOption, _sanitize: typeof escape_html) => {
@ -85,4 +86,4 @@ export class AjaxSelect extends HTMLSelectElement {
} }
} }
window.customElements.define("ajax-select", AjaxSelect, { extends: "select" }); window.customElements.define("ajax-select", AjaxSelect);

View File

@ -187,7 +187,7 @@ class MarkdownInput extends InheritedComponent<"textarea"> {
constructor() { constructor() {
super("textarea"); super("textarea");
window.addEventListener("DOMContentLoaded", () => loadEasyMde(this.widget)); window.addEventListener("DOMContentLoaded", () => loadEasyMde(this.node));
} }
} }

View File

@ -4,7 +4,7 @@
* create a new web component * create a new web component
* create the desired type inside * create the desired type inside
* pass all attributes to the child component * pass all attributes to the child component
* store is at as a widget inside the parent * store is at as `node` inside the parent
* *
* To use this, you must use the tag name twice, once for creating the class * To use this, you must use the tag name twice, once for creating the class
* and the second time while calling super to pass it to the constructor * and the second time while calling super to pass it to the constructor
@ -12,20 +12,22 @@
export class InheritedComponent< export class InheritedComponent<
K extends keyof HTMLElementTagNameMap, K extends keyof HTMLElementTagNameMap,
> extends HTMLElement { > extends HTMLElement {
widget: HTMLElementTagNameMap[K]; node: HTMLElementTagNameMap[K];
constructor(tagName: K) { constructor(tagName: K) {
super(); super();
this.widget = document.createElement(tagName); this.node = document.createElement(tagName);
const attributes: Attr[] = []; // We need to make a copy to delete while iterating const attributes: Attr[] = []; // We need to make a copy to delete while iterating
for (const attr of this.attributes) { for (const attr of this.attributes) {
attributes.push(attr); if (attr.name in this.node) {
attributes.push(attr);
}
} }
for (const attr of attributes) { for (const attr of attributes) {
this.removeAttributeNode(attr); this.removeAttributeNode(attr);
this.widget.setAttributeNode(attr); this.node.setAttributeNode(attr);
} }
this.appendChild(this.widget); this.appendChild(this.node);
} }
} }

View File

@ -307,7 +307,10 @@ exportToHtml("loadViewer", (config: ViewerConfig) => {
}); });
// refresh the identified users list // refresh the identified users list
await this.currentPicture.loadIdentifications({ forceReload: true }); await this.currentPicture.loadIdentifications({ forceReload: true });
// Clear selection and cache of retrieved user so they can be filtered again
widget.clear(false); widget.clear(false);
widget.clearOptions();
}, },
/** /**

View File

@ -157,13 +157,12 @@
<h5>{% trans %}People{% endtrans %}</h5> <h5>{% trans %}People{% endtrans %}</h5>
{% if user.was_subscribed %} {% if user.was_subscribed %}
<form @submit.prevent="submitIdentification" x-show="!!selector"> <form @submit.prevent="submitIdentification" x-show="!!selector">
<select <ajax-select
x-ref="search" x-ref="search"
is="ajax-select"
multiple multiple
data-delay="300" data-delay="300"
data-placeholder="{%- trans -%}Identify users on pictures{%- endtrans -%}" data-placeholder="{%- trans -%}Identify users on pictures{%- endtrans -%}"
></select> ></ajax-select>
<input type="submit" value="{% trans %}Go{% endtrans %}"/> <input type="submit" value="{% trans %}Go{% endtrans %}"/>
</form> </form>
{% endif %} {% endif %}