fix(base): 修复导航与API预设请求(by AI)
This commit is contained in:
parent
754720685f
commit
0b8e1b2952
@ -1,5 +1,10 @@
|
||||
# Changelog
|
||||
|
||||
## [1.0.21] - 2026-06-27
|
||||
### Fixed
|
||||
- **API**: Preserved declarative preset fields injected through `$.request.*` when the `API` component initializes its internal reactive request state.
|
||||
- **Nav**: Restored horizontal dropdown behavior by removing container-level clipping, added `label`/`class` support for menu grouping, and switched vertical mode dropdown groups to inline expansion so long sidebars can scroll without popup clipping.
|
||||
|
||||
## [1.0.18] - 2026-06-11
|
||||
### Changed
|
||||
- **Component-Based Refactor**: Reorganized source code into independent, PascalCase named modules (e.g., `AutoForm.js`, `TagsInput.js`, `List.js`) to improve maintainability.
|
||||
|
||||
11
README.md
11
README.md
@ -24,9 +24,9 @@
|
||||
<!-- 1. 基础状态机 (地基) -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/state@1.0.18/dist/state.min.js"></script>
|
||||
<!-- 2. Bootstrap 适配层 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/bootstrap@1.0.7/dist/bootstrap.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/bootstrap@1.0.9/dist/bootstrap.min.js"></script>
|
||||
<!-- 3. 本业务组件库 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/base@1.0.18/dist/base.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/base@1.0.21/dist/base.min.js"></script>
|
||||
```
|
||||
<script>
|
||||
// 4. 数据先行 (在 body 解析前定义)
|
||||
@ -215,6 +215,7 @@ const { HTTP, UI, AutoForm, State } = window.ApigoBase
|
||||
// 1. 预先确定品牌与菜单数据
|
||||
const myBrand = { icon: 'shield-lock', label: '安全控制台' };
|
||||
const myNavList = [
|
||||
{ type: 'label', label: '主导航', class: 'mt-1' },
|
||||
{ type: 'button', name: 'dashboard', label: '仪表盘', icon: 'speedometer' },
|
||||
{
|
||||
type: 'dropdown',
|
||||
@ -222,7 +223,8 @@ const { HTTP, UI, AutoForm, State } = window.ApigoBase
|
||||
label: '系统设置',
|
||||
icon: 'gear',
|
||||
list: [
|
||||
{ type: 'button', name: 'profile', label: '个人信息', icon: 'person' },
|
||||
{ type: 'label', label: '偏好设置' },
|
||||
{ type: 'button', name: 'profile', label: '个人信息', icon: 'person', class: 'ps-2' },
|
||||
{ type: 'switch', name: 'darkMode', label: '暗黑模式', icon: 'moon', bind: LocalStorage, name: 'darkMode' }
|
||||
]
|
||||
},
|
||||
@ -241,6 +243,9 @@ const { HTTP, UI, AutoForm, State } = window.ApigoBase
|
||||
```
|
||||
* **AI 核心要点**:
|
||||
* 常规导航项被点击时(且 `noselect` 不为 true),会自动更新全局 `Hash.nav = item.name`。
|
||||
* `label` 可在一级或二级菜单中作为分组标题使用,`class` 可附加 Bootstrap 工具类(如 `ps-2`)做缩进或样式微调。
|
||||
* `dropdown` 子项默认只派发 `nav` 事件,不自动写入 `Hash.nav`,适合承载偏好设置、开关等功能菜单。
|
||||
* 垂直模式下二级菜单以内联展开方式显示,外层布局需提供局部滚动边界。
|
||||
|
||||
---
|
||||
|
||||
|
||||
107
dist/base.js
vendored
107
dist/base.js
vendored
@ -399,9 +399,31 @@
|
||||
}
|
||||
globalThis.MouseMover = MouseMover;
|
||||
const APIComponent = globalThis.Component.register("API", (container) => {
|
||||
container.request = globalThis.NewState({ url: "", method: "GET", headers: {}, data: null, timeout: 1e4, responseType: "" });
|
||||
container.response = globalThis.NewState({ loading: false, ok: null, status: null, error: null, headers: {}, responseType: "", result: null });
|
||||
container.result = globalThis.NewState();
|
||||
const presetRequest = container.request && typeof container.request === "object" ? container.request : {};
|
||||
const presetResponse = container.response && typeof container.response === "object" ? container.response : {};
|
||||
const presetResult = container.result && typeof container.result === "object" ? container.result : {};
|
||||
container.request = globalThis.NewState({
|
||||
url: "",
|
||||
method: "GET",
|
||||
headers: {},
|
||||
data: null,
|
||||
timeout: 1e4,
|
||||
responseType: "",
|
||||
...presetRequest,
|
||||
headers: { ...presetRequest.headers || {} }
|
||||
});
|
||||
container.response = globalThis.NewState({
|
||||
loading: false,
|
||||
ok: null,
|
||||
status: null,
|
||||
error: null,
|
||||
headers: {},
|
||||
responseType: "",
|
||||
result: null,
|
||||
...presetResponse,
|
||||
headers: { ...presetResponse.headers || {} }
|
||||
});
|
||||
container.result = globalThis.NewState(presetResult);
|
||||
container.do = (opt = {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = { ...container.request, ...opt };
|
||||
@ -860,55 +882,104 @@
|
||||
));
|
||||
globalThis.Component.register("Nav", (container) => {
|
||||
container.vertical = container.hasAttribute("vertical");
|
||||
container.state.openName = container.state.openName || null;
|
||||
container.click = (item, noselect) => {
|
||||
if (!item.noselect && !noselect) globalThis.Hash.nav = item.name;
|
||||
container.dispatchEvent(new CustomEvent("nav", { detail: { item }, bubbles: false }));
|
||||
};
|
||||
container.clickSubitem = (item) => {
|
||||
container.dispatchEvent(new CustomEvent("nav", { detail: { item }, bubbles: false }));
|
||||
};
|
||||
container.toggleGroup = (item) => {
|
||||
container.state.openName = container.state.openName === item.name ? null : item.name;
|
||||
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' : 'navbar navbar-expand border-bottom'} bg-body-secondary px-3 \${this.vertical ? 'py-3' : 'pb-0'} align-items-center \${this.vertical ? 'align-items-start' : ''}">
|
||||
<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="d-flex align-items-center gap-2">
|
||||
<template $if="this.state?.brand?.image">
|
||||
<img $src="this.state.brand.image" $class="\${this.vertical ? 'mb-4' : 'me-2'}" style="height:30px;width:auto;max-width:300px">
|
||||
<img $src="this.state.brand.image" $class="\${this.vertical ? 'mb-2' : 'me-2'}" style="height:30px;width:auto;max-width:300px">
|
||||
</template>
|
||||
<template $if="this.state?.brand?.icon">
|
||||
<i $class="bi bi-\${this.state.brand.icon} \${this.vertical ? 'mb-4' : 'me-2'}"></i>
|
||||
<i $class="bi bi-\${this.state.brand.icon} \${this.vertical ? 'mb-2' : 'me-2'}"></i>
|
||||
</template>
|
||||
<template $if="this.state?.brand?.label">
|
||||
<span $class="\${this.vertical ? 'mb-4 fw-bold' : 'me-2'}" $text="this.state.brand.label"></span>
|
||||
<span $class="\${this.vertical ? 'mb-2 fw-bold' : 'me-2'}" style="transform: translateY(3px);" $text="this.state.brand.label"></span>
|
||||
</template>
|
||||
<div $class="\${this.vertical ? 'w-100' : 'ms-2'}"></div>
|
||||
</div>
|
||||
<div $class="d-flex \${this.vertical ? 'w-100 flex-fill mt-2 flex-column' : 'ms-2 align-items-center flex-fill'}" $style="this.vertical ? 'min-height:0; overflow-y:auto; overflow-x:visible;' : ''">
|
||||
<template $each="this.state?.list || []">
|
||||
<div $class="\${this.vertical ? 'nav nav-pills flex-column w-100' : 'navbar-nav'} text-truncate \${item.type==='fill'?'flex-fill':''}">
|
||||
<div $class="\${this.vertical ? 'nav nav-pills flex-column w-100 position-relative' : 'navbar-nav flex-row align-items-center'} \${!this.vertical && item.type==='fill' ? 'flex-fill' : ''}" style="transform: translateY(3px);">
|
||||
<template $if="item.type==='label'">
|
||||
<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 ? 'text-start' : ''}" $onclick="this.click(item)">
|
||||
<i $class="bi bi-\${item.icon} me-2"></i><span $class="\${this.vertical ? '' : 'd-none d-' + (this.state?.list?.length>5?'lg':'md') + '-inline'}" $text="item.label"></span>
|
||||
<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)">
|
||||
<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>
|
||||
<template $if="item.type==='dropdown'">
|
||||
<div class="dropdown">
|
||||
<button $class="nav-link \${Hash.nav===item.name?'active':''} \${this.vertical ? 'text-start' : ''}" data-bs-toggle="dropdown">
|
||||
<i $class="bi bi-\${item.icon} me-2"></i><span $class="\${this.vertical ? '' : 'd-none d-' + (this.state?.list?.length>5?'lg':'md') + '-inline'}" $text="item.label"></span>
|
||||
<template $if="this.vertical">
|
||||
<div class="w-100">
|
||||
<button type="button" $class="nav-link w-100 text-start py-1 px-2 d-flex align-items-center justify-content-between \${item.class || ''}" $onclick="this.toggleGroup(item)">
|
||||
<span class="d-inline-flex align-items-center">
|
||||
<i $class="bi bi-\${item.icon} me-2"></i><span class="text-truncate" $text="item.label"></span>
|
||||
</span>
|
||||
<i $class="bi \${this.state.openName===item.name?'bi-chevron-down':'bi-chevron-right'} small ms-2"></i>
|
||||
</button>
|
||||
<div $class="dropdown-menu \${this.vertical?'position-static':'dropdown-menu-end'} p-3 bg-body-secondary shadow" $style="width: \${item.width || 250}px;">
|
||||
<template $if="this.state.openName===item.name">
|
||||
<div class="d-flex flex-column w-100 ps-2 mt-1">
|
||||
<template $each="item.list" as="subitem">
|
||||
<template $if="subitem.type==='label'">
|
||||
<div $class="small text-uppercase text-body-secondary fw-semibold px-2 py-1 \${subitem.class || ''}" $text="subitem.label"></div>
|
||||
</template>
|
||||
<template $if="subitem.type==='button'">
|
||||
<button class="nav-link px-0 w-100 text-start" $onclick="this.click(subitem, true)">
|
||||
<button $class="nav-link w-100 text-start py-1 px-2 \${subitem.class || ''}" $onclick="this.clickSubitem(subitem)">
|
||||
<i $class="bi bi-\${subitem.icon} me-2 d-inline-block" style="width: 16px;"></i><span $text="subitem.label"></span>
|
||||
</button>
|
||||
</template>
|
||||
<template $if="subitem.type==='switch'">
|
||||
<div class="d-flex align-items-center">
|
||||
<div $class="d-flex align-items-center px-2 py-2 \${subitem.class || ''}">
|
||||
<i $class="bi bi-\${subitem.icon} me-2 d-inline-block" style="width: 16px;"></i><span $text="subitem.label"></span><div class="flex-fill"></div>
|
||||
<div class="form-switch"><input class="form-check-input mx-0" type="checkbox" $bind="subitem.bind[subitem.name]"></div>
|
||||
<div class="form-switch ms-2"><input class="form-check-input" type="checkbox" $bind="subitem.bind[subitem.name]"></div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template $if="!this.vertical">
|
||||
<div class="dropdown">
|
||||
<button type="button" $class="nav-link dropdown-toggle \${item.class || ''}" data-bs-toggle="dropdown">
|
||||
<i $class="bi bi-\${item.icon} me-2"></i><span $class="'d-none d-' + (this.state?.list?.length>5?'lg':'md') + '-inline'" $text="item.label"></span>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-end p-2 bg-body-secondary shadow" $style="'width: ' + (item.width || 250) + 'px;'">
|
||||
<template $each="item.list" as="subitem">
|
||||
<template $if="subitem.type==='label'">
|
||||
<div $class="dropdown-header \${subitem.class || ''}" $text="subitem.label"></div>
|
||||
</template>
|
||||
<template $if="subitem.type==='button'">
|
||||
<button $class="dropdown-item \${subitem.class || ''}" $onclick="this.clickSubitem(subitem)">
|
||||
<i $class="bi bi-\${subitem.icon} me-2 d-inline-block" style="width: 16px;"></i><span $text="subitem.label"></span>
|
||||
</button>
|
||||
</template>
|
||||
<template $if="subitem.type==='switch'">
|
||||
<div $class="d-flex align-items-center px-3 py-2 \${subitem.class || ''}">
|
||||
<i $class="bi bi-\${subitem.icon} me-2 d-inline-block" style="width: 16px;"></i><span $text="subitem.label"></span><div class="flex-fill"></div>
|
||||
<div class="form-switch ms-2"><input class="form-check-input" type="checkbox" $bind="subitem.bind[subitem.name]"></div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
));
|
||||
|
||||
2
dist/base.min.js
vendored
2
dist/base.min.js
vendored
File diff suppressed because one or more lines are too long
29
src/API.js
29
src/API.js
@ -2,9 +2,32 @@
|
||||
* API Component Module
|
||||
*/
|
||||
const APIComponent = globalThis.Component.register('API', container => {
|
||||
container.request = globalThis.NewState({ url: '', method: 'GET', headers: {}, data: null, timeout: 10000, responseType: '' })
|
||||
container.response = globalThis.NewState({ loading: false, ok: null, status: null, error: null, headers: {}, responseType: '', result: null })
|
||||
container.result = globalThis.NewState()
|
||||
const presetRequest = container.request && typeof container.request === 'object' ? container.request : {}
|
||||
const presetResponse = container.response && typeof container.response === 'object' ? container.response : {}
|
||||
const presetResult = container.result && typeof container.result === 'object' ? container.result : {}
|
||||
|
||||
container.request = globalThis.NewState({
|
||||
url: '',
|
||||
method: 'GET',
|
||||
headers: {},
|
||||
data: null,
|
||||
timeout: 10000,
|
||||
responseType: '',
|
||||
...presetRequest,
|
||||
headers: { ...(presetRequest.headers || {}) }
|
||||
})
|
||||
container.response = globalThis.NewState({
|
||||
loading: false,
|
||||
ok: null,
|
||||
status: null,
|
||||
error: null,
|
||||
headers: {},
|
||||
responseType: '',
|
||||
result: null,
|
||||
...presetResponse,
|
||||
headers: { ...(presetResponse.headers || {}) }
|
||||
})
|
||||
container.result = globalThis.NewState(presetResult)
|
||||
container.do = (opt = {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = { ...container.request, ...opt }
|
||||
|
||||
79
src/nav.js
79
src/nav.js
@ -3,52 +3,101 @@
|
||||
*/
|
||||
globalThis.Component.register('Nav', container => {
|
||||
container.vertical = container.hasAttribute('vertical')
|
||||
container.state.openName = container.state.openName || null
|
||||
container.click = (item, noselect) => {
|
||||
if (!item.noselect && !noselect) globalThis.Hash.nav = item.name
|
||||
container.dispatchEvent(new CustomEvent('nav', { detail: { item }, bubbles: false }))
|
||||
}
|
||||
container.clickSubitem = item => {
|
||||
container.dispatchEvent(new CustomEvent('nav', { detail: { item }, bubbles: false }))
|
||||
}
|
||||
container.toggleGroup = item => {
|
||||
container.state.openName = container.state.openName === item.name ? null : item.name
|
||||
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' : 'navbar navbar-expand border-bottom'} bg-body-secondary px-3 \${this.vertical ? 'py-3' : 'pb-0'} align-items-center \${this.vertical ? 'align-items-start' : ''}">
|
||||
<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="d-flex align-items-center gap-2">
|
||||
<template $if="this.state?.brand?.image">
|
||||
<img $src="this.state.brand.image" $class="\${this.vertical ? 'mb-4' : 'me-2'}" style="height:30px;width:auto;max-width:300px">
|
||||
<img $src="this.state.brand.image" $class="\${this.vertical ? 'mb-2' : 'me-2'}" style="height:30px;width:auto;max-width:300px">
|
||||
</template>
|
||||
<template $if="this.state?.brand?.icon">
|
||||
<i $class="bi bi-\${this.state.brand.icon} \${this.vertical ? 'mb-4' : 'me-2'}"></i>
|
||||
<i $class="bi bi-\${this.state.brand.icon} \${this.vertical ? 'mb-2' : 'me-2'}"></i>
|
||||
</template>
|
||||
<template $if="this.state?.brand?.label">
|
||||
<span $class="\${this.vertical ? 'mb-4 fw-bold' : 'me-2'}" $text="this.state.brand.label"></span>
|
||||
<span $class="\${this.vertical ? 'mb-2 fw-bold' : 'me-2'}" style="transform: translateY(3px);" $text="this.state.brand.label"></span>
|
||||
</template>
|
||||
<div $class="\${this.vertical ? 'w-100' : 'ms-2'}"></div>
|
||||
</div>
|
||||
<div $class="d-flex \${this.vertical ? 'w-100 flex-fill mt-2 flex-column' : 'ms-2 align-items-center flex-fill'}" $style="this.vertical ? 'min-height:0; overflow-y:auto; overflow-x:visible;' : ''">
|
||||
<template $each="this.state?.list || []">
|
||||
<div $class="\${this.vertical ? 'nav nav-pills flex-column w-100' : 'navbar-nav'} text-truncate \${item.type==='fill'?'flex-fill':''}">
|
||||
<div $class="\${this.vertical ? 'nav nav-pills flex-column w-100 position-relative' : 'navbar-nav flex-row align-items-center'} \${!this.vertical && item.type==='fill' ? 'flex-fill' : ''}" style="transform: translateY(3px);">
|
||||
<template $if="item.type==='label'">
|
||||
<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 ? 'text-start' : ''}" $onclick="this.click(item)">
|
||||
<i $class="bi bi-\${item.icon} me-2"></i><span $class="\${this.vertical ? '' : 'd-none d-' + (this.state?.list?.length>5?'lg':'md') + '-inline'}" $text="item.label"></span>
|
||||
<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)">
|
||||
<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>
|
||||
<template $if="item.type==='dropdown'">
|
||||
<div class="dropdown">
|
||||
<button $class="nav-link \${Hash.nav===item.name?'active':''} \${this.vertical ? 'text-start' : ''}" data-bs-toggle="dropdown">
|
||||
<i $class="bi bi-\${item.icon} me-2"></i><span $class="\${this.vertical ? '' : 'd-none d-' + (this.state?.list?.length>5?'lg':'md') + '-inline'}" $text="item.label"></span>
|
||||
<template $if="this.vertical">
|
||||
<div class="w-100">
|
||||
<button type="button" $class="nav-link w-100 text-start py-1 px-2 d-flex align-items-center justify-content-between \${item.class || ''}" $onclick="this.toggleGroup(item)">
|
||||
<span class="d-inline-flex align-items-center">
|
||||
<i $class="bi bi-\${item.icon} me-2"></i><span class="text-truncate" $text="item.label"></span>
|
||||
</span>
|
||||
<i $class="bi \${this.state.openName===item.name?'bi-chevron-down':'bi-chevron-right'} small ms-2"></i>
|
||||
</button>
|
||||
<div $class="dropdown-menu \${this.vertical?'position-static':'dropdown-menu-end'} p-3 bg-body-secondary shadow" $style="width: \${item.width || 250}px;">
|
||||
<template $if="this.state.openName===item.name">
|
||||
<div class="d-flex flex-column w-100 ps-2 mt-1">
|
||||
<template $each="item.list" as="subitem">
|
||||
<template $if="subitem.type==='label'">
|
||||
<div $class="small text-uppercase text-body-secondary fw-semibold px-2 py-1 \${subitem.class || ''}" $text="subitem.label"></div>
|
||||
</template>
|
||||
<template $if="subitem.type==='button'">
|
||||
<button class="nav-link px-0 w-100 text-start" $onclick="this.click(subitem, true)">
|
||||
<button $class="nav-link w-100 text-start py-1 px-2 \${subitem.class || ''}" $onclick="this.clickSubitem(subitem)">
|
||||
<i $class="bi bi-\${subitem.icon} me-2 d-inline-block" style="width: 16px;"></i><span $text="subitem.label"></span>
|
||||
</button>
|
||||
</template>
|
||||
<template $if="subitem.type==='switch'">
|
||||
<div class="d-flex align-items-center">
|
||||
<div $class="d-flex align-items-center px-2 py-2 \${subitem.class || ''}">
|
||||
<i $class="bi bi-\${subitem.icon} me-2 d-inline-block" style="width: 16px;"></i><span $text="subitem.label"></span><div class="flex-fill"></div>
|
||||
<div class="form-switch"><input class="form-check-input mx-0" type="checkbox" $bind="subitem.bind[subitem.name]"></div>
|
||||
<div class="form-switch ms-2"><input class="form-check-input" type="checkbox" $bind="subitem.bind[subitem.name]"></div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template $if="!this.vertical">
|
||||
<div class="dropdown">
|
||||
<button type="button" $class="nav-link dropdown-toggle \${item.class || ''}" data-bs-toggle="dropdown">
|
||||
<i $class="bi bi-\${item.icon} me-2"></i><span $class="'d-none d-' + (this.state?.list?.length>5?'lg':'md') + '-inline'" $text="item.label"></span>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-end p-2 bg-body-secondary shadow" $style="'width: ' + (item.width || 250) + 'px;'">
|
||||
<template $each="item.list" as="subitem">
|
||||
<template $if="subitem.type==='label'">
|
||||
<div $class="dropdown-header \${subitem.class || ''}" $text="subitem.label"></div>
|
||||
</template>
|
||||
<template $if="subitem.type==='button'">
|
||||
<button $class="dropdown-item \${subitem.class || ''}" $onclick="this.clickSubitem(subitem)">
|
||||
<i $class="bi bi-\${subitem.icon} me-2 d-inline-block" style="width: 16px;"></i><span $text="subitem.label"></span>
|
||||
</button>
|
||||
</template>
|
||||
<template $if="subitem.type==='switch'">
|
||||
<div $class="d-flex align-items-center px-3 py-2 \${subitem.class || ''}">
|
||||
<i $class="bi bi-\${subitem.icon} me-2 d-inline-block" style="width: 16px;"></i><span $text="subitem.label"></span><div class="flex-fill"></div>
|
||||
<div class="form-switch ms-2"><input class="form-check-input" type="checkbox" $bind="subitem.bind[subitem.name]"></div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
`))
|
||||
|
||||
@ -28,6 +28,35 @@ export async function runTests() {
|
||||
if (!apiResp.ok) throw new Error('API component request failed');
|
||||
api.remove();
|
||||
|
||||
console.log('Testing API declarative preset request fields...');
|
||||
LocalStorage.secret = 'api-secret-token';
|
||||
const presetApi = document.createElement('API');
|
||||
presetApi.setAttribute('$.request.url', "'/echo'");
|
||||
presetApi.setAttribute('$.request.method', "'POST'");
|
||||
presetApi.setAttribute('$.request.data.secret', "LocalStorage.secret || ''");
|
||||
document.body.appendChild(presetApi);
|
||||
await new Promise(r => setTimeout(r, 50));
|
||||
|
||||
const originalFetch = globalThis.fetch;
|
||||
let capturedBody = null;
|
||||
globalThis.fetch = async (_url, options = {}) => {
|
||||
capturedBody = options.body;
|
||||
return new Response(JSON.stringify({ ok: true }), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
};
|
||||
try {
|
||||
const presetResp = await presetApi.do();
|
||||
if (!presetResp.ok) throw new Error('Declarative API preset request failed');
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
presetApi.remove();
|
||||
}
|
||||
if (capturedBody !== JSON.stringify({ secret: 'api-secret-token' })) {
|
||||
throw new Error('API declarative preset request.data.secret was not preserved');
|
||||
}
|
||||
|
||||
// 5. AutoForm & TagsInput Test
|
||||
console.log('Testing AutoForm...');
|
||||
const { NewState } = await import('@apigo.cc/state');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user