refactor(base): 重构文档和列表导航(by AI)
This commit is contained in:
parent
2da203c513
commit
4b010c88c2
@ -250,8 +250,6 @@
|
||||
- **`collapsible`** (Boolean): 仅在 `tree` 模式下有效,开启树形节点的展开/收起能力。
|
||||
- **`auto-select`** (Boolean): 开启后,点击列表项时自动将该项的 `id` 记录至 `state.selectedItem`,如果重复点击则会置空。
|
||||
- **`auto-select-group`** (Boolean): 开启后,点击分组时自动将分组的 `id` 记录至 `state.selectedGroup`。
|
||||
- **`filter`** (String): 筛选关键字。支持通过 attribute 传入以自动同步至 `state.filter`。
|
||||
- **`order`** (String): 排序规则。支持通过 attribute 传入以自动同步至 `state.order`。
|
||||
|
||||
### 4. 内部状态模型 (`State`)
|
||||
- `list` (读写): 原始列表数据数组。
|
||||
@ -259,11 +257,15 @@
|
||||
- `collapsed` (读写): 仅在 `tree` 且 `collapsible` 时使用,存储节点 ID 折叠状态的 Map Proxy。
|
||||
- `selectedItem` (读写): 当前选中的列表项 ID。
|
||||
- `selectedGroup` (读写): 当前选中的分组 ID。
|
||||
- `filter` (读写): 筛选关键字(进行 label 和 summary 的不区分大小写模糊匹配)或自定义过滤函数 `(item) => boolean`。
|
||||
- `order` (读写): 排序规则。可为排序字段名(例如 `'name'`;前缀 `-` 表示降序,如 `'-name'`;或者后缀 `'desc'`/`'asc'` 语法,如 `'name desc'`),也可以为自定义排序函数 `(a, b) => number`。
|
||||
- `filter` (读写): 筛选规则。可为字符串(对 label 和 summary 进行忽略大小写的模糊包含匹配)或对象 `{ [key]: query }`(对对象特定字段进行模糊包含匹配)。
|
||||
- `order` (读写): 排序规则。可为字段名字符串(例如 `'name'`;支持前缀 `-` 表示降序,如 `'-name'`;或者后缀 `'desc'`/`'asc'` 语法,如 `'name desc'`)。
|
||||
- `_flatList` (只读): 列表核心逻辑处理后的扁平化数组。
|
||||
- `_renderedList` (只读): 实际在 DOM 中遍历渲染的数组片段(若开启虚拟滚动,则只包含可视区切片)。
|
||||
|
||||
### 5. 实例属性 (DOM Node Properties)
|
||||
- `filterFunc` (读写): 自定义过滤函数 `(item) => boolean`。设置后会重绘列表。
|
||||
- `orderFunc` (读写): 自定义排序比较函数 `(a, b) => number`。设置后会重绘列表。
|
||||
|
||||
### 5. 插槽 (Slots)
|
||||
- **`item`**:列表单项渲染模板插槽。在自定义模板中,可以访问当前行数据 `item` 和索引 `index`。
|
||||
- **`item-actions`**:行数据右侧按钮工具栏区域。
|
||||
|
||||
171
dist/base.js
vendored
171
dist/base.js
vendored
@ -761,16 +761,48 @@
|
||||
};
|
||||
container.collapsed = globalThis.NewState({});
|
||||
container.state.renderedList = [];
|
||||
container.addEventListener("bind", (e) => {
|
||||
container.state.selectedItem = e.detail;
|
||||
});
|
||||
const matchString = (val, query) => {
|
||||
if (query === void 0 || query === null || query === "") return true;
|
||||
return String(val || "").toLowerCase().includes(String(query).toLowerCase());
|
||||
};
|
||||
const matchesItem = (item, filter, filterFunc) => {
|
||||
if (filterFunc && !filterFunc(item)) return false;
|
||||
if (filter === 0) filter = "";
|
||||
if (filter !== void 0 && filter !== null && filter !== "") {
|
||||
if (typeof filter === "object") {
|
||||
for (const [key, qVal] of Object.entries(filter)) {
|
||||
if (qVal !== void 0 && qVal !== null && qVal !== "") {
|
||||
if (!matchString(item[key], qVal)) return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const q = String(filter).trim();
|
||||
if (q) {
|
||||
const labelMatches = matchString(item[container.labelfield], q);
|
||||
const summaryMatches = matchString(item[container.summaryfield], q);
|
||||
if (!labelMatches && !summaryMatches) return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const updateFlatList = () => {
|
||||
var _a;
|
||||
globalThis.Util.updateDefaults(container, defaultSets);
|
||||
let list = container.state.list || [];
|
||||
const filter = container.state.filter;
|
||||
const order = container.state.order;
|
||||
if (order) {
|
||||
if (typeof order === "function") {
|
||||
list = [...list].sort(order);
|
||||
} else if (typeof order === "string") {
|
||||
let filter = container.state.filter;
|
||||
let order = container.state.order;
|
||||
const filterFunc = container._filterFunc;
|
||||
const orderFunc = container._orderFunc;
|
||||
if (filter === 0) filter = "";
|
||||
if (order === 0) order = "";
|
||||
if (orderFunc) {
|
||||
list = [...list].sort(orderFunc);
|
||||
} else if (order) {
|
||||
if (typeof order === "string") {
|
||||
const parts = order.trim().split(/\s+/);
|
||||
const field = parts[0];
|
||||
const isDesc = ((_a = parts[1]) == null ? void 0 : _a.toLowerCase()) === "desc" || field.startsWith("-");
|
||||
@ -789,21 +821,7 @@
|
||||
}
|
||||
const flatList = [];
|
||||
if (container.mode === "group") {
|
||||
let filteredList = list;
|
||||
if (filter) {
|
||||
if (typeof filter === "function") {
|
||||
filteredList = list.filter(filter);
|
||||
} else if (typeof filter === "string") {
|
||||
const q = filter.trim().toLowerCase();
|
||||
if (q) {
|
||||
filteredList = list.filter((item) => {
|
||||
const label = String(item[container.labelfield] || "").toLowerCase();
|
||||
const summary = String(item[container.summaryfield] || "").toLowerCase();
|
||||
return label.includes(q) || summary.includes(q);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
const filteredList = list.filter((item) => matchesItem(item, filter, filterFunc));
|
||||
const itemMap = {};
|
||||
filteredList.forEach((item) => {
|
||||
var _a2;
|
||||
@ -811,11 +829,26 @@
|
||||
});
|
||||
(container.state.groups || []).forEach((group) => {
|
||||
const items = itemMap[group[container.groupidfield]];
|
||||
const groupLabel = String(group[container.grouplabelfield] || "").toLowerCase();
|
||||
const groupSummary = String(group[container.groupsummaryfield] || "").toLowerCase();
|
||||
const q = typeof filter === "string" ? filter.trim().toLowerCase() : "";
|
||||
const groupMatches = q ? groupLabel.includes(q) || groupSummary.includes(q) : false;
|
||||
if (items && items.length > 0 || groupMatches || !filter) {
|
||||
let groupMatches = false;
|
||||
if (filter !== void 0 && filter !== null && filter !== "") {
|
||||
if (typeof filter === "object") {
|
||||
groupMatches = true;
|
||||
for (const [key, qVal] of Object.entries(filter)) {
|
||||
if (qVal !== void 0 && qVal !== null && qVal !== "") {
|
||||
if (!matchString(group[key], qVal)) {
|
||||
groupMatches = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const q = String(filter).trim();
|
||||
if (q) {
|
||||
groupMatches = matchString(group[container.grouplabelfield], q) || matchString(group[container.groupsummaryfield], q);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (items && items.length > 0 || groupMatches || !filter && !filterFunc) {
|
||||
flatList.push({ type: "group", ...group });
|
||||
if (items) items.forEach((item) => flatList.push({ type: "item", ...item }));
|
||||
}
|
||||
@ -827,27 +860,12 @@
|
||||
});
|
||||
const matches = /* @__PURE__ */ new Set();
|
||||
list.forEach((item) => {
|
||||
let match = false;
|
||||
if (!filter) {
|
||||
match = true;
|
||||
} else if (typeof filter === "function") {
|
||||
match = filter(item);
|
||||
} else if (typeof filter === "string") {
|
||||
const q = filter.trim().toLowerCase();
|
||||
if (!q) {
|
||||
match = true;
|
||||
} else {
|
||||
const label = String(item[container.labelfield] || "").toLowerCase();
|
||||
const summary = String(item[container.summaryfield] || "").toLowerCase();
|
||||
match = label.includes(q) || summary.includes(q);
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
if (matchesItem(item, filter, filterFunc)) {
|
||||
matches.add(item[container.idfield]);
|
||||
}
|
||||
});
|
||||
const keep = new Set(matches);
|
||||
if (filter) {
|
||||
if (filter || filterFunc) {
|
||||
matches.forEach((id) => {
|
||||
let curr = itemMap[id];
|
||||
while (curr) {
|
||||
@ -861,7 +879,7 @@
|
||||
}
|
||||
const childrenMap = {};
|
||||
list.forEach((item) => {
|
||||
if (!filter || keep.has(item[container.idfield])) {
|
||||
if (!filter && !filterFunc || keep.has(item[container.idfield])) {
|
||||
const parentId = item[container.parentfield] || "";
|
||||
(childrenMap[parentId] ?? (childrenMap[parentId] = [])).push(item);
|
||||
}
|
||||
@ -876,21 +894,7 @@
|
||||
});
|
||||
traverse(childrenMap[""] || [], 0, []);
|
||||
} else {
|
||||
let filteredList = list;
|
||||
if (filter) {
|
||||
if (typeof filter === "function") {
|
||||
filteredList = list.filter(filter);
|
||||
} else if (typeof filter === "string") {
|
||||
const q = filter.trim().toLowerCase();
|
||||
if (q) {
|
||||
filteredList = list.filter((item) => {
|
||||
const label = String(item[container.labelfield] || "").toLowerCase();
|
||||
const summary = String(item[container.summaryfield] || "").toLowerCase();
|
||||
return label.includes(q) || summary.includes(q);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
const filteredList = list.filter((item) => matchesItem(item, filter, filterFunc));
|
||||
filteredList.forEach((item) => flatList.push({ type: "item", ...item }));
|
||||
}
|
||||
container.state.flatList = flatList;
|
||||
@ -932,7 +936,10 @@
|
||||
} else container.state.renderedList = flatList;
|
||||
});
|
||||
container.selectItem = (item, index2) => {
|
||||
if (container.hasAttribute("auto-select")) container.state.selectedItem = container.state.selectedItem === item[container.idfield] ? null : item[container.idfield];
|
||||
if (container.hasAttribute("auto-select")) {
|
||||
container.state.selectedItem = container.state.selectedItem === item[container.idfield] ? null : item[container.idfield];
|
||||
}
|
||||
container.dispatchEvent(new CustomEvent("change", { detail: container.state.selectedItem, bubbles: false }));
|
||||
container.dispatchEvent(new CustomEvent("itemclick", { bubbles: false, detail: { item, index: index2 + (container.fast ? container.state.listStartIndex || 0 : 0) } }));
|
||||
};
|
||||
container.selectGroup = (item, index2) => {
|
||||
@ -945,31 +952,20 @@
|
||||
updateFlatList();
|
||||
}
|
||||
};
|
||||
Object.defineProperty(container, "filter", {
|
||||
get: () => container.state.filter,
|
||||
Object.defineProperty(container, "filterFunc", {
|
||||
get: () => container._filterFunc,
|
||||
set: (v) => {
|
||||
container.state.filter = v;
|
||||
container._filterFunc = v;
|
||||
updateFlatList();
|
||||
}
|
||||
});
|
||||
Object.defineProperty(container, "order", {
|
||||
get: () => container.state.order,
|
||||
Object.defineProperty(container, "orderFunc", {
|
||||
get: () => container._orderFunc,
|
||||
set: (v) => {
|
||||
container.state.order = v;
|
||||
container._orderFunc = v;
|
||||
updateFlatList();
|
||||
}
|
||||
});
|
||||
const observer = new MutationObserver(() => {
|
||||
const filterAttr = container.getAttribute("filter");
|
||||
if (typeof container.state.filter !== "function" && filterAttr !== container.state.filter) {
|
||||
container.state.filter = filterAttr || void 0;
|
||||
}
|
||||
const orderAttr = container.getAttribute("order");
|
||||
if (typeof container.state.order !== "function" && orderAttr !== container.state.order) {
|
||||
container.state.order = orderAttr || void 0;
|
||||
}
|
||||
});
|
||||
observer.observe(container, { attributes: true, attributeFilter: ["filter", "order"] });
|
||||
container.state.filter = container.getAttribute("filter") || void 0;
|
||||
container.state.order = container.getAttribute("order") || void 0;
|
||||
updateFlatList();
|
||||
}, globalThis.Util.makeDom(
|
||||
/*html*/
|
||||
@ -1001,7 +997,7 @@
|
||||
<span $class="bi bi-\${this.itemicon} text-body"></span>
|
||||
</template>
|
||||
<div class="flex-shrink-0 px-1" $text="\${item[this.labelfield]}"></div>
|
||||
<div class="text-muted small flex-fill text-end" $text="\${item[this.summaryfield]}"></div>
|
||||
<div class="text-muted fs-7 small ms-2 flex-fill text-truncate text-end" $text="\${item[this.summaryfield]}"></div>
|
||||
<div slot-id="item-actions"></div>
|
||||
</template>
|
||||
</div>
|
||||
@ -1013,11 +1009,20 @@
|
||||
globalThis.Component.register("Nav", (container) => {
|
||||
container.vertical = container.hasAttribute("vertical");
|
||||
container.state.openName = container.state.openName || null;
|
||||
container.state.value = container.state.value || null;
|
||||
container.addEventListener("bind", (e) => {
|
||||
container.state.value = e.detail;
|
||||
});
|
||||
container.click = (item, noselect) => {
|
||||
if (!item.noselect && !noselect) globalThis.Hash.nav = item.name;
|
||||
if (!item.noselect && !noselect) {
|
||||
container.state.value = item.name;
|
||||
container.dispatchEvent(new CustomEvent("change", { detail: item.name, bubbles: false }));
|
||||
}
|
||||
container.dispatchEvent(new CustomEvent("nav", { detail: { item }, bubbles: false }));
|
||||
};
|
||||
container.clickSubitem = (item) => {
|
||||
container.state.value = item.name;
|
||||
container.dispatchEvent(new CustomEvent("change", { detail: item.name, bubbles: false }));
|
||||
container.dispatchEvent(new CustomEvent("nav", { detail: { item }, bubbles: false }));
|
||||
};
|
||||
container.toggleGroup = (item) => {
|
||||
@ -1027,7 +1032,7 @@
|
||||
}, globalThis.Util.makeDom(
|
||||
/*html*/
|
||||
`
|
||||
<div $class="\${this.vertical ? 'd-flex flex-column border-end h-100 align-self-stretch overflow-visible' : 'navbar navbar-expand border-bottom'} bg-body-secondary px-2 \${this.vertical ? 'py-3' : 'py-0'} align-items-center \${this.vertical ? 'align-items-start' : ''}" $style="this.vertical ? 'min-height:0;' : ''">
|
||||
<div $class="\${this.vertical ? 'd-flex flex-column border-end h-100 align-self-stretch overflow-visible' : 'navbar navbar-expand border-bottom'} bg-body-secondary px-2 \${this.vertical ? 'py-3' : 'py-1'} align-items-center \${this.vertical ? 'align-items-start' : ''}" $style="this.vertical ? 'min-height:0;' : ''">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<template $if="this.state?.brand?.image">
|
||||
<img $src="this.state.brand.image" $class="\${this.vertical ? 'mb-2' : 'mx-1'}" style="height:30px;width:auto;max-width:300px">
|
||||
@ -1046,7 +1051,7 @@
|
||||
<div $class="\${this.vertical ? 'small text-uppercase text-body-secondary fw-semibold px-2 py-1 mt-2' : 'navbar-text small text-uppercase text-body-secondary fw-semibold px-2'} \${item.class || ''}" $text="item.label"></div>
|
||||
</template>
|
||||
<template $if="item.type==='button'">
|
||||
<button $class="nav-link \${Hash.nav===item.name?'active':''} \${this.vertical ? 'w-100 text-start py-1 px-2' : ''} \${item.class || ''}" $onclick="this.click(item)">
|
||||
<button $class="nav-link \${this.state.value===item.name?'active':''} \${this.vertical ? 'w-100 text-start py-1 px-2' : ''} \${item.class || ''}" $onclick="this.click(item)">
|
||||
<i $class="bi bi-\${item.icon} me-2"></i><span $class="\${this.vertical ? 'text-truncate' : 'd-none d-' + (this.state?.list?.length>5?'lg':'md') + '-inline'}" $text="item.label"></span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
2
dist/base.min.js
vendored
2
dist/base.min.js
vendored
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@apigo.cc/base",
|
||||
"version": "1.0.22",
|
||||
"version": "1.0.23",
|
||||
"type": "module",
|
||||
"main": "dist/base.js",
|
||||
"files": [
|
||||
|
||||
162
src/list.js
162
src/list.js
@ -17,16 +17,53 @@ globalThis.Component.register('List', container => {
|
||||
container.collapsed = globalThis.NewState({})
|
||||
container.state.renderedList = []
|
||||
|
||||
container.addEventListener('bind', e => {
|
||||
container.state.selectedItem = e.detail
|
||||
})
|
||||
|
||||
const matchString = (val, query) => {
|
||||
if (query === undefined || query === null || query === '') return true
|
||||
return String(val || '').toLowerCase().includes(String(query).toLowerCase())
|
||||
}
|
||||
|
||||
const matchesItem = (item, filter, filterFunc) => {
|
||||
if (filterFunc && !filterFunc(item)) return false
|
||||
if (filter === 0) filter = ''
|
||||
|
||||
if (filter !== undefined && filter !== null && filter !== '') {
|
||||
if (typeof filter === 'object') {
|
||||
for (const [key, qVal] of Object.entries(filter)) {
|
||||
if (qVal !== undefined && qVal !== null && qVal !== '') {
|
||||
if (!matchString(item[key], qVal)) return false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const q = String(filter).trim()
|
||||
if (q) {
|
||||
const labelMatches = matchString(item[container.labelfield], q)
|
||||
const summaryMatches = matchString(item[container.summaryfield], q)
|
||||
if (!labelMatches && !summaryMatches) return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const updateFlatList = () => {
|
||||
globalThis.Util.updateDefaults(container, defaultSets)
|
||||
let list = container.state.list || []
|
||||
const filter = container.state.filter
|
||||
const order = container.state.order
|
||||
let filter = container.state.filter
|
||||
let order = container.state.order
|
||||
const filterFunc = container._filterFunc
|
||||
const orderFunc = container._orderFunc
|
||||
|
||||
if (order) {
|
||||
if (typeof order === 'function') {
|
||||
list = [...list].sort(order)
|
||||
} else if (typeof order === 'string') {
|
||||
if (filter === 0) filter = ''
|
||||
if (order === 0) order = ''
|
||||
|
||||
if (orderFunc) {
|
||||
list = [...list].sort(orderFunc)
|
||||
} else if (order) {
|
||||
if (typeof order === 'string') {
|
||||
const parts = order.trim().split(/\s+/)
|
||||
const field = parts[0]
|
||||
const isDesc = parts[1]?.toLowerCase() === 'desc' || field.startsWith('-')
|
||||
@ -46,31 +83,33 @@ globalThis.Component.register('List', container => {
|
||||
|
||||
const flatList = []
|
||||
if (container.mode === 'group') {
|
||||
let filteredList = list
|
||||
if (filter) {
|
||||
if (typeof filter === 'function') {
|
||||
filteredList = list.filter(filter)
|
||||
} else if (typeof filter === 'string') {
|
||||
const q = filter.trim().toLowerCase()
|
||||
if (q) {
|
||||
filteredList = list.filter(item => {
|
||||
const label = String(item[container.labelfield] || '').toLowerCase()
|
||||
const summary = String(item[container.summaryfield] || '').toLowerCase()
|
||||
return label.includes(q) || summary.includes(q)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
const filteredList = list.filter(item => matchesItem(item, filter, filterFunc))
|
||||
const itemMap = {}
|
||||
filteredList.forEach(item => (itemMap[item[container.groupfield]] ??= []).push(item));
|
||||
(container.state.groups || []).forEach(group => {
|
||||
const items = itemMap[group[container.groupidfield]]
|
||||
const groupLabel = String(group[container.grouplabelfield] || '').toLowerCase()
|
||||
const groupSummary = String(group[container.groupsummaryfield] || '').toLowerCase()
|
||||
const q = (typeof filter === 'string') ? filter.trim().toLowerCase() : ''
|
||||
const groupMatches = q ? (groupLabel.includes(q) || groupSummary.includes(q)) : false
|
||||
let groupMatches = false
|
||||
if (filter !== undefined && filter !== null && filter !== '') {
|
||||
if (typeof filter === 'object') {
|
||||
groupMatches = true
|
||||
for (const [key, qVal] of Object.entries(filter)) {
|
||||
if (qVal !== undefined && qVal !== null && qVal !== '') {
|
||||
if (!matchString(group[key], qVal)) {
|
||||
groupMatches = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const q = String(filter).trim()
|
||||
if (q) {
|
||||
groupMatches = matchString(group[container.grouplabelfield], q) ||
|
||||
matchString(group[container.groupsummaryfield], q)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((items && items.length > 0) || groupMatches || !filter) {
|
||||
if ((items && items.length > 0) || groupMatches || (!filter && !filterFunc)) {
|
||||
flatList.push({ type: 'group', ...group })
|
||||
if (items) items.forEach(item => flatList.push({ type: 'item', ...item }))
|
||||
}
|
||||
@ -81,28 +120,13 @@ globalThis.Component.register('List', container => {
|
||||
|
||||
const matches = new Set()
|
||||
list.forEach(item => {
|
||||
let match = false
|
||||
if (!filter) {
|
||||
match = true
|
||||
} else if (typeof filter === 'function') {
|
||||
match = filter(item)
|
||||
} else if (typeof filter === 'string') {
|
||||
const q = filter.trim().toLowerCase()
|
||||
if (!q) {
|
||||
match = true
|
||||
} else {
|
||||
const label = String(item[container.labelfield] || '').toLowerCase()
|
||||
const summary = String(item[container.summaryfield] || '').toLowerCase()
|
||||
match = label.includes(q) || summary.includes(q)
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
if (matchesItem(item, filter, filterFunc)) {
|
||||
matches.add(item[container.idfield])
|
||||
}
|
||||
})
|
||||
|
||||
const keep = new Set(matches)
|
||||
if (filter) {
|
||||
if (filter || filterFunc) {
|
||||
matches.forEach(id => {
|
||||
let curr = itemMap[id]
|
||||
while (curr) {
|
||||
@ -117,7 +141,7 @@ globalThis.Component.register('List', container => {
|
||||
|
||||
const childrenMap = {}
|
||||
list.forEach(item => {
|
||||
if (!filter || keep.has(item[container.idfield])) {
|
||||
if ((!filter && !filterFunc) || keep.has(item[container.idfield])) {
|
||||
const parentId = item[container.parentfield] || ''
|
||||
; (childrenMap[parentId] ??= []).push(item)
|
||||
}
|
||||
@ -132,21 +156,7 @@ globalThis.Component.register('List', container => {
|
||||
})
|
||||
traverse(childrenMap[''] || [], 0, [])
|
||||
} else {
|
||||
let filteredList = list
|
||||
if (filter) {
|
||||
if (typeof filter === 'function') {
|
||||
filteredList = list.filter(filter)
|
||||
} else if (typeof filter === 'string') {
|
||||
const q = filter.trim().toLowerCase()
|
||||
if (q) {
|
||||
filteredList = list.filter(item => {
|
||||
const label = String(item[container.labelfield] || '').toLowerCase()
|
||||
const summary = String(item[container.summaryfield] || '').toLowerCase()
|
||||
return label.includes(q) || summary.includes(q)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
const filteredList = list.filter(item => matchesItem(item, filter, filterFunc))
|
||||
filteredList.forEach(item => flatList.push({ type: 'item', ...item }))
|
||||
}
|
||||
container.state.flatList = flatList
|
||||
@ -187,7 +197,10 @@ globalThis.Component.register('List', container => {
|
||||
})
|
||||
|
||||
container.selectItem = (item, index) => {
|
||||
if (container.hasAttribute('auto-select')) container.state.selectedItem = container.state.selectedItem === item[container.idfield] ? null : item[container.idfield]
|
||||
if (container.hasAttribute('auto-select')) {
|
||||
container.state.selectedItem = container.state.selectedItem === item[container.idfield] ? null : item[container.idfield]
|
||||
}
|
||||
container.dispatchEvent(new CustomEvent('change', { detail: container.state.selectedItem, bubbles: false }))
|
||||
container.dispatchEvent(new CustomEvent('itemclick', { bubbles: false, detail: { item, index: index + (container.fast ? (container.state.listStartIndex || 0) : 0) } }))
|
||||
}
|
||||
container.selectGroup = (item, index) => {
|
||||
@ -196,30 +209,15 @@ globalThis.Component.register('List', container => {
|
||||
}
|
||||
container.toggleCollapse = (item) => { if (container.collapsible && item._hasChildren) { container.collapsed[item[container.idfield]] = !container.collapsed[item[container.idfield]]; updateFlatList(); } }
|
||||
|
||||
Object.defineProperty(container, 'filter', {
|
||||
get: () => container.state.filter,
|
||||
set: v => { container.state.filter = v; }
|
||||
Object.defineProperty(container, 'filterFunc', {
|
||||
get: () => container._filterFunc,
|
||||
set: v => { container._filterFunc = v; updateFlatList(); }
|
||||
})
|
||||
Object.defineProperty(container, 'order', {
|
||||
get: () => container.state.order,
|
||||
set: v => { container.state.order = v; }
|
||||
Object.defineProperty(container, 'orderFunc', {
|
||||
get: () => container._orderFunc,
|
||||
set: v => { container._orderFunc = v; updateFlatList(); }
|
||||
})
|
||||
|
||||
const observer = new MutationObserver(() => {
|
||||
const filterAttr = container.getAttribute('filter')
|
||||
if (typeof container.state.filter !== 'function' && filterAttr !== container.state.filter) {
|
||||
container.state.filter = filterAttr || undefined
|
||||
}
|
||||
const orderAttr = container.getAttribute('order')
|
||||
if (typeof container.state.order !== 'function' && orderAttr !== container.state.order) {
|
||||
container.state.order = orderAttr || undefined
|
||||
}
|
||||
})
|
||||
observer.observe(container, { attributes: true, attributeFilter: ['filter', 'order'] })
|
||||
|
||||
container.state.filter = container.getAttribute('filter') || undefined
|
||||
container.state.order = container.getAttribute('order') || undefined
|
||||
|
||||
updateFlatList()
|
||||
}, globalThis.Util.makeDom(/*html*/`
|
||||
<div class="list-group overflow-auto" onscroll="this.refresh()" style="overflow-anchor:none">
|
||||
@ -249,7 +247,7 @@ globalThis.Component.register('List', container => {
|
||||
<span $class="bi bi-\${this.itemicon} text-body"></span>
|
||||
</template>
|
||||
<div class="flex-shrink-0 px-1" $text="\${item[this.labelfield]}"></div>
|
||||
<div class="text-muted small flex-fill text-end" $text="\${item[this.summaryfield]}"></div>
|
||||
<div class="text-muted fs-7 small ms-2 flex-fill text-truncate text-end" $text="\${item[this.summaryfield]}"></div>
|
||||
<div slot-id="item-actions"></div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
17
src/nav.js
17
src/nav.js
@ -4,11 +4,22 @@
|
||||
globalThis.Component.register('Nav', container => {
|
||||
container.vertical = container.hasAttribute('vertical')
|
||||
container.state.openName = container.state.openName || null
|
||||
container.state.value = container.state.value || null
|
||||
|
||||
container.addEventListener('bind', e => {
|
||||
container.state.value = e.detail
|
||||
})
|
||||
|
||||
container.click = (item, noselect) => {
|
||||
if (!item.noselect && !noselect) globalThis.Hash.nav = item.name
|
||||
if (!item.noselect && !noselect) {
|
||||
container.state.value = item.name
|
||||
container.dispatchEvent(new CustomEvent('change', { detail: item.name, bubbles: false }))
|
||||
}
|
||||
container.dispatchEvent(new CustomEvent('nav', { detail: { item }, bubbles: false }))
|
||||
}
|
||||
container.clickSubitem = item => {
|
||||
container.state.value = item.name
|
||||
container.dispatchEvent(new CustomEvent('change', { detail: item.name, bubbles: false }))
|
||||
container.dispatchEvent(new CustomEvent('nav', { detail: { item }, bubbles: false }))
|
||||
}
|
||||
container.toggleGroup = item => {
|
||||
@ -16,7 +27,7 @@ globalThis.Component.register('Nav', container => {
|
||||
container.dispatchEvent(new CustomEvent('nav', { detail: { item, open: container.state.openName === item.name }, bubbles: false }))
|
||||
}
|
||||
}, globalThis.Util.makeDom(/*html*/`
|
||||
<div $class="\${this.vertical ? 'd-flex flex-column border-end h-100 align-self-stretch overflow-visible' : 'navbar navbar-expand border-bottom'} bg-body-secondary px-2 \${this.vertical ? 'py-3' : 'py-0'} align-items-center \${this.vertical ? 'align-items-start' : ''}" $style="this.vertical ? 'min-height:0;' : ''">
|
||||
<div $class="\${this.vertical ? 'd-flex flex-column border-end h-100 align-self-stretch overflow-visible' : 'navbar navbar-expand border-bottom'} bg-body-secondary px-2 \${this.vertical ? 'py-3' : 'py-1'} align-items-center \${this.vertical ? 'align-items-start' : ''}" $style="this.vertical ? 'min-height:0;' : ''">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<template $if="this.state?.brand?.image">
|
||||
<img $src="this.state.brand.image" $class="\${this.vertical ? 'mb-2' : 'mx-1'}" style="height:30px;width:auto;max-width:300px">
|
||||
@ -35,7 +46,7 @@ globalThis.Component.register('Nav', container => {
|
||||
<div $class="\${this.vertical ? 'small text-uppercase text-body-secondary fw-semibold px-2 py-1 mt-2' : 'navbar-text small text-uppercase text-body-secondary fw-semibold px-2'} \${item.class || ''}" $text="item.label"></div>
|
||||
</template>
|
||||
<template $if="item.type==='button'">
|
||||
<button $class="nav-link \${Hash.nav===item.name?'active':''} \${this.vertical ? 'w-100 text-start py-1 px-2' : ''} \${item.class || ''}" $onclick="this.click(item)">
|
||||
<button $class="nav-link \${this.state.value===item.name?'active':''} \${this.vertical ? 'w-100 text-start py-1 px-2' : ''} \${item.class || ''}" $onclick="this.click(item)">
|
||||
<i $class="bi bi-\${item.icon} me-2"></i><span $class="\${this.vertical ? 'text-truncate' : 'd-none d-' + (this.state?.list?.length>5?'lg':'md') + '-inline'}" $text="item.label"></span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
171
test/lib/base.js
171
test/lib/base.js
@ -761,16 +761,48 @@
|
||||
};
|
||||
container.collapsed = globalThis.NewState({});
|
||||
container.state.renderedList = [];
|
||||
container.addEventListener("bind", (e) => {
|
||||
container.state.selectedItem = e.detail;
|
||||
});
|
||||
const matchString = (val, query) => {
|
||||
if (query === void 0 || query === null || query === "") return true;
|
||||
return String(val || "").toLowerCase().includes(String(query).toLowerCase());
|
||||
};
|
||||
const matchesItem = (item, filter, filterFunc) => {
|
||||
if (filterFunc && !filterFunc(item)) return false;
|
||||
if (filter === 0) filter = "";
|
||||
if (filter !== void 0 && filter !== null && filter !== "") {
|
||||
if (typeof filter === "object") {
|
||||
for (const [key, qVal] of Object.entries(filter)) {
|
||||
if (qVal !== void 0 && qVal !== null && qVal !== "") {
|
||||
if (!matchString(item[key], qVal)) return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const q = String(filter).trim();
|
||||
if (q) {
|
||||
const labelMatches = matchString(item[container.labelfield], q);
|
||||
const summaryMatches = matchString(item[container.summaryfield], q);
|
||||
if (!labelMatches && !summaryMatches) return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const updateFlatList = () => {
|
||||
var _a;
|
||||
globalThis.Util.updateDefaults(container, defaultSets);
|
||||
let list = container.state.list || [];
|
||||
const filter = container.state.filter;
|
||||
const order = container.state.order;
|
||||
if (order) {
|
||||
if (typeof order === "function") {
|
||||
list = [...list].sort(order);
|
||||
} else if (typeof order === "string") {
|
||||
let filter = container.state.filter;
|
||||
let order = container.state.order;
|
||||
const filterFunc = container._filterFunc;
|
||||
const orderFunc = container._orderFunc;
|
||||
if (filter === 0) filter = "";
|
||||
if (order === 0) order = "";
|
||||
if (orderFunc) {
|
||||
list = [...list].sort(orderFunc);
|
||||
} else if (order) {
|
||||
if (typeof order === "string") {
|
||||
const parts = order.trim().split(/\s+/);
|
||||
const field = parts[0];
|
||||
const isDesc = ((_a = parts[1]) == null ? void 0 : _a.toLowerCase()) === "desc" || field.startsWith("-");
|
||||
@ -789,21 +821,7 @@
|
||||
}
|
||||
const flatList = [];
|
||||
if (container.mode === "group") {
|
||||
let filteredList = list;
|
||||
if (filter) {
|
||||
if (typeof filter === "function") {
|
||||
filteredList = list.filter(filter);
|
||||
} else if (typeof filter === "string") {
|
||||
const q = filter.trim().toLowerCase();
|
||||
if (q) {
|
||||
filteredList = list.filter((item) => {
|
||||
const label = String(item[container.labelfield] || "").toLowerCase();
|
||||
const summary = String(item[container.summaryfield] || "").toLowerCase();
|
||||
return label.includes(q) || summary.includes(q);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
const filteredList = list.filter((item) => matchesItem(item, filter, filterFunc));
|
||||
const itemMap = {};
|
||||
filteredList.forEach((item) => {
|
||||
var _a2;
|
||||
@ -811,11 +829,26 @@
|
||||
});
|
||||
(container.state.groups || []).forEach((group) => {
|
||||
const items = itemMap[group[container.groupidfield]];
|
||||
const groupLabel = String(group[container.grouplabelfield] || "").toLowerCase();
|
||||
const groupSummary = String(group[container.groupsummaryfield] || "").toLowerCase();
|
||||
const q = typeof filter === "string" ? filter.trim().toLowerCase() : "";
|
||||
const groupMatches = q ? groupLabel.includes(q) || groupSummary.includes(q) : false;
|
||||
if (items && items.length > 0 || groupMatches || !filter) {
|
||||
let groupMatches = false;
|
||||
if (filter !== void 0 && filter !== null && filter !== "") {
|
||||
if (typeof filter === "object") {
|
||||
groupMatches = true;
|
||||
for (const [key, qVal] of Object.entries(filter)) {
|
||||
if (qVal !== void 0 && qVal !== null && qVal !== "") {
|
||||
if (!matchString(group[key], qVal)) {
|
||||
groupMatches = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const q = String(filter).trim();
|
||||
if (q) {
|
||||
groupMatches = matchString(group[container.grouplabelfield], q) || matchString(group[container.groupsummaryfield], q);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (items && items.length > 0 || groupMatches || !filter && !filterFunc) {
|
||||
flatList.push({ type: "group", ...group });
|
||||
if (items) items.forEach((item) => flatList.push({ type: "item", ...item }));
|
||||
}
|
||||
@ -827,27 +860,12 @@
|
||||
});
|
||||
const matches = /* @__PURE__ */ new Set();
|
||||
list.forEach((item) => {
|
||||
let match = false;
|
||||
if (!filter) {
|
||||
match = true;
|
||||
} else if (typeof filter === "function") {
|
||||
match = filter(item);
|
||||
} else if (typeof filter === "string") {
|
||||
const q = filter.trim().toLowerCase();
|
||||
if (!q) {
|
||||
match = true;
|
||||
} else {
|
||||
const label = String(item[container.labelfield] || "").toLowerCase();
|
||||
const summary = String(item[container.summaryfield] || "").toLowerCase();
|
||||
match = label.includes(q) || summary.includes(q);
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
if (matchesItem(item, filter, filterFunc)) {
|
||||
matches.add(item[container.idfield]);
|
||||
}
|
||||
});
|
||||
const keep = new Set(matches);
|
||||
if (filter) {
|
||||
if (filter || filterFunc) {
|
||||
matches.forEach((id) => {
|
||||
let curr = itemMap[id];
|
||||
while (curr) {
|
||||
@ -861,7 +879,7 @@
|
||||
}
|
||||
const childrenMap = {};
|
||||
list.forEach((item) => {
|
||||
if (!filter || keep.has(item[container.idfield])) {
|
||||
if (!filter && !filterFunc || keep.has(item[container.idfield])) {
|
||||
const parentId = item[container.parentfield] || "";
|
||||
(childrenMap[parentId] ?? (childrenMap[parentId] = [])).push(item);
|
||||
}
|
||||
@ -876,21 +894,7 @@
|
||||
});
|
||||
traverse(childrenMap[""] || [], 0, []);
|
||||
} else {
|
||||
let filteredList = list;
|
||||
if (filter) {
|
||||
if (typeof filter === "function") {
|
||||
filteredList = list.filter(filter);
|
||||
} else if (typeof filter === "string") {
|
||||
const q = filter.trim().toLowerCase();
|
||||
if (q) {
|
||||
filteredList = list.filter((item) => {
|
||||
const label = String(item[container.labelfield] || "").toLowerCase();
|
||||
const summary = String(item[container.summaryfield] || "").toLowerCase();
|
||||
return label.includes(q) || summary.includes(q);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
const filteredList = list.filter((item) => matchesItem(item, filter, filterFunc));
|
||||
filteredList.forEach((item) => flatList.push({ type: "item", ...item }));
|
||||
}
|
||||
container.state.flatList = flatList;
|
||||
@ -932,7 +936,10 @@
|
||||
} else container.state.renderedList = flatList;
|
||||
});
|
||||
container.selectItem = (item, index2) => {
|
||||
if (container.hasAttribute("auto-select")) container.state.selectedItem = container.state.selectedItem === item[container.idfield] ? null : item[container.idfield];
|
||||
if (container.hasAttribute("auto-select")) {
|
||||
container.state.selectedItem = container.state.selectedItem === item[container.idfield] ? null : item[container.idfield];
|
||||
}
|
||||
container.dispatchEvent(new CustomEvent("change", { detail: container.state.selectedItem, bubbles: false }));
|
||||
container.dispatchEvent(new CustomEvent("itemclick", { bubbles: false, detail: { item, index: index2 + (container.fast ? container.state.listStartIndex || 0 : 0) } }));
|
||||
};
|
||||
container.selectGroup = (item, index2) => {
|
||||
@ -945,31 +952,20 @@
|
||||
updateFlatList();
|
||||
}
|
||||
};
|
||||
Object.defineProperty(container, "filter", {
|
||||
get: () => container.state.filter,
|
||||
Object.defineProperty(container, "filterFunc", {
|
||||
get: () => container._filterFunc,
|
||||
set: (v) => {
|
||||
container.state.filter = v;
|
||||
container._filterFunc = v;
|
||||
updateFlatList();
|
||||
}
|
||||
});
|
||||
Object.defineProperty(container, "order", {
|
||||
get: () => container.state.order,
|
||||
Object.defineProperty(container, "orderFunc", {
|
||||
get: () => container._orderFunc,
|
||||
set: (v) => {
|
||||
container.state.order = v;
|
||||
container._orderFunc = v;
|
||||
updateFlatList();
|
||||
}
|
||||
});
|
||||
const observer = new MutationObserver(() => {
|
||||
const filterAttr = container.getAttribute("filter");
|
||||
if (typeof container.state.filter !== "function" && filterAttr !== container.state.filter) {
|
||||
container.state.filter = filterAttr || void 0;
|
||||
}
|
||||
const orderAttr = container.getAttribute("order");
|
||||
if (typeof container.state.order !== "function" && orderAttr !== container.state.order) {
|
||||
container.state.order = orderAttr || void 0;
|
||||
}
|
||||
});
|
||||
observer.observe(container, { attributes: true, attributeFilter: ["filter", "order"] });
|
||||
container.state.filter = container.getAttribute("filter") || void 0;
|
||||
container.state.order = container.getAttribute("order") || void 0;
|
||||
updateFlatList();
|
||||
}, globalThis.Util.makeDom(
|
||||
/*html*/
|
||||
@ -1001,7 +997,7 @@
|
||||
<span $class="bi bi-\${this.itemicon} text-body"></span>
|
||||
</template>
|
||||
<div class="flex-shrink-0 px-1" $text="\${item[this.labelfield]}"></div>
|
||||
<div class="text-muted small flex-fill text-end" $text="\${item[this.summaryfield]}"></div>
|
||||
<div class="text-muted fs-7 small ms-2 flex-fill text-truncate text-end" $text="\${item[this.summaryfield]}"></div>
|
||||
<div slot-id="item-actions"></div>
|
||||
</template>
|
||||
</div>
|
||||
@ -1013,11 +1009,20 @@
|
||||
globalThis.Component.register("Nav", (container) => {
|
||||
container.vertical = container.hasAttribute("vertical");
|
||||
container.state.openName = container.state.openName || null;
|
||||
container.state.value = container.state.value || null;
|
||||
container.addEventListener("bind", (e) => {
|
||||
container.state.value = e.detail;
|
||||
});
|
||||
container.click = (item, noselect) => {
|
||||
if (!item.noselect && !noselect) globalThis.Hash.nav = item.name;
|
||||
if (!item.noselect && !noselect) {
|
||||
container.state.value = item.name;
|
||||
container.dispatchEvent(new CustomEvent("change", { detail: item.name, bubbles: false }));
|
||||
}
|
||||
container.dispatchEvent(new CustomEvent("nav", { detail: { item }, bubbles: false }));
|
||||
};
|
||||
container.clickSubitem = (item) => {
|
||||
container.state.value = item.name;
|
||||
container.dispatchEvent(new CustomEvent("change", { detail: item.name, bubbles: false }));
|
||||
container.dispatchEvent(new CustomEvent("nav", { detail: { item }, bubbles: false }));
|
||||
};
|
||||
container.toggleGroup = (item) => {
|
||||
@ -1027,7 +1032,7 @@
|
||||
}, globalThis.Util.makeDom(
|
||||
/*html*/
|
||||
`
|
||||
<div $class="\${this.vertical ? 'd-flex flex-column border-end h-100 align-self-stretch overflow-visible' : 'navbar navbar-expand border-bottom'} bg-body-secondary px-2 \${this.vertical ? 'py-3' : 'py-0'} align-items-center \${this.vertical ? 'align-items-start' : ''}" $style="this.vertical ? 'min-height:0;' : ''">
|
||||
<div $class="\${this.vertical ? 'd-flex flex-column border-end h-100 align-self-stretch overflow-visible' : 'navbar navbar-expand border-bottom'} bg-body-secondary px-2 \${this.vertical ? 'py-3' : 'py-1'} align-items-center \${this.vertical ? 'align-items-start' : ''}" $style="this.vertical ? 'min-height:0;' : ''">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<template $if="this.state?.brand?.image">
|
||||
<img $src="this.state.brand.image" $class="\${this.vertical ? 'mb-2' : 'mx-1'}" style="height:30px;width:auto;max-width:300px">
|
||||
@ -1046,7 +1051,7 @@
|
||||
<div $class="\${this.vertical ? 'small text-uppercase text-body-secondary fw-semibold px-2 py-1 mt-2' : 'navbar-text small text-uppercase text-body-secondary fw-semibold px-2'} \${item.class || ''}" $text="item.label"></div>
|
||||
</template>
|
||||
<template $if="item.type==='button'">
|
||||
<button $class="nav-link \${Hash.nav===item.name?'active':''} \${this.vertical ? 'w-100 text-start py-1 px-2' : ''} \${item.class || ''}" $onclick="this.click(item)">
|
||||
<button $class="nav-link \${this.state.value===item.name?'active':''} \${this.vertical ? 'w-100 text-start py-1 px-2' : ''} \${item.class || ''}" $onclick="this.click(item)">
|
||||
<i $class="bi bi-\${item.icon} me-2"></i><span $class="\${this.vertical ? 'text-truncate' : 'd-none d-' + (this.state?.list?.length>5?'lg':'md') + '-inline'}" $text="item.label"></span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
@ -222,12 +222,26 @@
|
||||
if (attr.name === "style") {
|
||||
if (to.hasAttribute("style")) to.setAttribute("style", `${attr.value}; ${to.getAttribute("style")}`);
|
||||
else to.setAttribute("style", attr.value);
|
||||
} else if (!to.hasAttribute(attr.name)) {
|
||||
} else if (to.hasAttribute(attr.name)) {
|
||||
const isClass = ["$class", "st-class"].includes(attr.name);
|
||||
const isStyle = ["$style", "st-style"].includes(attr.name);
|
||||
if (isClass || isStyle) {
|
||||
const oldVal = to.getAttribute(attr.name);
|
||||
const newVal = attr.value;
|
||||
const delimiter = isClass ? " " : "; ";
|
||||
const oldExpr = oldVal.includes("${") ? oldVal : `\${${oldVal}}`;
|
||||
const newExpr = newVal.includes("${") ? newVal : `\${${newVal}}`;
|
||||
to.setAttribute(attr.name, `${newExpr}${delimiter}${oldExpr}`);
|
||||
}
|
||||
} else {
|
||||
to.setAttribute(attr.name, attr.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
const toClassList = [...to.classList];
|
||||
to.className = "";
|
||||
to.classList.add(...from.classList);
|
||||
to.classList.add(...toClassList);
|
||||
const target = to.tagName === "TEMPLATE" ? to.content : to;
|
||||
const sourceNodes = from.tagName === "TEMPLATE" ? from.content.childNodes : from.childNodes;
|
||||
Array.from(sourceNodes).forEach((child) => target.appendChild(child));
|
||||
@ -318,6 +332,7 @@
|
||||
}
|
||||
}
|
||||
_setActiveBinding(null);
|
||||
binding.lastResult = result;
|
||||
if (binding.prop) {
|
||||
const prop = binding.prop;
|
||||
let o = node;
|
||||
@ -441,7 +456,21 @@
|
||||
if (attr === "text") node.textContent = result ?? "";
|
||||
else if (attr === "html") node.innerHTML = result ?? "";
|
||||
else if (node.tagName === "IMG" && attr === "src" && result.includes(".svg")) node.setAttribute("_src", result ?? "");
|
||||
else node.setAttribute(attr, result ?? "");
|
||||
else if (attr === "class") {
|
||||
if (node._staticClasses === void 0) {
|
||||
node._staticClasses = node.getAttribute("class") || "";
|
||||
}
|
||||
const staticClasses = node._staticClasses;
|
||||
const finalClass = result ? staticClasses ? `${result} ${staticClasses}` : result : staticClasses;
|
||||
node.setAttribute("class", finalClass.trim().replace(/\s+/g, " "));
|
||||
} else if (attr === "style") {
|
||||
if (node._staticStyles === void 0) {
|
||||
node._staticStyles = node.getAttribute("style") || "";
|
||||
}
|
||||
const staticStyles = node._staticStyles;
|
||||
const finalStyle = result ? staticStyles ? `${result}; ${staticStyles}` : result : staticStyles;
|
||||
node.setAttribute("style", finalStyle.trim().replace(/;;+/g, ";").replace(/^;+\s*|;\s*$/g, ""));
|
||||
} else node.setAttribute(attr, result ?? "");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -472,7 +501,6 @@
|
||||
if (prop[i]) o = o[_a2 = prop[i]] ?? (o[_a2] = {});
|
||||
}
|
||||
o[prop[prop.length - 1]] = result;
|
||||
node.removeAttribute(attr.name);
|
||||
}
|
||||
});
|
||||
_makeComponent(node.tagName, node, scanObj);
|
||||
@ -486,9 +514,9 @@
|
||||
}
|
||||
let attrs = [];
|
||||
if (node.tagName === "TEMPLATE") {
|
||||
["$if", "$each", "st-if", "st-each"].forEach((n) => node.hasAttribute(n) && attrs.push(node.getAttributeNode(n)));
|
||||
["$if", "$each", "st-if", "st-each", "$$if", "$$each", "st-st-if", "st-st-each"].forEach((n) => node.hasAttribute(n) && attrs.push(node.getAttributeNode(n)));
|
||||
} else {
|
||||
attrs = Array.from(node.attributes).filter((a) => (a.name.startsWith("$") || a.name.startsWith("st-")) && !["$if", "$each", "st-if", "st-each"].includes(a.name) || a.name.includes("."));
|
||||
attrs = Array.from(node.attributes).filter((a) => (a.name.startsWith("$") || a.name.startsWith("st-")) && !["$if", "$each", "st-if", "st-each", "$$if", "$$each", "st-st-if", "st-st-each"].includes(a.name) || a.name.includes("."));
|
||||
}
|
||||
if (node._thisObj && scanObj.thisObj && node._thisObj !== scanObj.thisObj) node._thisObj.parent = scanObj.thisObj;
|
||||
if (!node._thisObj) node._thisObj = scanObj.thisObj || null;
|
||||
@ -552,9 +580,9 @@
|
||||
});
|
||||
node._stTranslated = true;
|
||||
}
|
||||
if (node.tagName !== "TEMPLATE" && (node.hasAttribute("$if") || node.hasAttribute("$each") || node.hasAttribute("st-if") || node.hasAttribute("st-each"))) {
|
||||
if (node.tagName !== "TEMPLATE" && (node.hasAttribute("$if") || node.hasAttribute("$each") || node.hasAttribute("st-if") || node.hasAttribute("st-each") || node.hasAttribute("$$if") || node.hasAttribute("$$each") || node.hasAttribute("st-st-if") || node.hasAttribute("st-st-each"))) {
|
||||
const template = document.createElement("TEMPLATE");
|
||||
const attrs = Array.from(node.attributes).filter((attr) => ["$if", "$each", "st-if", "st-each"].includes(attr.name) || (node.hasAttribute("$each") || node.hasAttribute("st-each")) && ["as", "index"].includes(attr.name));
|
||||
const attrs = Array.from(node.attributes).filter((attr) => ["$if", "$each", "st-if", "st-each", "$$if", "$$each", "st-st-if", "st-st-each"].includes(attr.name) || (node.hasAttribute("$each") || node.hasAttribute("st-each") || node.hasAttribute("$$each") || node.hasAttribute("st-st-each")) && ["as", "index"].includes(attr.name));
|
||||
attrs.forEach((attr) => {
|
||||
template.setAttribute(attr.name, attr.value);
|
||||
node.removeAttribute(attr.name);
|
||||
@ -565,13 +593,13 @@
|
||||
_scanTree(template, scanObj);
|
||||
return;
|
||||
}
|
||||
if (node.tagName === "TEMPLATE" && (node.hasAttribute("$if") || node.hasAttribute("st-if")) && (node.hasAttribute("$each") || node.hasAttribute("st-each"))) {
|
||||
if (node.tagName === "TEMPLATE" && (node.hasAttribute("$if") || node.hasAttribute("st-if") || node.hasAttribute("$$if") || node.hasAttribute("st-st-if")) && (node.hasAttribute("$each") || node.hasAttribute("st-each") || node.hasAttribute("$$each") || node.hasAttribute("st-st-each"))) {
|
||||
const template = document.createElement("TEMPLATE");
|
||||
const attrs = Array.from(node.attributes).filter((attr2) => ["$if", "$each", "st-if", "st-each"].includes(attr2.name));
|
||||
const attrs = Array.from(node.attributes).filter((attr2) => ["$if", "$each", "st-if", "st-each", "$$if", "$$each", "st-st-if", "st-st-each"].includes(attr2.name));
|
||||
const attr = attrs[attrs.length - 1];
|
||||
template.setAttribute(attr.name, attr.value);
|
||||
node.removeAttribute(attr.name);
|
||||
if (attr.name === "$each" || attr.name === "st-each") {
|
||||
if (["$each", "st-each", "$$each", "st-st-each"].includes(attr.name)) {
|
||||
Array.from(node.attributes).filter((attr2) => ["as", "index"].includes(attr2.name)).forEach((attr2) => {
|
||||
template.setAttribute(attr2.name, attr2.value);
|
||||
node.removeAttribute(attr2.name);
|
||||
@ -658,5 +686,9 @@
|
||||
exports2.State = State;
|
||||
exports2.Util = Util;
|
||||
exports2.__unsafeRefreshState = __unsafeRefreshState;
|
||||
exports2._returnCode = _returnCode;
|
||||
exports2._runCode = _runCode;
|
||||
exports2.onNotifyUpdate = _onNotifyUpdate;
|
||||
exports2.setActiveBinding = _setActiveBinding;
|
||||
Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
|
||||
});
|
||||
|
||||
@ -38,6 +38,7 @@ test('List fixtures cover normal, grouped, tree and virtual modes', async ({ pag
|
||||
});
|
||||
|
||||
test('List component supports internal filtering and sorting', async ({ page }) => {
|
||||
page.on('console', msg => console.log('BROWSER CONSOLE:', msg.text()));
|
||||
await page.goto('/test/list_test.html');
|
||||
|
||||
// 1. Test Standard List Filtering
|
||||
|
||||
@ -16,32 +16,32 @@
|
||||
<body>
|
||||
<div class="test-container">
|
||||
<div class="list-card">
|
||||
<h5 class="d-flex align-items-center justify-content-between">
|
||||
<List id="listStd" class="flex-fill" $.state.list="State.stdItems" auto-select></List>
|
||||
<h5 class="d-flex align-items-center justify-content-between order-first">
|
||||
<span>1. Standard List (Normal)</span>
|
||||
<div class="d-flex gap-2">
|
||||
<input type="text" id="searchInput" class="form-control form-control-sm" style="width: 120px;" placeholder="Search..." $bind="State.stdFilter">
|
||||
<select id="sortSelect" class="form-select form-select-sm" style="width: 100px;" $bind="State.stdOrder">
|
||||
<input type="text" id="searchInput" class="form-control form-control-sm" style="width: 120px;" placeholder="Search..." $bind="listStd.state.filter">
|
||||
<select id="sortSelect" class="form-select form-select-sm" style="width: 100px;" $bind="listStd.state.order">
|
||||
<option value="">No Sort</option>
|
||||
<option value="label">Label Asc</option>
|
||||
<option value="-label">Label Desc</option>
|
||||
</select>
|
||||
</div>
|
||||
</h5>
|
||||
<List id="listStd" class="flex-fill" $.state.list="State.stdItems" $filter="State.stdFilter || ''" $order="State.stdOrder" auto-select></List>
|
||||
</div>
|
||||
<div class="list-card">
|
||||
<h5 class="d-flex align-items-center justify-content-between">
|
||||
<List id="listGrp" mode="group" class="flex-fill" $.state.list="State.grpItems" $.state.groups="State.groups"></List>
|
||||
<h5 class="d-flex align-items-center justify-content-between order-first">
|
||||
<span>2. Group List (Mode: Group)</span>
|
||||
<input type="text" id="grpSearchInput" class="form-control form-control-sm" style="width: 120px;" placeholder="Search..." $bind="State.grpFilter">
|
||||
<input type="text" id="grpSearchInput" class="form-control form-control-sm" style="width: 120px;" placeholder="Search..." $bind="listGrp.state.filter">
|
||||
</h5>
|
||||
<List id="listGrp" mode="group" class="flex-fill" $.state.list="State.grpItems" $.state.groups="State.groups" $filter="State.grpFilter || ''"></List>
|
||||
</div>
|
||||
<div class="list-card">
|
||||
<h5 class="d-flex align-items-center justify-content-between">
|
||||
<List id="listTree" mode="tree" collapsible class="flex-fill" $.state.list="State.treeItems"></List>
|
||||
<h5 class="d-flex align-items-center justify-content-between order-first">
|
||||
<span>3. Tree List (Mode: Tree + Collapsible)</span>
|
||||
<input type="text" id="treeSearchInput" class="form-control form-control-sm" style="width: 120px;" placeholder="Search..." $bind="State.treeFilter">
|
||||
<input type="text" id="treeSearchInput" class="form-control form-control-sm" style="width: 120px;" placeholder="Search..." $bind="listTree.state.filter">
|
||||
</h5>
|
||||
<List id="listTree" mode="tree" collapsible class="flex-fill" $.state.list="State.treeItems" $filter="State.treeFilter || ''"></List>
|
||||
</div>
|
||||
<div class="list-card">
|
||||
<h5>4. FAST Virtual List (10,000 Items + Dynamic Height)</h5>
|
||||
@ -105,7 +105,6 @@
|
||||
summary: lorem[i % 4]
|
||||
});
|
||||
}
|
||||
|
||||
State.stdItems = stdItems;
|
||||
State.groups = groups;
|
||||
State.grpItems = grpItems;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user