diff --git a/api/js/etemplate/Et2Select/FindSelectOptions.ts b/api/js/etemplate/Et2Select/FindSelectOptions.ts
index cd9603ea7e..a835ffaf88 100644
--- a/api/js/etemplate/Et2Select/FindSelectOptions.ts
+++ b/api/js/etemplate/Et2Select/FindSelectOptions.ts
@@ -1,3 +1,5 @@
+import {SearchResult} from "../Et2Widget/SearchMixin";
+
/**
* Interface for select options
*
@@ -5,22 +7,8 @@
* For option groups, value is the list of sub-options.
*
*/
-export interface SelectOption
+export interface SelectOption extends SearchResult
{
- value : string | SelectOption[];
- label : string;
- // Hover help text
- title? : string;
- // Related image or icon
- icon? : string;
- // Class applied to node
- class? : string;
- // Show the option, but it is not selectable.
- // If multiple=true and the option is in the value, it is not removable.
- disabled? : boolean;
- // If a search is in progress, does this option match.
- // Automatically changed.
- isMatch? : boolean;
}
/**
diff --git a/api/js/etemplate/Et2Tree/Et2Tree.ts b/api/js/etemplate/Et2Tree/Et2Tree.ts
index e06b3900cb..1e374d99d1 100644
--- a/api/js/etemplate/Et2Tree/Et2Tree.ts
+++ b/api/js/etemplate/Et2Tree/Et2Tree.ts
@@ -1,6 +1,6 @@
import {SlTreeItem} from "@shoelace-style/shoelace";
import {egw} from "../../jsapi/egw_global";
-import {find_select_options} from "../Et2Select/FindSelectOptions";
+import {find_select_options, SelectOption} from "../Et2Select/FindSelectOptions";
import {Et2WidgetWithSelectMixin} from "../Et2Select/Et2WidgetWithSelectMixin";
import {css, html, LitElement, nothing, PropertyValues, TemplateResult} from "lit";
import {repeat} from "lit/directives/repeat.js";
@@ -14,7 +14,7 @@ import {EgwActionObject} from "../../egw_action/EgwActionObject";
import {EgwAction} from "../../egw_action/EgwAction";
import {EgwDragDropShoelaceTree} from "../../egw_action/EgwDragDropShoelaceTree";
-export type TreeItemData = {
+export type TreeItemData = SelectOption & {
focused?: boolean;
// Has children, but they may not be provided in item
child: Boolean | 1,
@@ -652,7 +652,7 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement)
/*
if collapsed .. opended? leaf?
*/
- let img: String = selectOption.im0 ?? selectOption.im1 ?? selectOption.im2;
+ let img : String = selectOption.icon ?? selectOption.im0 ?? selectOption.im1 ?? selectOption.im2;
if (img)
{
//sl-icon images need to be svgs if there is a png try to find the corresponding svg
@@ -696,8 +696,8 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement)
>
- ${selectOption.text}
- ${selectOption.item ? repeat(selectOption.item, this._optionTemplate) : nothing}
+ ${selectOption.label ?? selectOption.text}
+ ${selectOption.children ? repeat(selectOption.children, this._optionTemplate) : (selectOption.item ? repeat(selectOption.item, this._optionTemplate) : nothing)}
`
}
diff --git a/api/js/etemplate/Et2Tree/Et2TreeDropdown.ts b/api/js/etemplate/Et2Tree/Et2TreeDropdown.ts
index 15966f101f..a2844c99d0 100644
--- a/api/js/etemplate/Et2Tree/Et2TreeDropdown.ts
+++ b/api/js/etemplate/Et2Tree/Et2TreeDropdown.ts
@@ -1,6 +1,6 @@
import {LitElement, nothing} from "lit";
import {html, literal, StaticValue} from "lit/static-html.js";
-import {Et2Tree, TreeItemData} from "./Et2Tree";
+import {Et2Tree, TreeItemData, TreeSearchResult} from "./Et2Tree";
import {Et2WidgetWithSelectMixin} from "../Et2Select/Et2WidgetWithSelectMixin";
import {property} from "lit/decorators/property.js";
import {classMap} from "lit/directives/class-map.js";
@@ -15,7 +15,7 @@ import {SearchMixin, SearchResult, SearchResultsInterface} from "../Et2Widget/Se
import {Et2InputWidgetInterface} from "../Et2InputWidget/Et2InputWidget";
-interface TreeSearchResult extends SearchResultsInterface
+interface TreeSearchResults extends SearchResultsInterface
{
}
@@ -43,7 +43,7 @@ type Constructor = new (...args : any[]) => T;
* @csspart form-control - The form control that wraps the label, input, and help text.
*/
-export class Et2TreeDropdown extends SearchMixin & Et2InputWidgetInterface & typeof LitElement, SearchResult, TreeSearchResult>(Et2WidgetWithSelectMixin(LitElement))
+export class Et2TreeDropdown extends SearchMixin & Et2InputWidgetInterface & typeof LitElement, TreeSearchResult, TreeSearchResults>(Et2WidgetWithSelectMixin(LitElement))
{
static get styles()
@@ -89,6 +89,11 @@ export class Et2TreeDropdown extends SearchMixin & Et2InputWidg
this.__value = [];
}
+ updated()
+ {
+ // @ts-ignore Popup sometimes loses the anchor which breaks the sizing
+ this._popup.handleAnchorChange();
+ }
/** Selected tree leaves */
@property()
set value(new_value : string | string[])
@@ -202,6 +207,22 @@ export class Et2TreeDropdown extends SearchMixin & Et2InputWidg
this.treeOrSearch = "search";
}
+
+ /**
+ * If you have a local list of options, you can search through them on the client and include them in the results.
+ *
+ * This is done independently from the server-side search, and the results are merged.
+ *
+ * @param {string} search
+ * @param {object} options
+ * @returns {Promise}
+ * @protected
+ */
+ protected localSearch(search : string, searchOptions : object, localOptions : DataType[] = []) : Promise
+ {
+ return super.localSearch(search, searchOptions, this.select_options);
+ }
+
protected searchResultSelected()
{
super.searchResultSelected();
@@ -219,6 +240,9 @@ export class Et2TreeDropdown extends SearchMixin & Et2InputWidg
// Done with search, show the tree
this.treeOrSearch = "tree";
+ // Close the dropdown
+ this.hide();
+ this.requestUpdate("value");
}
/**
@@ -448,7 +472,7 @@ export class Et2TreeDropdown extends SearchMixin & Et2InputWidg
return html`${map(value, (value, index) =>
{
// Deal with value that is not in options
- const option = this.optionSearch(value, this.select_options, 'value', 'item');
+ const option = this.optionSearch(value, this.select_options, 'value', 'children');
return option ? this.tagTemplate(option) : nothing;
})}`;
}
diff --git a/api/js/etemplate/Et2Widget/SearchMixin.ts b/api/js/etemplate/Et2Widget/SearchMixin.ts
index b19d1e9e1d..a6c676c174 100644
--- a/api/js/etemplate/Et2Widget/SearchMixin.ts
+++ b/api/js/etemplate/Et2Widget/SearchMixin.ts
@@ -27,6 +27,11 @@ export type SearchResult = {
// If a search is in progress, does this option match.
// Automatically changed.
isMatch? : boolean;
+
+ // The item has children (option group)
+ hasChildren? : boolean,
+ // The item's children
+ children? : SearchResult[]
}
/**
@@ -191,7 +196,7 @@ export const SearchMixin = }
* @protected
*/
- protected localSearch(search : string, options : object) : Promise
+ protected localSearch(search : string, searchOptions : object, localOptions : DataType[] = []) : Promise
{
- const nullResults : Results = {
+ const local : Results = {
results: [],
total: 0
}
- return Promise.resolve(this.processLocalResults(nullResults));
+ let doSearch = function (options : DataType[], value : string)
+ {
+ options.forEach((option) =>
+ {
+ if(typeof option !== "object")
+ {
+ return;
+ }
+ if(option.label?.includes(value) || option.value?.includes(value))
+ {
+ local.results.push(option);
+ local.total++;
+ }
+ if(typeof option.children != "undefined" && Array.isArray(option.children))
+ {
+ return doSearch(option.children, value);
+ }
+ });
+ }
+ doSearch(localOptions, search);
+
+ return Promise.resolve(this.processLocalResults(local));
}
protected remoteSearch(search : string, options : object) : Promise
@@ -375,7 +401,7 @@ export const SearchMixin =
diff --git a/api/src/Etemplate/Widget/Tree.php b/api/src/Etemplate/Widget/Tree.php
index 1df5766eb2..8a618e837e 100644
--- a/api/src/Etemplate/Widget/Tree.php
+++ b/api/src/Etemplate/Widget/Tree.php
@@ -523,9 +523,8 @@ class Tree extends Etemplate\Widget
$cat_id_list[] = $cat['id'];
if(!empty($cat['children']))
{
- $category['item'] = [];
- unset($cat['children']);
- static::processCategory($cat['id'], $category['item'], $categories, $globals, $cat_id_list);
+ unset($category['children']);
+ static::processCategory($cat['id'], $category['children'], $categories, $globals, $cat_id_list);
}
$options[] = $category;
}