From 5f286185ae697bbec4b090ff5e7d584f18733c6c Mon Sep 17 00:00:00 2001 From: AI Engineer Date: Fri, 5 Jun 2026 20:05:26 +0800 Subject: [PATCH] Fixed virtual list scrolling issues by removing reactive padding bindings and optimizing hidden element height measurement. By: AICoder --- CAPABILITY.md | 372 ++ CHANGELOG.md | 19 + README.md | 344 +- dist/base.js | 1391 ++--- dist/base.min.js | 2 +- dist/base.min.mjs | 1 + dist/base.mjs | 920 +++ node_modules/.vite/deps/_metadata.json | 17 +- node_modules/.vite/deps/bootstrap.js | 5188 +++++++++++++++++ node_modules/.vite/deps/bootstrap.js.map | 7 + package.json | 2 +- src/index.js | 15 +- src/list.js | 44 +- test-results/.last-run.json | 6 +- .../error-context.md | 24 - test/all.spec.js | 1 + test/base.test.js | 4 + test/index.html | 4 + vite.config.js | 31 +- 19 files changed, 7576 insertions(+), 816 deletions(-) create mode 100644 CAPABILITY.md create mode 100644 dist/base.min.mjs create mode 100644 dist/base.mjs create mode 100644 node_modules/.vite/deps/bootstrap.js create mode 100644 node_modules/.vite/deps/bootstrap.js.map delete mode 100644 test-results/all-base-project-comprehen-6c76e-ts-and-scrolling-benchmarks/error-context.md diff --git a/CAPABILITY.md b/CAPABILITY.md new file mode 100644 index 0000000..f9e8dd8 --- /dev/null +++ b/CAPABILITY.md @@ -0,0 +1,372 @@ +# @apigo.cc/base 能力清单 (Capability Specification) + + +## 一、 全局导出与生命周期行为 + +### 1. 导出与全局挂载 +- **模块导出**:`index.js` 重新导出了 `http.js`, `ui.js`, `form.js`, `controls.js`, `list.js`, `nav.js`, `interaction.js` 中的所有对象,以及来自 `@apigo.cc/state` 的 `State`。 +- **全局命名空间挂载**:在 `window/globalThis` 上挂载了 `HTTP`, `UI`, `AutoForm`, `MouseMover`, `VirtualScroll`, `ApigoBase`。 +- **初始化自动刷新**:页面加载时(`DOMContentLoaded` 或立即执行),对 `document.documentElement` 执行 `RefreshState()`。 + +### 2. 退出拦截逻辑 +- **拦截触发条件**:当 `State.exitBlocks > 0` 时,通过 `window.addEventListener('beforeunload')` 拦截页面刷新或关闭(调用 `event.preventDefault()`)。 + +### 3. 主题自动适配 +- **逻辑行为**:若 `` 元素上既没有 `data-bs-theme` 也没有 `$data-bs-theme`,则自动注入属性: + `$data-bs-theme="LocalStorage.darkMode?'dark':'light'"`。 + + +--- + + +## 二、 HTTP 请求引擎 (`HTTP`) + +### 1. 核心方法 +- **`HTTP.request(options)`** (异步) + - **参数类型**:`options: Object` + - `url` (String, 必填): 请求地址。 + - `method` (String, 可选): 默认 `'POST'`。会内部自动转为大写。 + - `data` (Any, 可选): 请求载荷。 + - `headers` (Object, 可选): 默认 `{}`。 + - `responseType` (String, 可选): `'json' | 'binary' | 'text'`。若未提供,将根据响应头 `Content-Type` 自动解析(含 `application/json` 解析为 `'json'`,匹配图片/音视频/zip/pdf等解析为 `'binary'`,其余解析为 `'text'`)。 + - `timeout` (Number, 可选): 默认 `10000` (ms)。基于 `AbortSignal.timeout` 实现。 + - **请求载荷 (`data`) 自动处理逻辑**: + - 若 `data` 为 `HTMLFormElement`,内部自动转换为 `FormData`。 + - 若 `data` 为普通对象,且其属性中包含 `File`, `Blob`, `FileList` 实例,内部自动转换为 `FormData`。其中,`FileList` 或 `Array` 属性会依次 append 到相同 key 下,排除 `undefined` / `null`。 + - 若数据最终为 `FormData`,则 **自动删除** `headers['Content-Type']`(由浏览器自动设置 boundary)。 + - 若数据为普通对象且非二进制,且 `headers` 中无 `Content-Type`,则序列化为 JSON 字符串并自动添加 `headers['Content-Type'] = 'application/json'`。 + - **返回值**:`Promise`,其结构为: + ```javascript + { + ok: Boolean, // 请求是否成功 (status 在 200-299) + status: Number, // HTTP 状态码 + headers: Object, // 响应头键值对 + responseType: String, // 实际解析后的响应类型 ('json' | 'binary' | 'text') + result: Any, // 响应数据主体 + error: String | null // 错误信息 (若请求失败或 ok === false) + } + ``` +- **快捷方法** (内部包装 `HTTP.request`): + - `HTTP.get({ url, ...opt })` + - `HTTP.post({ url, data, ...opt })` + - `HTTP.put({ url, data, ...opt })` + - `HTTP.delete({ url, ...opt })` + - `HTTP.head({ url, ...opt })` + + +--- + + +## 三、 声明式数据组件 (``) + +### 1. 响应式状态模型 (`State`) +- **`request`** (读写): 映射请求参数。结构与 `HTTP.request` 参数一致: + `{ url: '', method: 'GET', headers: {}, data: null, timeout: 10000, responseType: '' }` +- **`response`** (读写): 包含当前请求执行结果。结构为: + `{ loading: false, ok: null, status: null, error: null, headers: {}, responseType: '', result: null }` +- **`result`** (读写): 响应结果快捷引用。当 `response.result` 为对象时,底层通过 `Object.assign` 合并更新;否则直接覆盖。 + +### 2. 编程接口方法 (`Methods`) +- **`do(opt = {})`** + - **用途**:手动执行请求。 + - **参数**:`opt` 对象的字段(如 `headers`, `data` 等)会与 `request` 属性合并。 + - **控制参数**:`opt.noui` (Boolean)。若为 `true`,则在请求失败时不自动调用 `UI.toast` 弹窗。 + - **返回值**:`Promise`。若请求失败或接口返回 `{ error: ... }`,Promise 会抛出异常(reject)。 + +### 3. 特殊逻辑行为 +- **`auto` (Attribute)**: + 若组件含有 `auto` 属性,且 `request.url` 非空,则深度监听 `request` 对象的变化。一旦变化,会在下一微任务队列(Promise.resolve())自动、异步触发 `do()`。有防抖机制,避免同一周期多次触发。 + +### 4. 事件派发 +- **`response`**:请求成功时触发。 + - `bubbles`: `false` + - `cancelable`: `false` + - `detail`: 完整的 `ResponseObject` 对象。 +- **`error`**:请求失败(网络错误、非2xx状态码、或返回数据中含 `error` 字段)时触发。 + - `bubbles`: `true` + - `cancelable`: `false` + - `detail`: Error 对象。 + + +--- + + +## 四、 万能表单系统 (``) + +### 1. 容器属性 (Attributes) +- **`vertical`** (Boolean): 开启垂直表单布局。 +- **`inline`** (Boolean): 开启无边框的流式行内布局(常用于顶部工具栏)。 +- **`nobutton`** (Boolean): 隐藏底部的默认提交/保存按钮。 +- **`api`** (String / Element): 绑定页面中 `` 组件的 ID(或直接为组件实例引用),以便在提交时自动对接。 +- **`submitlabel`** (String): 自定义提交按钮的文本内容。默认为 `"{#Submit#}"`。 + +### 2. 实例编程接口 +- **`data`** (读写): 表单内部数据的响应式 Proxy 模型(映射至 `state.data`)。可直接通过 `Object.assign(form.data, newData)` 或逐个属性赋值来修改表单中的值。 +- **`request`** (读写): 默认提交配置,默认为 `{ method: 'POST' }`。 +- **`response`** (只读): 提交请求返回的完整响应对象。 +- **`result`** (只读): 提交请求返回的 `response.result`。 +- **`submit(opt = {})`** + - **用途**:手动触发表单校验与提交。 + - **校验逻辑**:通过原生 `form.reportValidity()` 校验。若失败,调用 `UI.toast` 提示 `"{#verify failed#}"` 并退出。 + - **提交逻辑**:如果绑定了 `api` 属性,则调用 `api.do(req)`(其中 `req.data` 自动绑定为表单 `data`);否则,如果配置了 `request.url`,则直接调用 `HTTP.request(req)`;两者皆无则在控制台抛出警告。 + +### 3. Schema 项目 (`FormItem`) 结构定义 +`state.schema` 数组中每一项可配置的字段: +- **`name`** (String, 必填): 数据字段键名,与 `form.data` 中的属性双向绑定。 +- **`label`** (String, 可选): 表单项标签名称(仅在非 `inline` 模式下展示)。 +- **`type`** (String, 必填): 表单控件类型。 + - 原生支持:`'text'`, `'password'`, `'email'`, `'number'`, `'date'`, `'datetime'`, `'file'`, `'select'`, `'checkbox'`, `'radio'`, `'switch'`, `'textarea'`。 + - 扩展注册支持:如 `'DatePicker'`, `'ColorPicker'`, `'IconPicker'`, `'TagsInput'`。 +- **`if`** (String, 可选): 控制是否显示该表单项的条件表达式字符串。使用双重计算渲染(`$$if`),其执行上下文中 `this` 指向 AutoForm 组件实例,可以通过 `this.data` 引用其他字段值。 +- **`setting`** (Object, 可选): 原生属性透传对象,会通过 `$.="item.setting || {}"` 绑定到控件上。 +- **`options`** (Array, 可选): 针对 `select`, `checkbox`, `radio` 类型。 + - 格式可以为扁平字符串数组:`['Option1', 'Option2']` + - 也可以为键值对数组:`[{ label: '选项1', value: 'val1' }]` +- **`placeholder`** (String, 可选): 仅对 `select` 类型有效,表示未选中时的禁用占位选项。 +- **`vertical`** (Boolean, 可选): 仅对 `checkbox` 或 `radio` 类型有效。若为 `true`,各选项纵向排列;默认为 `false`(横向排列)。 +- **`text` / `label`** (String, 可选): 当 `checkbox` 或 `radio` 未提供 `options` 选项时,使用此值作为单选项的标签,其选中时的值是 `'on'`。 + +### 4. 插槽 (Slots) +- **`actions`**:在表单底部提交按钮左侧的区域插槽(仅在非 `inline` 且非 `nobutton` 模式下渲染)。 + +### 5. 事件派发 +- **`submit`**:在开始提交数据前触发。 + - `bubbles`: `false` + - `cancelable`: `true`(调用 `event.preventDefault()` 可彻底终止提交动作) + - `detail`: 当前表单的数据对象 `container.data`。 +- **`response`**:表单自动提交成功并得到接口响应后触发。 + - `bubbles`: `false` + - `cancelable`: `false` + - `detail`: 完整的响应对象 `resp`。 +- **`error`**:提交失败(网络错、校验错或返回的 json 带有 error 字段)时触发。 + - `bubbles`: `true` + - `cancelable`: `false` + - `detail`: Error 对象。 + +### 6. 表单控件扩展机制 (`AutoForm.register`) +- **`AutoForm.register(name, typeName)`** + - **用途**:将第三方/自定义 Web Component 注册为表单的自定义控件类型。 + - **参数**: + - `name` (String): 组件标签名(如 `'DatePicker'`)。 + - `typeName` (String, 可选): schema 中对应的 `type` 字符串。不传则默认等同于 `name`。 + - **实现逻辑**:当在 AutoForm 的 `schema` 中匹配到对应 `type` 时,自动在 DOM 树中插入该自定义组件,并将其属性 `$.` 绑定为 `item.setting`,其值 `bind` 双向绑定到 AutoForm 的对应数据字段。 + + +--- + + +## 五、 表单自定义扩展组件 + +### 1. 日期选择器 (``) +- **核心能力**:支持单日期选择,或双日期范围选择(主字段 + 影子字段模式)。 +- **启用范围选择**:在 AutoForm 的对应 Schema 项目中,配置 `setting.rangeEnd: "endFieldName"`;或在组件上直接配置 `rangeEnd` 属性。 +- **内部状态 (`state`)**: + - `start` (String): 开始日期。 + - `end` (String): 结束日期。 +- **实例属性**: + - `value` (读写): 映射至 `state.start`。 + - `isRange` (只读): 根据是否配置了 `rangeEnd` 返回 `Boolean` 值。 +- **操作方法**: + - `updateStart(val)`: 更新 `state.start` 并分发 `change` 事件。 + - `updateEnd(val)`: 更新 `state.end`,若处于 AutoForm 容器中,将该值同步写入到表单数据的 `rangeEnd` 对应字段中。 +- **事件派发**: + - `'change'`:当主字段/开始日期发生改变时触发。 + - `bubbles`: `true` + - `detail`: 改变后的开始日期字符串(`state.start`)。 + +### 2. 颜色选择器 (``) +- **核心能力**:提供颜色取色器(`type="color"`)与十六进制文本输入框的双向联动。 +- **内部状态 (`state`)**: + - `value` (String): 颜色值,默认为 `'#000000'`。 +- **实例属性**: + - `value` (读写): 映射至 `state.value`。 +- **方法**: + - `updateValue(val)`: 更新颜色状态并分发事件。 +- **事件派发**: + - `'change'`:当值发生改变时触发。 + - `bubbles`: `true` + - `detail`: 改变后的十六进制颜色字符串。 + +### 3. 图标选择器 (``) +- **核心能力**:基于 Bootstrap Icons 的可视化下拉搜索选择器。 +- **内部状态 (`state`)**: + - `value` (String): 当前选中的图标名。 + - `search` (String): 图标搜索关键字。 + - `open` (Boolean): 下拉菜单的展开状态。 +- **实例属性**: + - `value` (读写): 映射至 `state.value`。 + - `filteredIcons` (只读): 经过 `search` 关键字过滤后的图标名称列表。 +- **方法**: + - `selectIcon(icon)`: 选中指定图标,收起下拉框并派发 `change` 事件。 + - `toggle()`: 切换下拉框展开状态。若展开,会自动延时聚焦在搜索输入框中。 +- **事件派发**: + - `'change'`:当选中图标发生改变时触发。 + - `bubbles`: `true` + - `detail`: 选中的图标类名。 + - *点击外部收起*:监听全局 `click` 事件,点击外部自动收起下拉框;组件被移除 DOM 时自动注销该监听。 + +### 4. 标签输入框 (``) +- **核心能力**:可视化添加和删除标签。 +- **添加交互**:在输入框中输入内容后,按 `Enter`, `,` (逗号) 或 ` ` (空格) 会自动将标签加入。 +- **删除交互**:点击标签聚焦后,按 `Backspace` 或 `Delete` 键可将其移除,并自动聚焦到前一个标签。 +- **内部状态 (`state`)**: + - `tags` (Array): 存储标签字符串的数组。 +- **实例属性**: + - `value` (读写): 映射至 `state.tags`。 +- **事件派发**: + - `'change'`:当标签数组发生增删改变时触发。 + - `bubbles`: `true` + - `detail`: 最新标签的字符串数组。 + + +--- + + +## 六、 增强列表组件 (``) + +### 1. 列表布局模式 (`mode` Attribute) +- **`normal`** (默认): 扁平列表模式。 +- **`group`** (分组): + - 关联逻辑:通过 `list` 中成员的 `groupfield` 匹配 `groups` 数组中项的 `groupidfield`。 +- **`tree`** (树形): + - 关联逻辑:扁平化数组,通过每一项的 `parentfield` 匹配父项的 `idfield`。根项的值为空字符串 `''`。 + +### 2. 字段映射配置属性 (可重写默认值) +在组件初始化时,会调用 `Util.updateDefaults` 将以下属性更新为默认值: +- `idfield`: 项的主键字段名,默认 `'id'`。 +- `labelfield`: 项展示文本字段名,默认 `'label'`。 +- `summaryfield`: 项辅助文本字段名,默认 `'summary'`。 +- `groupidfield`: 分组的主键字段名,默认 `'id'`。 +- `grouplabelfield`: 分组展示文本字段名,默认 `'label'`。 +- `groupsummaryfield`: 分组辅助文本字段名,默认 `'summary'`。 +- `groupfield`: 项关联分组的字段名,默认 `'group'`。 +- `parentfield`: 树形下关联父节点的字段名,默认 `'parent'`。 +- `groupicon`: 组/父节点的图标类,默认 `'folder'` (对应 Bootstrap Icons 的类名)。 +- `itemicon`: 项/叶子节点的图标类,默认 `'file'`。 + +### 3. 功能属性 (Attributes) +- **`fast`** (Boolean): 启用虚拟滚动。 + - **重要约束**:`` 容器必须包含 Bootstrap 的 `overflow-auto` 类,且内置 `style="overflow-anchor:none"`(模板自带)。 +- **`collapsible`** (Boolean): 仅在 `tree` 模式下有效,开启树形节点的展开/收起能力。 +- **`auto-select`** (Boolean): 开启后,点击列表项时自动将该项的 `id` 记录至 `state.selectedItem`,如果重复点击则会置空。 +- **`auto-select-group`** (Boolean): 开启后,点击分组时自动将分组的 `id` 记录至 `state.selectedGroup`。 + +### 4. 内部状态模型 (`State`) +- `list` (读写): 原始列表数据数组。 +- `groups` (读写): 仅在 `group` 模式下使用,分组定义数组。 +- `collapsed` (读写): 仅在 `tree` 且 `collapsible` 时使用,存储节点 ID 折叠状态的 Map Proxy。 +- `selectedItem` (读写): 当前选中的列表项 ID。 +- `selectedGroup` (读写): 当前选中的分组 ID。 +- `_flatList` (只读): 列表核心逻辑处理后的扁平化数组。 +- `_renderedList` (只读): 实际在 DOM 中遍历渲染的数组片段(若开启虚拟滚动,则只包含可视区切片)。 + +### 5. 插槽 (Slots) +- **`item`**:列表单项渲染模板插槽。在自定义模板中,可以访问当前行数据 `item` 和索引 `index`。 +- **`item-actions`**:行数据右侧按钮工具栏区域。 +- **`group-actions`**:分组数据行右侧按钮工具栏区域。 + +### 6. 虚拟滚动运行参数 +- **高度优先规则**: + 若列表数据的某一项上配置了数字属性 `_itemHeight`,虚拟滚动引擎会将其作为该项的测量高度,直接跳过 DOM 的高度测量。 + +### 7. 事件派发 +- **`itemclick`**:点击列表项(非分组)时触发。 + - `bubbles`: `false` + - `detail`: `{ item, index }` (其中 `index` 为考虑虚拟滚动偏移量后的 **绝对全局索引**)。 +- **`groupclick`**:点击分组行时触发。 + - `bubbles`: `false` + - `detail`: `{ item, index }`。 + + +--- + + +## 七、 导航组件 (`