Compare commits
No commits in common. "5f286185ae697bbec4b090ff5e7d584f18733c6c" and "41d0745441a9e1b32e85b76686e9c8d4f9783ecd" have entirely different histories.
5f286185ae
...
41d0745441
372
CAPABILITY.md
372
CAPABILITY.md
@ -1,372 +0,0 @@
|
|||||||
# @apigo.cc/base 能力清单 (Capability Specification)
|
|
||||||
|
|
||||||
<!-- section: header -->
|
|
||||||
## 一、 全局导出与生命周期行为
|
|
||||||
|
|
||||||
### 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. 主题自动适配
|
|
||||||
- **逻辑行为**:若 `<html>` 元素上既没有 `data-bs-theme` 也没有 `$data-bs-theme`,则自动注入属性:
|
|
||||||
`$data-bs-theme="LocalStorage.darkMode?'dark':'light'"`。
|
|
||||||
<!-- endsection: header -->
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- section: http -->
|
|
||||||
## 二、 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<ResponseObject>`,其结构为:
|
|
||||||
```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 })`
|
|
||||||
<!-- endsection: http -->
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- section: api -->
|
|
||||||
## 三、 声明式数据组件 (`<API>`)
|
|
||||||
|
|
||||||
### 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<ResponseObject>`。若请求失败或接口返回 `{ 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 对象。
|
|
||||||
<!-- endsection: api -->
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- section: form -->
|
|
||||||
## 四、 万能表单系统 (`<AutoForm>`)
|
|
||||||
|
|
||||||
### 1. 容器属性 (Attributes)
|
|
||||||
- **`vertical`** (Boolean): 开启垂直表单布局。
|
|
||||||
- **`inline`** (Boolean): 开启无边框的流式行内布局(常用于顶部工具栏)。
|
|
||||||
- **`nobutton`** (Boolean): 隐藏底部的默认提交/保存按钮。
|
|
||||||
- **`api`** (String / Element): 绑定页面中 `<API>` 组件的 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 的对应数据字段。
|
|
||||||
<!-- endsection: form -->
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- section: extension-controls -->
|
|
||||||
## 五、 表单自定义扩展组件
|
|
||||||
|
|
||||||
### 1. 日期选择器 (`<DatePicker>`)
|
|
||||||
- **核心能力**:支持单日期选择,或双日期范围选择(主字段 + 影子字段模式)。
|
|
||||||
- **启用范围选择**:在 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. 颜色选择器 (`<ColorPicker>`)
|
|
||||||
- **核心能力**:提供颜色取色器(`type="color"`)与十六进制文本输入框的双向联动。
|
|
||||||
- **内部状态 (`state`)**:
|
|
||||||
- `value` (String): 颜色值,默认为 `'#000000'`。
|
|
||||||
- **实例属性**:
|
|
||||||
- `value` (读写): 映射至 `state.value`。
|
|
||||||
- **方法**:
|
|
||||||
- `updateValue(val)`: 更新颜色状态并分发事件。
|
|
||||||
- **事件派发**:
|
|
||||||
- `'change'`:当值发生改变时触发。
|
|
||||||
- `bubbles`: `true`
|
|
||||||
- `detail`: 改变后的十六进制颜色字符串。
|
|
||||||
|
|
||||||
### 3. 图标选择器 (`<IconPicker>`)
|
|
||||||
- **核心能力**:基于 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. 标签输入框 (`<TagsInput>`)
|
|
||||||
- **核心能力**:可视化添加和删除标签。
|
|
||||||
- **添加交互**:在输入框中输入内容后,按 `Enter`, `,` (逗号) 或 ` ` (空格) 会自动将标签加入。
|
|
||||||
- **删除交互**:点击标签聚焦后,按 `Backspace` 或 `Delete` 键可将其移除,并自动聚焦到前一个标签。
|
|
||||||
- **内部状态 (`state`)**:
|
|
||||||
- `tags` (Array): 存储标签字符串的数组。
|
|
||||||
- **实例属性**:
|
|
||||||
- `value` (读写): 映射至 `state.tags`。
|
|
||||||
- **事件派发**:
|
|
||||||
- `'change'`:当标签数组发生增删改变时触发。
|
|
||||||
- `bubbles`: `true`
|
|
||||||
- `detail`: 最新标签的字符串数组。
|
|
||||||
<!-- endsection: extension-controls -->
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- section: list -->
|
|
||||||
## 六、 增强列表组件 (`<List>`)
|
|
||||||
|
|
||||||
### 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): 启用虚拟滚动。
|
|
||||||
- **重要约束**:`<List>` 容器必须包含 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 }`。
|
|
||||||
<!-- endsection: list -->
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- section: nav -->
|
|
||||||
## 七、 导航组件 (`<Nav>`)
|
|
||||||
|
|
||||||
### 1. 结构与属性
|
|
||||||
- **`vertical`** (Attribute, Boolean): 决定导航为垂直面板布局(带有右侧分割线)或水平导航栏布局(带底部阴影)。
|
|
||||||
|
|
||||||
### 2. 响应式数据模型 (`State`)
|
|
||||||
- **`brand`** (Object, 可选): `{ image, icon, label }`,控制左上角/顶部的 Logo、图标及品牌文本。
|
|
||||||
- **`list`** (Array, 必填): 导航栏项的数组。
|
|
||||||
|
|
||||||
### 3. 导航项结构(`list` 成员)
|
|
||||||
- **`type`**: 决定导航项的渲染模式:
|
|
||||||
- `'button'`: 导航按钮。
|
|
||||||
- `'dropdown'`: 下拉菜单导航。含有 `list` (子项数组),子项类型可为:
|
|
||||||
- `'button'`: 子项按钮。
|
|
||||||
- `'switch'`: 状态开关。需要配置 `bind` (状态Proxy引用) 和 `name` (要绑定的字段名)。
|
|
||||||
- 水平布局下可以通过 `width` (Number) 控制下拉菜单宽度,默认 `250`。
|
|
||||||
- `'fill'`: 弹性填充块(`flex-fill`),用于实现右对齐等布局。
|
|
||||||
- **`name`** (String): 导航标识。当点击导航按钮时,该 `name` 会自动被写入 `Hash.nav` 以同步 URL 哈希。
|
|
||||||
- **`label`** (String): 文本展示。
|
|
||||||
- **`icon`** (String): 图标类名(基于 Bootstrap Icons)。
|
|
||||||
- **`noselect`** (Boolean, 可选): 若为 `true`,点击该项仅派发点击事件,**不会** 修改 `Hash.nav`。
|
|
||||||
|
|
||||||
### 4. 事件派发
|
|
||||||
- **`nav`**:点击任何菜单项或下拉子菜单项时触发。
|
|
||||||
- `bubbles`: `false`
|
|
||||||
- `detail`: `{ item }` (被点击的导航项数据定义)。
|
|
||||||
<!-- endsection: nav -->
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- section: resizer -->
|
|
||||||
## 八、 拖拽改变大小组件 (`<Resizer>`)
|
|
||||||
|
|
||||||
### 1. 组件属性
|
|
||||||
- **`vertical`** (Attribute, Boolean): 为 `true` 时,鼠标为 `row-resize`,调整高度;默认调整宽度。
|
|
||||||
- **`min`** (Attribute, Number): 像素下限,默认 `10`。
|
|
||||||
- **`max`** (Attribute, Number): 像素上限,默认 `1000`。
|
|
||||||
- **`target`** (Element): 要调整大小的目标 DOM 元素。默认是组件的前一个兄弟节点 (`container.previousElementSibling`)。
|
|
||||||
|
|
||||||
### 2. 事件与双向绑定
|
|
||||||
- **`bind`** (输入拦截): 支持通过绑定表达式传入像素值(数字),自动设置为 target 的 `width`/`height`。
|
|
||||||
- **`resizing`**:拖动调整大小的过程中持续触发。
|
|
||||||
- `bubbles`: `false`
|
|
||||||
- `detail`: `{ oldSize, newSize }`
|
|
||||||
- **`resize`**:拖动结束时触发。
|
|
||||||
- `bubbles`: `false`
|
|
||||||
- `detail`: `{ oldSize, newSize }`
|
|
||||||
- **`change`**:拖动结束时触发,输出最终的大小数值。常用于将新尺寸写回全局 `State`。
|
|
||||||
- `bubbles`: `false`
|
|
||||||
- `detail`: `newSize` (Number)
|
|
||||||
<!-- endsection: resizer -->
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- section: ui -->
|
|
||||||
## 九、 编程交互工具集 (`UI`)
|
|
||||||
|
|
||||||
### 1. 核心对话框方法 (`UI.showDialog`)
|
|
||||||
- **`UI.showDialog({ title, message, buttons, type })`**
|
|
||||||
- **参数**:
|
|
||||||
- `title` (String, 可选): 对话框标题。
|
|
||||||
- `message` (String, 必填): 提示消息,支持 HTML 标记(`$html` 渲染)。
|
|
||||||
- `buttons` (Array, 可选): 按钮文字数组,默认 `['{#Close#}']`。
|
|
||||||
- `type` (String, 可选): 主题类型 `'primary' | 'danger' | 'warning' | 'success'` 等,控制边框、标题及最后一个主按钮的颜色。
|
|
||||||
- **返回值**:`Promise<Number>`。
|
|
||||||
- 点击按钮返回其索引值 (从 `1` 开始)。
|
|
||||||
- 点击关闭图标或按 ESC 取消返回 `0`。
|
|
||||||
|
|
||||||
### 2. 快捷对话框
|
|
||||||
- **`UI.alert(message, options)`**:包装 `UI.showDialog`。
|
|
||||||
- **`UI.confirm(message, options)`**:确认框。
|
|
||||||
- 默认按钮:`['{#Cancel#}', '{#Confirm#}']`。
|
|
||||||
- 返回值:`Promise<Boolean>` (选中确认按钮为 `true`,取消为 `false`)。
|
|
||||||
|
|
||||||
### 3. 轻提示方法 (`UI.toast`)
|
|
||||||
- **`UI.toast(message, options)`**
|
|
||||||
- **参数**:
|
|
||||||
- `message` (String, 必填): 消息文本。
|
|
||||||
- `options` (Object, 可选):
|
|
||||||
- `delay` (Number): 自动关闭延迟时间(毫秒),默认 `5000`。若传入 `0` 则永久不消失。
|
|
||||||
- `type` (String): 主题背景色 `'primary' | 'success' | 'danger' | 'warning'` 等。
|
|
||||||
- `buttons` (Array): 自定义 Toast 内嵌按钮,点击会设置 `result = index + 1` 并在点击后自动 dismiss。
|
|
||||||
- `container` (String): 目标 Toast 容器 ID,默认 `'default'`。
|
|
||||||
|
|
||||||
### 4. 快捷 Toast 确认
|
|
||||||
- **`UI.toastConfirm(message, options)`**
|
|
||||||
- 弹出带有一个 `Confirm` 按钮的 Toast。
|
|
||||||
- 返回值:`Promise<Boolean>`(点击确认按钮返回 `true`,其余情况返回 `false`)。
|
|
||||||
<!-- endsection: ui -->
|
|
||||||
19
CHANGELOG.md
19
CHANGELOG.md
@ -1,24 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [1.0.10] - 2026-06-05
|
|
||||||
### Fixed
|
|
||||||
- List: Removed reactive data-binding for `prevHeight` and `postHeight` and refactored virtual scroll to directly mutate padding DOM node heights, preventing reactive re-render overhead.
|
|
||||||
- List: Ignored elements with `offsetHeight === 0` during layout measurement, preventing hidden tab state (display: none) from polluting height average `avg`.
|
|
||||||
- List: Applied a safety minimum limit (16px) to average item height calculations to prevent `visibleCount` explosion.
|
|
||||||
- List: Implemented a synchronization re-entry lock using `setTimeout` in list refresh to cut recursive `scroll` event loops.
|
|
||||||
- Config: Configured `server.fs.allow: ['..']` in vite.config.js to allow local development cross-directory assets access (like fonts).
|
|
||||||
- Dependency: Updated `@apigo.cc/state` CDN script dependency to `1.0.15`.
|
|
||||||
|
|
||||||
## [1.0.9] - 2026-06-05
|
|
||||||
### Changed
|
|
||||||
- Document: Comprehensive rewrite of base/README.md utilizing example-driven and static declaration binding paradigm.
|
|
||||||
- Document: Created base/CAPABILITY.md as the 100% complete spec checklist for all components and API.
|
|
||||||
- Config: Updated @apigo.cc/state dependency reference to v1.0.13.
|
|
||||||
|
|
||||||
## [1.0.8] - 2026-06-05
|
|
||||||
### Changed
|
|
||||||
- Bump version and align dependencies.
|
|
||||||
|
|
||||||
## [1.0.7] - 2026-05-29
|
## [1.0.7] - 2026-05-29
|
||||||
### Added
|
### Added
|
||||||
- DatePicker: New control with range support (main/shadow field sync).
|
- DatePicker: New control with range support (main/shadow field sync).
|
||||||
|
|||||||
344
README.md
344
README.md
@ -1,288 +1,112 @@
|
|||||||
# @apigo.cc/base - AI 逻辑操作说明书 (示例驱动)
|
# @web/base AI 开发指南 (全面版)
|
||||||
|
|
||||||
本库是基于 `@apigo.cc/state` 增强的 UI 原子库,提供遵循 Bootstrap 5.3 规范的高阶业务组件。
|
`@web/base` 是基于 State.js 构建的高性能 Web 基础组件库。它采用**原生 ESM、零打包**架构,深度集成 Bootstrap 5,旨在为 AI 驱动的开发提供极致精简且功能完备的 UI 与逻辑基建。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. 快速集成 (Quick Start)
|
## 一、 快速开始
|
||||||
|
|
||||||
|
### 1. 引入依赖
|
||||||
|
在 HTML 中配置 `importmap`。推荐使用 `loader.js` 自动管理:
|
||||||
|
|
||||||
### CDN 集成 (自包含 UMD,锁定版本号,无需引入 Bootstrap CSS)
|
|
||||||
```html
|
```html
|
||||||
<!-- 依赖核心库 -->
|
<!-- 依赖 Bootstrap 5 -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/state@1.0.15/dist/state.min.js"></script>
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<!-- Bootstrap 集成引擎 (自包含 CSS 注入) -->
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/bootstrap@1.0.5/dist/bootstrap.min.js"></script>
|
|
||||||
<!-- 本组件库 -->
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/base@1.0.10/dist/base.min.js"></script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### ESM 模块引入
|
<script type="importmap">
|
||||||
```javascript
|
{
|
||||||
import { HTTP, UI, AutoForm, State } from '@apigo.cc/base'
|
"imports": {
|
||||||
```
|
"@web/state": "path/to/state.mjs",
|
||||||
|
"@web/base": "path/to/base.mjs"
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 组件超能力示例 (Mega Examples)
|
|
||||||
|
|
||||||
### 2.1 声明式数据组件 (`<API>`)
|
|
||||||
```html
|
|
||||||
<API id="userSearchApi" auto
|
|
||||||
$request='{
|
|
||||||
"url": "/api/users/search",
|
|
||||||
"method": "POST",
|
|
||||||
"data": { "keyword": State.searchKey }
|
|
||||||
}'
|
|
||||||
$onresponse="console.log('加载成功,响应对象:', event.detail); State.userList = event.detail.result"
|
|
||||||
$onerror="UI.toast('加载失败:' + event.detail.message, { type: 'danger' })">
|
|
||||||
</API>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// 手动调用 API 示例 (适合动态触发/事后赋值场景)
|
|
||||||
async function forceReload() {
|
|
||||||
try {
|
|
||||||
// do() 返回 Promise<ResponseObject>。传入 noui: true 屏蔽全局 UI 弹窗报错。
|
|
||||||
const resp = await userSearchApi.do({ noui: true });
|
|
||||||
console.log("手动加载完成,结果:", resp.result);
|
|
||||||
} catch (err) {
|
|
||||||
console.error("请求失败", err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
* **AI 核心要点**:
|
|
||||||
* **请求响应结构 (ResponseObject)**:
|
### 2. 导出清单
|
||||||
`{ ok: Boolean, status: Number, headers: Object, responseType: 'json'|'binary'|'text', result: Any, error: String|null }`
|
* **Logic**: `HTTP`, `UI`, `State`, `MouseMover`
|
||||||
* 深度监听 `request` 任何字段的改变自动触发请求(微任务防抖)。`response` 事件为 `bubbles: false`,`error` 为 `bubbles: true`。
|
* **Components**: `<API>`, `<Modal>`, `<Dialog>`, `<Toast>`, `<AutoForm>`, `<TagsInput>`, `<DatePicker>`, `<ColorPicker>`, `<IconPicker>`, `<FastList>`, `<List>`, `<GroupedList>`, `<FastGroupedList>`, `<Tree>`, `<FastTree>`, `<CollapseTree>`, `<Nav>`, `<Resizer>`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 2.2 万能表单与内嵌控件 (`<AutoForm>`)
|
## 二、 核心组件详述
|
||||||
```html
|
|
||||||
<script>
|
|
||||||
// 1. 预先确定表单 Schema (合并了所有扩展控件配置)
|
|
||||||
const employeeFormSchema = [
|
|
||||||
{ name: 'name', label: '姓名', type: 'text', setting: { required: true } },
|
|
||||||
{
|
|
||||||
name: 'joinDate',
|
|
||||||
label: '入职周期',
|
|
||||||
type: 'DatePicker', // 内置日期范围选择控件
|
|
||||||
setting: { rangeEnd: 'leaveDate' } // 影子字段绑定为 leaveDate
|
|
||||||
},
|
|
||||||
{ name: 'color', label: '工位颜色', type: 'ColorPicker' }, // 内置颜色控件
|
|
||||||
{ name: 'avatarIcon', label: '头像图标', type: 'IconPicker' }, // 内置图标控件
|
|
||||||
{ name: 'skills', label: '专业技能', type: 'TagsInput' }, // 内置多标签控件
|
|
||||||
{
|
|
||||||
name: 'role',
|
|
||||||
label: '角色',
|
|
||||||
type: 'select',
|
|
||||||
options: [{ label: '管理员', value: 'admin' }, { label: '员工', value: 'staff' }],
|
|
||||||
placeholder: '请选择角色...'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'status',
|
|
||||||
label: '状态',
|
|
||||||
type: 'checkbox',
|
|
||||||
options: ['在职', '离职'],
|
|
||||||
if: "this.data.age > 18" // 仅在 age > 18 时渲染,this 访问 AutoForm 实例
|
|
||||||
},
|
|
||||||
{ name: 'notify', label: '启用通知', type: 'switch' }
|
|
||||||
];
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- 2. HTML 声明式属性绑定 schema 状态,且绑定接口 API 实例 -->
|
### 1. 网络与数据 (`HTTP` & `<API>`)
|
||||||
<AutoForm id="employeeForm"
|
* **`HTTP`**: 静态请求工具(`get`, `post`, `put`, `delete`, `request`)。
|
||||||
vertical
|
* **`<API>`**: 声明式请求容器。
|
||||||
$state.schema="employeeFormSchema"
|
* `$.request.url`: 请求地址。
|
||||||
$api="saveEmployeeApi"
|
* `auto`: 属性存在时,url 变化自动触发请求。
|
||||||
$onresponse="UI.toast('保存成功!')">
|
* `@response`: 请求成功事件。
|
||||||
|
|
||||||
|
### 2. UI 交互 (Namespace `UI`)
|
||||||
|
* `UI.alert(msg)` / `UI.confirm(msg)`: 基础对话框。
|
||||||
|
* `UI.toast(msg, {type: 'success'})`: 自动消失的轻提示。
|
||||||
|
|
||||||
|
### 3. 数据驱动表单 (`<AutoForm>`)
|
||||||
|
核心配置项:`$.state.schema` (结构) 和 `data` (响应式数据)。
|
||||||
|
|
||||||
|
* **`data`**: 表单实时绑定的响应式对象 (建议通过 `NewState` 创建)。
|
||||||
|
* **`field.if`**: 支持使用字符串表达式进行联动显隐。在表达式中可通过 `this.data` 访问表单数据。
|
||||||
|
|
||||||
|
**示例 (动态联动显隐):**
|
||||||
|
```html
|
||||||
|
<AutoForm
|
||||||
|
$.state.schema="[
|
||||||
|
{name:'type', type:'select', options:['personal', 'company'], label:'类型'},
|
||||||
|
{name:'taxId', type:'text', label:'税号', if: 'this.data.type === \'company\''}
|
||||||
|
]"
|
||||||
|
$.data="formState">
|
||||||
</AutoForm>
|
</AutoForm>
|
||||||
|
|
||||||
<script>
|
|
||||||
// 3. 数据层回显/事后赋值操作 (使用 Object.assign 保证绑定的 Proxy 响应式链路不丢失)
|
|
||||||
Object.assign(employeeForm.state.data, {
|
|
||||||
name: '张三',
|
|
||||||
joinDate: '2026-06-01',
|
|
||||||
leaveDate: '2026-06-30',
|
|
||||||
color: '#ff0000',
|
|
||||||
avatarIcon: 'person',
|
|
||||||
skills: ['Vue', 'React'],
|
|
||||||
role: 'staff',
|
|
||||||
notify: true
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
```
|
||||||
* **AI 核心要点**:
|
|
||||||
* 容器属性:`vertical` (垂直排列)、`inline` (无边框流式行内布局)、`nobutton` (隐藏底部默认按钮)。
|
|
||||||
* **表单数据操作红线**:禁止对 `form.state.data` 或 `form.data` 执行覆盖式重新赋值,必须使用 `Object.assign`。
|
|
||||||
|
|
||||||
---
|
#### 新增高级表单控件
|
||||||
|
* **`<DatePicker>`**: 支持范围选择。设置 `setting: { rangeEnd: 'field_name' }` 即可开启范围模式,自动同步结束日期到指定字段。
|
||||||
|
* **`<ColorPicker>`**: 颜色选择器,支持可视化选取及十六进制文本同步。
|
||||||
|
* **`<IconPicker>`**: 基于 Bootstrap Icons 的可视化图标选择器,支持搜索过滤。
|
||||||
|
|
||||||
### 2.3 增强列表组件 (`<List>`)
|
### 4. 高性能列表 (`<FastList>` 家族)
|
||||||
|
支持万级数据、动态高度、虚拟滚动。
|
||||||
|
|
||||||
列表有三类布局模式,分别有不同的数据结构关联,均推荐在 HTML 中声明绑定:
|
| 组件名 | 特点 |
|
||||||
|
| :--- | :--- |
|
||||||
|
| `<FastList>` | 基础虚拟滚动列表。 |
|
||||||
|
| `<FastGroupedList>` | 带分组的虚拟列表。 |
|
||||||
|
| `<FastTree>` | 虚拟滚动树。 |
|
||||||
|
| `<CollapseTree>` | 支持折叠的树结构。 |
|
||||||
|
|
||||||
#### 模式一:普通扁平列表 (`mode="normal"`)
|
**示例 (动态高度列表):**
|
||||||
* **关联逻辑**:直接渲染一维数组项。
|
|
||||||
* **数据结构**:
|
|
||||||
```javascript
|
|
||||||
const userList = [
|
|
||||||
{ id: '1', label: '小明', summary: '前端开发' },
|
|
||||||
{ id: '2', label: '小红', summary: '产品经理' }
|
|
||||||
];
|
|
||||||
```
|
|
||||||
* **HTML 写法**:
|
|
||||||
```html
|
|
||||||
<List mode="normal" $list="userList" $onitemclick="console.log(event.detail.item)"></List>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 模式二:分组列表 (`mode="group"`)
|
|
||||||
* **关联逻辑**:`list` 的项通过 `group` 字段去匹配 `groups` 数组中的 `id` 字段。
|
|
||||||
* **数据结构**:
|
|
||||||
```javascript
|
|
||||||
const deptGroups = [
|
|
||||||
{ id: 'devGroup', label: '研发中心', summary: '共 5 人' },
|
|
||||||
{ id: 'hrGroup', label: '人力资源', summary: '共 2 人' }
|
|
||||||
];
|
|
||||||
const groupUserList = [
|
|
||||||
{ id: '1', label: '小明', group: 'devGroup' },
|
|
||||||
{ id: '2', label: '小红', group: 'hrGroup' }
|
|
||||||
];
|
|
||||||
```
|
|
||||||
* **HTML 写法**:
|
|
||||||
```html
|
|
||||||
<List mode="group" $list="groupUserList" $state.groups="deptGroups"></List>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 模式三:树形可折叠列表 (`mode="tree"`) —— 超级示例
|
|
||||||
* **关联逻辑**:通过每项的 `parent` 关联父项的 `id` 属性,根项的 `parent` 设为空字符串 `''`。
|
|
||||||
* **数据结构**(内置 `_itemHeight` 优化虚拟滚动高度测量):
|
|
||||||
```javascript
|
|
||||||
const deptTreeList = [
|
|
||||||
{ id: '1', label: '总经办', parent: '', _itemHeight: 40 },
|
|
||||||
{ id: '2', label: '研发部', parent: '1', _itemHeight: 40 },
|
|
||||||
{ id: '3', label: '开发组', parent: '2', _itemHeight: 40 }
|
|
||||||
];
|
|
||||||
```
|
|
||||||
* **HTML 写法**:
|
|
||||||
```html
|
|
||||||
<List id="orgTreeList"
|
|
||||||
mode="tree"
|
|
||||||
fast
|
|
||||||
collapsible
|
|
||||||
auto-select
|
|
||||||
class="overflow-auto border"
|
|
||||||
style="height: 400px;"
|
|
||||||
$list="deptTreeList"
|
|
||||||
$onitemclick="console.log('点击节点:', event.detail.item, '全局绝对索引:', event.detail.index)">
|
|
||||||
|
|
||||||
<!-- 自定义单项右侧动作区域 -->
|
|
||||||
<template slot-id="item">
|
|
||||||
<span class="fw-semibold text-primary" $text="item.label"></span>
|
|
||||||
<div slot-id="item-actions">
|
|
||||||
<button class="btn btn-sm btn-link py-0 bi bi-plus-circle" onclick="addNode(item)"></button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</List>
|
|
||||||
```
|
|
||||||
* **AI 核心要点**:
|
|
||||||
* **属性默认值**:当数据结构符合默认字段命名(`id`, `label`, `summary`, `parent`, `group`)时,**无须填写**任何映射属性(如 `idfield`, `labelfield`, `parentfield` 等),实际编写代码时应当将其省略。
|
|
||||||
* 虚拟滚动 (`fast`) 强制容器必须声明为 `overflow-auto` 类。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2.4 导航组件 (`<Nav>`)
|
|
||||||
```html
|
```html
|
||||||
<script>
|
<FastList $.state.list="items">
|
||||||
// 1. 预先确定品牌与菜单数据
|
<div slot="item" $.style.height="${item.h}px" $text="item.label"></div>
|
||||||
const myBrand = { icon: 'shield-lock', label: '安全控制台' };
|
</FastList>
|
||||||
const myNavList = [
|
|
||||||
{ type: 'button', name: 'dashboard', label: '仪表盘', icon: 'speedometer' },
|
|
||||||
{
|
|
||||||
type: 'dropdown',
|
|
||||||
name: 'settings',
|
|
||||||
label: '系统设置',
|
|
||||||
icon: 'gear',
|
|
||||||
list: [
|
|
||||||
{ type: 'button', name: 'profile', label: '个人信息', icon: 'person' },
|
|
||||||
{ type: 'switch', name: 'darkMode', label: '暗黑模式', icon: 'moon', bind: LocalStorage, name: 'darkMode' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{ type: 'fill' },
|
|
||||||
{ type: 'button', name: 'logout', label: '退出登录', icon: 'box-arrow-right', noselect: true }
|
|
||||||
];
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- 2. HTML 声明式属性绑定 -->
|
|
||||||
<Nav id="sidebarNav"
|
|
||||||
vertical
|
|
||||||
$state.brand="myBrand"
|
|
||||||
$state.list="myNavList"
|
|
||||||
$onnav="console.log('导航点击:', event.detail.item)">
|
|
||||||
</Nav>
|
|
||||||
```
|
```
|
||||||
* **AI 核心要点**:
|
|
||||||
* 常规导航项被点击时(且 `noselect` 不为 true),会自动更新全局 `Hash.nav = item.name`。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 2.5 拖拽改变大小组件 (`<Resizer>`)
|
## 三、 组件详细清单与 API
|
||||||
```html
|
|
||||||
<div class="d-flex" style="height: 300px;">
|
### 逻辑类 (JS API)
|
||||||
<div id="leftPanel" style="width: 200px;" class="bg-light">侧边栏</div>
|
* **`HTTP.request(opt)`**: 返回 `Promise`,结果包含 `ok`, `status`, `result`, `error`。
|
||||||
<!-- 拖拽调节器,默认 target 为前一个兄弟节点 -->
|
* **`UI.showDialog({title, message, buttons, type})`**: 弹出自定义对话框。
|
||||||
<Resizer target="leftPanel"
|
* **`MouseMover.start(event, callbacks)`**: 全局鼠标移动监听(用于拖拽)。
|
||||||
min="100"
|
|
||||||
max="400"
|
### 组件类 (Custom Elements)
|
||||||
$onresizing="console.log('拖动尺寸:', event.detail.newSize)"
|
* **`<Modal>`**:
|
||||||
$change="State.leftPanelWidth">
|
* `$bind="state.show"`: 双向绑定显隐。
|
||||||
</Resizer>
|
* `slot-id="header/body/footer"`: 内容插槽。
|
||||||
<div class="flex-fill">内容区</div>
|
* **`<TagsInput>`**: 标签录入,绑定数据为字符串数组。
|
||||||
</div>
|
* **`<Nav>`**: 响应式导航栏。
|
||||||
```
|
* `$.state.brand`: `{image, icon, label}`。
|
||||||
* **AI 核心要点**:
|
* `$.state.list`: 菜单数组。
|
||||||
* `resizing` 与 `resize` 事件的 `detail` 结构均为 `{ oldSize: Number, newSize: Number }`;`change` 事件的 `detail` 为 `newSize` 像素数字。
|
* **`<Resizer>`**:
|
||||||
|
* `vertical`: 属性,切换水平/垂直。
|
||||||
|
* `min/max`: 限制尺寸。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. 网络与交互工具集 (`HTTP` & `UI`)
|
## 四、 最佳实践 (AI 指令)
|
||||||
|
1. **拒绝手动 DOM 操作**: 优先使用 `$bind` 和 `$.state` 驱动 UI。
|
||||||
### 3.1 HTTP 请求工具 (`HTTP`)
|
2. **列表性能**: 大数据量(>50条)强制使用 `Fast` 系列组件。
|
||||||
```javascript
|
3. **样式优先**: 优先使用 Bootstrap 5 Utility classes (如 `d-flex`, `p-3`, `gap-2`)。
|
||||||
// 支持 HTTP.get, HTTP.post, HTTP.put, HTTP.delete, HTTP.head
|
4. **微任务**: 涉及 DOM 尺寸计算的代码必须包裹在 `Promise.resolve().then(() => { ... })` 中。
|
||||||
const resp = await HTTP.post({
|
|
||||||
url: '/api/user/save',
|
|
||||||
data: { name: 'Alice', file: avatarFile },
|
|
||||||
timeout: 5000
|
|
||||||
});
|
|
||||||
```
|
|
||||||
* **返回值 `ResponseObject` 结构**:
|
|
||||||
* `ok` (Boolean): 状态码是否在 200-299。
|
|
||||||
* `status` (Number): HTTP 状态码。
|
|
||||||
* `headers` (Object): 响应头键值对。
|
|
||||||
* `responseType` (String): `'json' | 'binary' | 'text'`。
|
|
||||||
* `result` (Any): 成功后的解析结果。
|
|
||||||
* `error` (String | null): 失败的错误描述。
|
|
||||||
* **数据自动转换**:若 `data` 内包含 `File`/`Blob`,自动转为 `FormData` 且清除 Content-Type;普通对象自动序列化为 JSON 并添加 `application/json`。
|
|
||||||
|
|
||||||
### 3.2 交互工具 (`UI`)
|
|
||||||
* **`UI.showDialog({ title, message, buttons, type })`**
|
|
||||||
* **返回值**:`Promise<Number>`。点击按钮返回其索引值 (从 `1` 开始),点击关闭或取消返回 `0`。
|
|
||||||
* **参数**:`type` 可以为 `'primary'|'danger'|'warning'|'success'` 控制主题色;`message` 支持 HTML。
|
|
||||||
* **`UI.alert(message, options)`**
|
|
||||||
* **返回值**:`Promise<Boolean>`(点击关闭按钮返回 `false`)。
|
|
||||||
* **`UI.confirm(message, options)`**
|
|
||||||
* **返回值**:`Promise<Boolean>`。点击“确认”返回 `true`,点击“取消”或关闭返回 `false`。
|
|
||||||
* **`UI.toast(message, options)`**
|
|
||||||
* **返回值**:`void`。`options.delay` 为自动消失延迟毫秒(`0` 代表不消失)。可以通过传入 `buttons: ['选项1']` 按钮提供交互,点击后在实例 `result` 上保存索引。
|
|
||||||
* **`UI.toastConfirm(message, options)`**
|
|
||||||
* **返回值**:`Promise<Boolean>`。在 Toast 中提供单确认按钮,点击确认返回 `true`,否则返回 `false`。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 开发红线 (Constraints)
|
|
||||||
|
|
||||||
1. **表单数据操作红线**:严禁直接覆盖表单的 `state.data` 或 `data` 对象(如 `form.data = {}`)。这会切断与内部 Proxy 的响应式链路。必须使用 `Object.assign(form.state.data, newData)`。
|
|
||||||
2. **列表布局红线**:开启虚拟滚动(`fast`)时,容器必须包含 `overflow-auto` 类。
|
|
||||||
3. **指令 DOM 保护**:严禁使用原生 DOM API 直接修改由 `$each`、`$if` 或组件渲染指令生成的 DOM 节点。所有 DOM 状态的变化应当完全通过修改与之绑定的底层 `State` 属性驱动。
|
|
||||||
4. **退出拦截约束**:在全局配置有 `State.exitBlocks > 0` 时,框架将强行拦截并警告任何刷新/关闭页面的行为。
|
|
||||||
|
|||||||
1425
dist/base.js
vendored
1425
dist/base.js
vendored
File diff suppressed because one or more lines are too long
2
dist/base.min.js
vendored
2
dist/base.min.js
vendored
File diff suppressed because one or more lines are too long
1
dist/base.min.mjs
vendored
1
dist/base.min.mjs
vendored
File diff suppressed because one or more lines are too long
920
dist/base.mjs
vendored
920
dist/base.mjs
vendored
File diff suppressed because one or more lines are too long
924
node_modules/.package-lock.json
generated
vendored
924
node_modules/.package-lock.json
generated
vendored
@ -1,464 +1,464 @@
|
|||||||
{
|
{
|
||||||
"name": "@apigo.cc/base",
|
"name": "@web/base",
|
||||||
"version": "1.0.7",
|
"version": "1.0.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"node_modules/@esbuild/darwin-x64": {
|
"node_modules/@esbuild/darwin-x64": {
|
||||||
"version": "0.21.5",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
|
||||||
"integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
|
"integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/gen-mapping": {
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
"version": "0.3.13",
|
"version": "0.3.13",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||||
"@jridgewell/trace-mapping": "^0.3.24"
|
"@jridgewell/trace-mapping": "^0.3.24"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/resolve-uri": {
|
"node_modules/@jridgewell/resolve-uri": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/source-map": {
|
"node_modules/@jridgewell/source-map": {
|
||||||
"version": "0.3.11",
|
"version": "0.3.11",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
|
||||||
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
|
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/gen-mapping": "^0.3.5",
|
"@jridgewell/gen-mapping": "^0.3.5",
|
||||||
"@jridgewell/trace-mapping": "^0.3.25"
|
"@jridgewell/trace-mapping": "^0.3.25"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/sourcemap-codec": {
|
"node_modules/@jridgewell/sourcemap-codec": {
|
||||||
"version": "1.5.5",
|
"version": "1.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/trace-mapping": {
|
"node_modules/@jridgewell/trace-mapping": {
|
||||||
"version": "0.3.31",
|
"version": "0.3.31",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||||
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/resolve-uri": "^3.1.0",
|
"@jridgewell/resolve-uri": "^3.1.0",
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@playwright/test": {
|
"node_modules/@playwright/test": {
|
||||||
"version": "1.60.0",
|
"version": "1.60.0",
|
||||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz",
|
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz",
|
||||||
"integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==",
|
"integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright": "1.60.0"
|
"playwright": "1.60.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js"
|
"playwright": "cli.js"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/plugin-terser": {
|
"node_modules/@rollup/plugin-terser": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-1.0.0.tgz",
|
||||||
"integrity": "sha512-FnCxhTBx6bMOYQrar6C8h3scPt8/JwIzw3+AJ2K++6guogH5fYaIFia+zZuhqv0eo1RN7W1Pz630SyvLbDjhtQ==",
|
"integrity": "sha512-FnCxhTBx6bMOYQrar6C8h3scPt8/JwIzw3+AJ2K++6guogH5fYaIFia+zZuhqv0eo1RN7W1Pz630SyvLbDjhtQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"serialize-javascript": "^7.0.3",
|
"serialize-javascript": "^7.0.3",
|
||||||
"smob": "^1.0.0",
|
"smob": "^1.0.0",
|
||||||
"terser": "^5.17.4"
|
"terser": "^5.17.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.0.0"
|
"node": ">=20.0.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"rollup": "^2.0.0||^3.0.0||^4.0.0"
|
"rollup": "^2.0.0||^3.0.0||^4.0.0"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"rollup": {
|
"rollup": {
|
||||||
"optional": true
|
"optional": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-x64": {
|
"node_modules/@rollup/rollup-darwin-x64": {
|
||||||
"version": "4.60.3",
|
"version": "4.60.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.3.tgz",
|
||||||
"integrity": "sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==",
|
"integrity": "sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@types/estree": {
|
"node_modules/@types/estree": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/acorn": {
|
"node_modules/acorn": {
|
||||||
"version": "8.16.0",
|
"version": "8.16.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
|
||||||
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/buffer-from": {
|
"node_modules/buffer-from": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/commander": {
|
"node_modules/commander": {
|
||||||
"version": "2.20.3",
|
"version": "2.20.3",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.21.5",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
|
||||||
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
|
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"esbuild": "bin/esbuild"
|
"esbuild": "bin/esbuild"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@esbuild/aix-ppc64": "0.21.5",
|
"@esbuild/aix-ppc64": "0.21.5",
|
||||||
"@esbuild/android-arm": "0.21.5",
|
"@esbuild/android-arm": "0.21.5",
|
||||||
"@esbuild/android-arm64": "0.21.5",
|
"@esbuild/android-arm64": "0.21.5",
|
||||||
"@esbuild/android-x64": "0.21.5",
|
"@esbuild/android-x64": "0.21.5",
|
||||||
"@esbuild/darwin-arm64": "0.21.5",
|
"@esbuild/darwin-arm64": "0.21.5",
|
||||||
"@esbuild/darwin-x64": "0.21.5",
|
"@esbuild/darwin-x64": "0.21.5",
|
||||||
"@esbuild/freebsd-arm64": "0.21.5",
|
"@esbuild/freebsd-arm64": "0.21.5",
|
||||||
"@esbuild/freebsd-x64": "0.21.5",
|
"@esbuild/freebsd-x64": "0.21.5",
|
||||||
"@esbuild/linux-arm": "0.21.5",
|
"@esbuild/linux-arm": "0.21.5",
|
||||||
"@esbuild/linux-arm64": "0.21.5",
|
"@esbuild/linux-arm64": "0.21.5",
|
||||||
"@esbuild/linux-ia32": "0.21.5",
|
"@esbuild/linux-ia32": "0.21.5",
|
||||||
"@esbuild/linux-loong64": "0.21.5",
|
"@esbuild/linux-loong64": "0.21.5",
|
||||||
"@esbuild/linux-mips64el": "0.21.5",
|
"@esbuild/linux-mips64el": "0.21.5",
|
||||||
"@esbuild/linux-ppc64": "0.21.5",
|
"@esbuild/linux-ppc64": "0.21.5",
|
||||||
"@esbuild/linux-riscv64": "0.21.5",
|
"@esbuild/linux-riscv64": "0.21.5",
|
||||||
"@esbuild/linux-s390x": "0.21.5",
|
"@esbuild/linux-s390x": "0.21.5",
|
||||||
"@esbuild/linux-x64": "0.21.5",
|
"@esbuild/linux-x64": "0.21.5",
|
||||||
"@esbuild/netbsd-x64": "0.21.5",
|
"@esbuild/netbsd-x64": "0.21.5",
|
||||||
"@esbuild/openbsd-x64": "0.21.5",
|
"@esbuild/openbsd-x64": "0.21.5",
|
||||||
"@esbuild/sunos-x64": "0.21.5",
|
"@esbuild/sunos-x64": "0.21.5",
|
||||||
"@esbuild/win32-arm64": "0.21.5",
|
"@esbuild/win32-arm64": "0.21.5",
|
||||||
"@esbuild/win32-ia32": "0.21.5",
|
"@esbuild/win32-ia32": "0.21.5",
|
||||||
"@esbuild/win32-x64": "0.21.5"
|
"@esbuild/win32-x64": "0.21.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.2",
|
"version": "2.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
"version": "3.3.12",
|
"version": "3.3.12",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
|
||||||
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
|
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/ai"
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"bin": {
|
"bin": {
|
||||||
"nanoid": "bin/nanoid.cjs"
|
"nanoid": "bin/nanoid.cjs"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/playwright": {
|
"node_modules/playwright": {
|
||||||
"version": "1.60.0",
|
"version": "1.60.0",
|
||||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz",
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz",
|
||||||
"integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==",
|
"integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.60.0"
|
"playwright-core": "1.60.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js"
|
"playwright": "cli.js"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"fsevents": "2.3.2"
|
"fsevents": "2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/playwright-core": {
|
"node_modules/playwright-core": {
|
||||||
"version": "1.60.0",
|
"version": "1.60.0",
|
||||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz",
|
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz",
|
||||||
"integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==",
|
"integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright-core": "cli.js"
|
"playwright-core": "cli.js"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.14",
|
"version": "8.5.14",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
|
||||||
"integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
|
"integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
"url": "https://opencollective.com/postcss/"
|
"url": "https://opencollective.com/postcss/"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "tidelift",
|
"type": "tidelift",
|
||||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/ai"
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.11",
|
"nanoid": "^3.3.11",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
"source-map-js": "^1.2.1"
|
"source-map-js": "^1.2.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.60.3",
|
"version": "4.60.3",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.3.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.3.tgz",
|
||||||
"integrity": "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==",
|
"integrity": "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree": "1.0.8"
|
"@types/estree": "1.0.8"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"rollup": "dist/bin/rollup"
|
"rollup": "dist/bin/rollup"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0",
|
"node": ">=18.0.0",
|
||||||
"npm": ">=8.0.0"
|
"npm": ">=8.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rollup/rollup-android-arm-eabi": "4.60.3",
|
"@rollup/rollup-android-arm-eabi": "4.60.3",
|
||||||
"@rollup/rollup-android-arm64": "4.60.3",
|
"@rollup/rollup-android-arm64": "4.60.3",
|
||||||
"@rollup/rollup-darwin-arm64": "4.60.3",
|
"@rollup/rollup-darwin-arm64": "4.60.3",
|
||||||
"@rollup/rollup-darwin-x64": "4.60.3",
|
"@rollup/rollup-darwin-x64": "4.60.3",
|
||||||
"@rollup/rollup-freebsd-arm64": "4.60.3",
|
"@rollup/rollup-freebsd-arm64": "4.60.3",
|
||||||
"@rollup/rollup-freebsd-x64": "4.60.3",
|
"@rollup/rollup-freebsd-x64": "4.60.3",
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": "4.60.3",
|
"@rollup/rollup-linux-arm-gnueabihf": "4.60.3",
|
||||||
"@rollup/rollup-linux-arm-musleabihf": "4.60.3",
|
"@rollup/rollup-linux-arm-musleabihf": "4.60.3",
|
||||||
"@rollup/rollup-linux-arm64-gnu": "4.60.3",
|
"@rollup/rollup-linux-arm64-gnu": "4.60.3",
|
||||||
"@rollup/rollup-linux-arm64-musl": "4.60.3",
|
"@rollup/rollup-linux-arm64-musl": "4.60.3",
|
||||||
"@rollup/rollup-linux-loong64-gnu": "4.60.3",
|
"@rollup/rollup-linux-loong64-gnu": "4.60.3",
|
||||||
"@rollup/rollup-linux-loong64-musl": "4.60.3",
|
"@rollup/rollup-linux-loong64-musl": "4.60.3",
|
||||||
"@rollup/rollup-linux-ppc64-gnu": "4.60.3",
|
"@rollup/rollup-linux-ppc64-gnu": "4.60.3",
|
||||||
"@rollup/rollup-linux-ppc64-musl": "4.60.3",
|
"@rollup/rollup-linux-ppc64-musl": "4.60.3",
|
||||||
"@rollup/rollup-linux-riscv64-gnu": "4.60.3",
|
"@rollup/rollup-linux-riscv64-gnu": "4.60.3",
|
||||||
"@rollup/rollup-linux-riscv64-musl": "4.60.3",
|
"@rollup/rollup-linux-riscv64-musl": "4.60.3",
|
||||||
"@rollup/rollup-linux-s390x-gnu": "4.60.3",
|
"@rollup/rollup-linux-s390x-gnu": "4.60.3",
|
||||||
"@rollup/rollup-linux-x64-gnu": "4.60.3",
|
"@rollup/rollup-linux-x64-gnu": "4.60.3",
|
||||||
"@rollup/rollup-linux-x64-musl": "4.60.3",
|
"@rollup/rollup-linux-x64-musl": "4.60.3",
|
||||||
"@rollup/rollup-openbsd-x64": "4.60.3",
|
"@rollup/rollup-openbsd-x64": "4.60.3",
|
||||||
"@rollup/rollup-openharmony-arm64": "4.60.3",
|
"@rollup/rollup-openharmony-arm64": "4.60.3",
|
||||||
"@rollup/rollup-win32-arm64-msvc": "4.60.3",
|
"@rollup/rollup-win32-arm64-msvc": "4.60.3",
|
||||||
"@rollup/rollup-win32-ia32-msvc": "4.60.3",
|
"@rollup/rollup-win32-ia32-msvc": "4.60.3",
|
||||||
"@rollup/rollup-win32-x64-gnu": "4.60.3",
|
"@rollup/rollup-win32-x64-gnu": "4.60.3",
|
||||||
"@rollup/rollup-win32-x64-msvc": "4.60.3",
|
"@rollup/rollup-win32-x64-msvc": "4.60.3",
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/serialize-javascript": {
|
"node_modules/serialize-javascript": {
|
||||||
"version": "7.0.5",
|
"version": "7.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz",
|
||||||
"integrity": "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==",
|
"integrity": "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.0.0"
|
"node": ">=20.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/smob": {
|
"node_modules/smob": {
|
||||||
"version": "1.6.1",
|
"version": "1.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/smob/-/smob-1.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/smob/-/smob-1.6.1.tgz",
|
||||||
"integrity": "sha512-KAkBqZl3c2GvNgNhcoyJae1aKldDW0LO279wF9bk1PnluRTETKBq0WyzRXxEhoQLk56yHaOY4JCBEKDuJIET5g==",
|
"integrity": "sha512-KAkBqZl3c2GvNgNhcoyJae1aKldDW0LO279wF9bk1PnluRTETKBq0WyzRXxEhoQLk56yHaOY4JCBEKDuJIET5g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.0.0"
|
"node": ">=20.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/source-map": {
|
"node_modules/source-map": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/source-map-support": {
|
"node_modules/source-map-support": {
|
||||||
"version": "0.5.21",
|
"version": "0.5.21",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||||
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"buffer-from": "^1.0.0",
|
"buffer-from": "^1.0.0",
|
||||||
"source-map": "^0.6.0"
|
"source-map": "^0.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/terser": {
|
"node_modules/terser": {
|
||||||
"version": "5.47.1",
|
"version": "5.47.1",
|
||||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.47.1.tgz",
|
"resolved": "https://registry.npmjs.org/terser/-/terser-5.47.1.tgz",
|
||||||
"integrity": "sha512-tPbLXTI6ohPASb/1YViL428oEHu6/qv1OxqYnfaonVCFHqx4+wCd95pHrQWsL5X4pl90CTyW9piSAsS2L0VoMw==",
|
"integrity": "sha512-tPbLXTI6ohPASb/1YViL428oEHu6/qv1OxqYnfaonVCFHqx4+wCd95pHrQWsL5X4pl90CTyW9piSAsS2L0VoMw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/source-map": "^0.3.3",
|
"@jridgewell/source-map": "^0.3.3",
|
||||||
"acorn": "^8.15.0",
|
"acorn": "^8.15.0",
|
||||||
"commander": "^2.20.0",
|
"commander": "^2.20.0",
|
||||||
"source-map-support": "~0.5.20"
|
"source-map-support": "~0.5.20"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"terser": "bin/terser"
|
"terser": "bin/terser"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "5.4.21",
|
"version": "5.4.21",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
|
||||||
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.21.3",
|
"esbuild": "^0.21.3",
|
||||||
"postcss": "^8.4.43",
|
"postcss": "^8.4.43",
|
||||||
"rollup": "^4.20.0"
|
"rollup": "^4.20.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"vite": "bin/vite.js"
|
"vite": "bin/vite.js"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.0.0 || >=20.0.0"
|
"node": "^18.0.0 || >=20.0.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/vitejs/vite?sponsor=1"
|
"url": "https://github.com/vitejs/vite?sponsor=1"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"fsevents": "~2.3.3"
|
"fsevents": "~2.3.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/node": "^18.0.0 || >=20.0.0",
|
"@types/node": "^18.0.0 || >=20.0.0",
|
||||||
"less": "*",
|
"less": "*",
|
||||||
"lightningcss": "^1.21.0",
|
"lightningcss": "^1.21.0",
|
||||||
"sass": "*",
|
"sass": "*",
|
||||||
"sass-embedded": "*",
|
"sass-embedded": "*",
|
||||||
"stylus": "*",
|
"stylus": "*",
|
||||||
"sugarss": "*",
|
"sugarss": "*",
|
||||||
"terser": "^5.4.0"
|
"terser": "^5.4.0"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"less": {
|
"less": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"lightningcss": {
|
"lightningcss": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"sass": {
|
"sass": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"sass-embedded": {
|
"sass-embedded": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"stylus": {
|
"stylus": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"sugarss": {
|
"sugarss": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"terser": {
|
"terser": {
|
||||||
"optional": true
|
"optional": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/fsevents": {
|
"node_modules/vite/node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
17
node_modules/.vite/deps/_metadata.json
generated
vendored
17
node_modules/.vite/deps/_metadata.json
generated
vendored
@ -1,15 +1,8 @@
|
|||||||
{
|
{
|
||||||
"hash": "945ec9e8",
|
"hash": "5a59143b",
|
||||||
"configHash": "6c629749",
|
"configHash": "a1c4c8e0",
|
||||||
"lockfileHash": "463a0a64",
|
"lockfileHash": "6014e7a8",
|
||||||
"browserHash": "38e755dd",
|
"browserHash": "c3b48a46",
|
||||||
"optimized": {
|
"optimized": {},
|
||||||
"bootstrap": {
|
|
||||||
"src": "../../../../bootstrap/node_modules/bootstrap/dist/js/bootstrap.esm.js",
|
|
||||||
"file": "bootstrap.js",
|
|
||||||
"fileHash": "b9d6a4a2",
|
|
||||||
"needsInterop": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"chunks": {}
|
"chunks": {}
|
||||||
}
|
}
|
||||||
5188
node_modules/.vite/deps/bootstrap.js
generated
vendored
5188
node_modules/.vite/deps/bootstrap.js
generated
vendored
File diff suppressed because it is too large
Load Diff
7
node_modules/.vite/deps/bootstrap.js.map
generated
vendored
7
node_modules/.vite/deps/bootstrap.js.map
generated
vendored
File diff suppressed because one or more lines are too long
2272
package-lock.json
generated
2272
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
39
package.json
39
package.json
@ -1,22 +1,21 @@
|
|||||||
{
|
{
|
||||||
"name": "@apigo.cc/base",
|
"name": "@web/base",
|
||||||
"version": "1.0.10",
|
"version": "1.0.7",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/base.js",
|
"main": "dist/base.js",
|
||||||
"module": "dist/base.js",
|
"module": "dist/base.js",
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"test": "playwright test",
|
"test": "playwright test"
|
||||||
"pub": "node scripts/publish.js"
|
},
|
||||||
},
|
"devDependencies": {
|
||||||
"devDependencies": {
|
"@playwright/test": "^1.40.0",
|
||||||
"@playwright/test": "^1.40.0",
|
"@rollup/plugin-terser": "^1.0.0",
|
||||||
"@rollup/plugin-terser": "^1.0.0",
|
"terser": "^5.47.1",
|
||||||
"terser": "^5.47.1",
|
"vite": "^5.0.0"
|
||||||
"vite": "^5.0.0"
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,48 +0,0 @@
|
|||||||
import { execSync } from 'child_process';
|
|
||||||
import fs from 'fs';
|
|
||||||
import path from 'path';
|
|
||||||
import { fileURLToPath } from 'url';
|
|
||||||
|
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 1. 获取最新 tag
|
|
||||||
let tag;
|
|
||||||
try {
|
|
||||||
tag = execSync('git describe --tags --abbrev=0', { encoding: 'utf8' }).trim();
|
|
||||||
} catch (err) {
|
|
||||||
throw new Error('Failed to find git tags. Please make sure the repository has tags (e.g., v1.0.0) before publishing.');
|
|
||||||
}
|
|
||||||
// 去掉 v 前缀
|
|
||||||
const version = tag.startsWith('v') ? tag.slice(1) : tag;
|
|
||||||
|
|
||||||
console.log(`Latest git tag: ${tag}, Version to publish: ${version}`);
|
|
||||||
|
|
||||||
// 2. 读取并更新 package.json
|
|
||||||
const pkgPath = path.join(__dirname, '../package.json');
|
|
||||||
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
||||||
|
|
||||||
// 保持原有名称(如果已经带有 @apigo.cc/ 前缀)或替换前缀
|
|
||||||
if (!pkg.name.startsWith('@apigo.cc/')) {
|
|
||||||
const baseName = pkg.name.includes('/') ? pkg.name.split('/')[1] : pkg.name;
|
|
||||||
pkg.name = `@apigo.cc/${baseName}`;
|
|
||||||
}
|
|
||||||
pkg.version = version;
|
|
||||||
|
|
||||||
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
||||||
console.log(`Updated package.json: name=${pkg.name}, version=${pkg.version}`);
|
|
||||||
|
|
||||||
// 3. 构建
|
|
||||||
console.log('Running build...');
|
|
||||||
execSync('npm run build', { stdio: 'inherit', cwd: path.join(__dirname, '..') });
|
|
||||||
|
|
||||||
// 4. 发布
|
|
||||||
console.log('Publishing to npm...');
|
|
||||||
const args = process.argv.slice(2).join(' ');
|
|
||||||
execSync(`npm publish --access public ${args}`, { stdio: 'inherit', cwd: path.join(__dirname, '..') });
|
|
||||||
|
|
||||||
console.log('Publish successful!');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Publish failed:', error.message);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
2
src/controls.js
vendored
2
src/controls.js
vendored
@ -1,4 +1,4 @@
|
|||||||
import { Component, NewState, Util, $ } from '@apigo.cc/state'
|
import { Component, NewState, Util, $ } from '@web/state'
|
||||||
import { AutoForm } from './form.js'
|
import { AutoForm } from './form.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
16
src/form.js
16
src/form.js
@ -1,4 +1,4 @@
|
|||||||
import { Component, NewState, Util, $ } from '@apigo.cc/state'
|
import { Component, NewState, Util, $ } from '@web/state'
|
||||||
import { HTTP } from './http.js'
|
import { HTTP } from './http.js'
|
||||||
|
|
||||||
Component.register('AutoForm', container => {
|
Component.register('AutoForm', container => {
|
||||||
@ -80,15 +80,13 @@ Component.register('AutoForm', container => {
|
|||||||
|
|
||||||
const _pendingAutoFormComponents = []
|
const _pendingAutoFormComponents = []
|
||||||
export const AutoForm = {
|
export const AutoForm = {
|
||||||
register: (name, typeName) => {
|
register: name => {
|
||||||
const entry = { name, typeName: typeName || name }
|
|
||||||
if (typeof document !== 'undefined') {
|
if (typeof document !== 'undefined') {
|
||||||
if (document.readyState !== 'loading' && Component.getTemplate('AutoForm')) AutoForm._addAutoFormComponent(entry)
|
if (document.readyState !== 'loading' && Component.getTemplate('AutoForm')) AutoForm._addAutoFormComponent(name)
|
||||||
else _pendingAutoFormComponents.push(entry)
|
else _pendingAutoFormComponents.push(name)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_addAutoFormComponent: entry => {
|
_addAutoFormComponent: name => {
|
||||||
const { name, typeName } = entry
|
|
||||||
const template = Component.getTemplate('AutoForm')
|
const template = Component.getTemplate('AutoForm')
|
||||||
if (template) {
|
if (template) {
|
||||||
let container = $(template.content, '[control-wrapper]')
|
let container = $(template.content, '[control-wrapper]')
|
||||||
@ -97,7 +95,7 @@ export const AutoForm = {
|
|||||||
if (nested) container = $(nested.content, '[control-wrapper]')
|
if (nested) container = $(nested.content, '[control-wrapper]')
|
||||||
}
|
}
|
||||||
if (container && !container.querySelector(name)) {
|
if (container && !container.querySelector(name)) {
|
||||||
container.appendChild(Util.makeDom(`<${name} $if="item.type?.toLowerCase() === '${typeName.toLowerCase()}'" $name="item.name" $.="item.setting || {}" $bind="thisNode.closest('AutoForm').data[item.name]"></${name}>`))
|
container.appendChild(Util.makeDom(`<${name} $if="item.type?.toLowerCase() === '${name.toLowerCase()}'" $name="item.name" $.="item.setting || {}" $bind="thisNode.closest('AutoForm').data[item.name]"></${name}>`))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,7 +104,7 @@ export const AutoForm = {
|
|||||||
|
|
||||||
if (typeof document !== 'undefined') {
|
if (typeof document !== 'undefined') {
|
||||||
const initAutoForm = () => {
|
const initAutoForm = () => {
|
||||||
_pendingAutoFormComponents.forEach(entry => AutoForm._addAutoFormComponent(entry))
|
_pendingAutoFormComponents.forEach(name => AutoForm._addAutoFormComponent(name))
|
||||||
_pendingAutoFormComponents.length = 0
|
_pendingAutoFormComponents.length = 0
|
||||||
}
|
}
|
||||||
if (document.readyState !== 'loading') setTimeout(initAutoForm, 100)
|
if (document.readyState !== 'loading') setTimeout(initAutoForm, 100)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Component, NewState } from '@apigo.cc/state'
|
import { Component, NewState } from '@web/state'
|
||||||
|
|
||||||
export const HTTP = {
|
export const HTTP = {
|
||||||
get: ({ url, ...opt }) => HTTP.request({ url, method: 'GET', ...opt }),
|
get: ({ url, ...opt }) => HTTP.request({ url, method: 'GET', ...opt }),
|
||||||
|
|||||||
21
src/index.js
21
src/index.js
@ -1,5 +1,4 @@
|
|||||||
import { NewState, State } from '@apigo.cc/state'
|
import { NewState } from '@web/state'
|
||||||
import '@apigo.cc/bootstrap'
|
|
||||||
|
|
||||||
// Re-exports
|
// Re-exports
|
||||||
export * from './http.js'
|
export * from './http.js'
|
||||||
@ -10,7 +9,9 @@ export * from './list.js'
|
|||||||
export * from './nav.js'
|
export * from './nav.js'
|
||||||
export * from './interaction.js'
|
export * from './interaction.js'
|
||||||
|
|
||||||
export { State }
|
// 页面退出状态
|
||||||
|
export const State = NewState({ exitBlocks: 0 })
|
||||||
|
globalThis.State = State
|
||||||
|
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
window.addEventListener('beforeunload', (event) => {
|
window.addEventListener('beforeunload', (event) => {
|
||||||
@ -28,24 +29,16 @@ import { HTTP } from './http.js'
|
|||||||
import { UI } from './ui.js'
|
import { UI } from './ui.js'
|
||||||
import { AutoForm } from './form.js'
|
import { AutoForm } from './form.js'
|
||||||
import { MouseMover } from './interaction.js'
|
import { MouseMover } from './interaction.js'
|
||||||
import { VirtualScroll } from './list.js'
|
|
||||||
|
|
||||||
globalThis.HTTP = HTTP
|
globalThis.HTTP = HTTP
|
||||||
globalThis.UI = UI
|
globalThis.UI = UI
|
||||||
globalThis.AutoForm = AutoForm
|
globalThis.AutoForm = AutoForm
|
||||||
globalThis.MouseMover = MouseMover
|
globalThis.MouseMover = MouseMover
|
||||||
globalThis.VirtualScroll = VirtualScroll
|
|
||||||
|
|
||||||
const ApigoBase = {
|
import { RefreshState } from '@web/state'
|
||||||
HTTP, UI, AutoForm, MouseMover, VirtualScroll, State
|
|
||||||
};
|
|
||||||
|
|
||||||
import { RefreshState } from '@apigo.cc/state'
|
|
||||||
if (typeof document !== 'undefined') {
|
if (typeof document !== 'undefined') {
|
||||||
globalThis.ApigoBase = ApigoBase;
|
|
||||||
|
|
||||||
const doRefresh = () => RefreshState(document.documentElement)
|
const doRefresh = () => RefreshState(document.documentElement)
|
||||||
if (document.readyState !== 'loading') setTimeout(doRefresh, 1)
|
if (document.readyState !== 'loading') doRefresh()
|
||||||
else document.addEventListener('DOMContentLoaded', () => setTimeout(doRefresh, 1), true)
|
else document.addEventListener('DOMContentLoaded', doRefresh, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Component, Util } from '@apigo.cc/state'
|
import { Component, Util } from '@web/state'
|
||||||
|
|
||||||
let _mouseMoverMoving = false
|
let _mouseMoverMoving = false
|
||||||
let _mouseMoverPos = {}
|
let _mouseMoverPos = {}
|
||||||
|
|||||||
46
src/list.js
46
src/list.js
@ -1,4 +1,4 @@
|
|||||||
import { Component, NewState, Util, Hash } from '@apigo.cc/state'
|
import { Component, NewState, Util, Hash } from '@web/state'
|
||||||
|
|
||||||
export const VirtualScroll = (options = {}) => {
|
export const VirtualScroll = (options = {}) => {
|
||||||
const itemHeights = new Map()
|
const itemHeights = new Map()
|
||||||
@ -47,7 +47,6 @@ export const VirtualScroll = (options = {}) => {
|
|||||||
listInited = true; refreshCallback();
|
listInited = true; refreshCallback();
|
||||||
},
|
},
|
||||||
update: (absoluteIndex, node) => {
|
update: (absoluteIndex, node) => {
|
||||||
if (node.offsetHeight === 0) return;
|
|
||||||
if (itemMarginTop === null) {
|
if (itemMarginTop === null) {
|
||||||
const style = window.getComputedStyle(node);
|
const style = window.getComputedStyle(node);
|
||||||
itemMarginTop = parseFloat(style.marginTop) || 0; itemMarginBottom = parseFloat(style.marginBottom) || 0;
|
itemMarginTop = parseFloat(style.marginTop) || 0; itemMarginBottom = parseFloat(style.marginBottom) || 0;
|
||||||
@ -64,17 +63,12 @@ export const VirtualScroll = (options = {}) => {
|
|||||||
calc: (container, list) => {
|
calc: (container, list) => {
|
||||||
if (!listInited || !list) return null;
|
if (!listInited || !list) return null;
|
||||||
const size = list.length;
|
const size = list.length;
|
||||||
const avgVal = Math.max(16, avg.get() || 32);
|
let visibleCount = Math.max(10, Math.ceil((container.clientHeight || 100) / (avg.get() || 32)));
|
||||||
let visibleCount = Math.max(10, Math.ceil((container.clientHeight || 100) / avgVal));
|
|
||||||
let prev = padTop + topMargin + rowGap, post = 0, status = 0, listStartIndex = 0, listEndIndex = 0;
|
let prev = padTop + topMargin + rowGap, post = 0, status = 0, listStartIndex = 0, listEndIndex = 0;
|
||||||
let renderedList = [];
|
let renderedList = [];
|
||||||
const scrollTop = container.scrollTop;
|
const scrollTop = container.scrollTop;
|
||||||
let loopCount = 0;
|
|
||||||
|
|
||||||
for (let i = 0; i < size; i++) {
|
for (let i = 0; i < size; i++) {
|
||||||
if (++loopCount > size * 2) {
|
|
||||||
throw new Error(`VirtualScroll.calc infinite loop detected at i=${i}, status=${status}, size=${size}, groupItemCount=${groupItemCount}`);
|
|
||||||
}
|
|
||||||
if (status === 0) {
|
if (status === 0) {
|
||||||
const gh = groupHeights.get(i);
|
const gh = groupHeights.get(i);
|
||||||
if (gh && prev + gh <= scrollTop && (i + groupItemCount < size)) {
|
if (gh && prev + gh <= scrollTop && (i + groupItemCount < size)) {
|
||||||
@ -120,9 +114,6 @@ Component.register('List', container => {
|
|||||||
container.fast = container.hasAttribute('fast')
|
container.fast = container.hasAttribute('fast')
|
||||||
container.collapsible = container.hasAttribute('collapsible')
|
container.collapsible = container.hasAttribute('collapsible')
|
||||||
|
|
||||||
const padTopEl = container.fast ? container.querySelector('.vs-pad-top') : null
|
|
||||||
const padBottomEl = container.fast ? container.querySelector('.vs-pad-bottom') : null
|
|
||||||
|
|
||||||
const defaultSets = {
|
const defaultSets = {
|
||||||
idfield: 'id', labelfield: 'label', summaryfield: 'summary',
|
idfield: 'id', labelfield: 'label', summaryfield: 'summary',
|
||||||
groupidfield: 'id', grouplabelfield: 'label', groupsummaryfield: 'summary', groupfield: 'group',
|
groupidfield: 'id', grouplabelfield: 'label', groupsummaryfield: 'summary', groupfield: 'group',
|
||||||
@ -159,27 +150,14 @@ Component.register('List', container => {
|
|||||||
const vs = container.fast ? VirtualScroll() : null
|
const vs = container.fast ? VirtualScroll() : null
|
||||||
container.state._renderedList = []
|
container.state._renderedList = []
|
||||||
|
|
||||||
let refreshing = false
|
|
||||||
container.refresh = () => {
|
container.refresh = () => {
|
||||||
if (!container.fast || refreshing) return
|
if (!container.fast) return
|
||||||
refreshing = true
|
const res = vs.calc(container, container.state._flatList)
|
||||||
try {
|
if (res) {
|
||||||
const res = vs.calc(container, container.state._flatList)
|
container.state.prevHeight = res.prevHeight
|
||||||
if (res) {
|
container.state.postHeight = res.postHeight
|
||||||
if (padTopEl) padTopEl.style.height = `${res.prevHeight}px`
|
container.state._listStartIndex = res.listStartIndex
|
||||||
if (padBottomEl) padBottomEl.style.height = `${res.postHeight}px`
|
container.state._renderedList = res.renderedList
|
||||||
|
|
||||||
if (container.state._listStartIndex !== res.listStartIndex) {
|
|
||||||
container.state._listStartIndex = res.listStartIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
const cur = container.state._renderedList || []
|
|
||||||
if (cur.length !== res.renderedList.length || cur[0] !== res.renderedList[0] || cur[cur.length - 1] !== res.renderedList[res.renderedList.length - 1]) {
|
|
||||||
container.state._renderedList = res.renderedList
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
setTimeout(() => { refreshing = false }, 0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,8 +165,6 @@ Component.register('List', container => {
|
|||||||
|
|
||||||
container.state.__watch('_flatList', flatList => {
|
container.state.__watch('_flatList', flatList => {
|
||||||
if (container.fast) {
|
if (container.fast) {
|
||||||
if (padTopEl) padTopEl.style.height = '0px'
|
|
||||||
if (padBottomEl) padBottomEl.style.height = '0px'
|
|
||||||
container.state._listStartIndex = 0
|
container.state._listStartIndex = 0
|
||||||
container.state._renderedList = vs.reset(flatList, container) || []
|
container.state._renderedList = vs.reset(flatList, container) || []
|
||||||
setTimeout(() => { if (container.state._flatList === flatList) vs.init(flatList, container.refresh) })
|
setTimeout(() => { if (container.state._flatList === flatList) vs.init(flatList, container.refresh) })
|
||||||
@ -208,7 +184,7 @@ Component.register('List', container => {
|
|||||||
updateFlatList()
|
updateFlatList()
|
||||||
}, Util.makeDom(/*html*/`
|
}, Util.makeDom(/*html*/`
|
||||||
<div class="list-group overflow-auto" onscroll="this.refresh()" style="overflow-anchor:none">
|
<div class="list-group overflow-auto" onscroll="this.refresh()" style="overflow-anchor:none">
|
||||||
<div class="vs-pad-top flex-shrink-0" style="height:0px;"></div>
|
<div $if="this.fast && (this.state?.prevHeight || 0) > 0" $style="height:\${this.state?.prevHeight}px;" class="flex-shrink-0"></div>
|
||||||
<template slot-id="item" $each="this.state?._renderedList">
|
<template slot-id="item" $each="this.state?._renderedList">
|
||||||
<div $onupdate="this.onItemUpdate(index, thisNode)" $class="list-group-item list-group-item-action d-inline-flex align-items-center ps-2 pe-2 \${item.type==='group'?(this.state?.selectedGroup===item[this.groupidfield]?'active':''):(this.state?.selectedItem===item[this.idfield]?'active':'')}" $onclick="item.type==='group'?this.selectGroup(item,index):this.selectItem(item,index)">
|
<div $onupdate="this.onItemUpdate(index, thisNode)" $class="list-group-item list-group-item-action d-inline-flex align-items-center ps-2 pe-2 \${item.type==='group'?(this.state?.selectedGroup===item[this.groupidfield]?'active':''):(this.state?.selectedItem===item[this.idfield]?'active':'')}" $onclick="item.type==='group'?this.selectGroup(item,index):this.selectItem(item,index)">
|
||||||
<template $if="item.type === 'group'">
|
<template $if="item.type === 'group'">
|
||||||
@ -229,6 +205,6 @@ Component.register('List', container => {
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="vs-pad-bottom flex-shrink-0" style="height:0px;"></div>
|
<div $if="this.fast && (this.state?.postHeight || 0) > 0" $style="height:\${this.state?.postHeight}px;" class="flex-shrink-0"></div>
|
||||||
</div>
|
</div>
|
||||||
`))
|
`))
|
||||||
|
|||||||
28
src/nav.js
28
src/nav.js
@ -1,27 +1,25 @@
|
|||||||
import { Component, Hash, Util } from '@apigo.cc/state'
|
import { Component, Hash, Util } from '@web/state'
|
||||||
|
|
||||||
Component.register('Nav', container => {
|
Component.register('Nav', container => {
|
||||||
container.vertical = container.hasAttribute('vertical')
|
|
||||||
container.click = (item, noselect) => {
|
container.click = (item, noselect) => {
|
||||||
if (!item.noselect && !noselect) Hash.nav = item.name
|
if (!item.noselect && !noselect) Hash.nav = item.name
|
||||||
container.dispatchEvent(new CustomEvent('nav', { detail: { item }, bubbles: false }))
|
container.dispatchEvent(new CustomEvent('nav', { detail: { item }, bubbles: false }))
|
||||||
}
|
}
|
||||||
}, Util.makeDom(/*html*/`
|
}, 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="navbar navbar-expand bg-body-secondary px-3 pb-0 border-bottom align-items-center">
|
||||||
<img $if="this.state.brand.image" $src="this.state.brand.image" $class="\${this.vertical ? 'mb-4' : 'me-2'}" style="height:30px;width:auto;max-width:300px">
|
<img $if="this.state.brand.image" $src="this.state.brand.image" class="me-2" style="height:30px;width:auto;max-width:300px">
|
||||||
<i $if="this.state.brand.icon" $class="bi bi-\${this.state.brand.icon} \${this.vertical ? 'mb-4' : 'me-2'}"></i>
|
<i $if="this.state.brand.icon" $class="bi bi-\${this.state.brand.icon} me-2"></i>
|
||||||
<span $if="this.state.brand.label" $class="\${this.vertical ? 'mb-4 fw-bold' : 'me-2'}" $text="this.state.brand.label"></span>
|
<span $if="this.state.brand.label" class="me-2" $text="this.state.brand.label"></span>
|
||||||
<div $class="\${this.vertical ? 'w-100' : 'ms-2'}"></div>
|
<div class="ms-2"></div>
|
||||||
<div $each="this.state.list" $class="\${this.vertical ? 'nav nav-pills flex-column w-100' : 'navbar-nav'} text-truncate \${item.type==='fill'?'flex-fill':''}">
|
<div $each="this.state.list" $class="navbar-nav text-truncate \${item.type==='fill'?'flex-fill':''}">
|
||||||
<button $if="item.type==='button'" $class="nav-link \${Hash.nav===item.name?'active':''} \${this.vertical ? 'text-start' : ''}" $onclick="this.click(item)">
|
<button $if="item.type==='button'" $class="nav-link \${Hash.nav===item.name?'active':''}" $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>
|
<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>
|
</button>
|
||||||
<div $if="item.type==='dropdown'" $class="dropdown \${this.vertical ? 'w-100' : ''}">
|
<div $if="item.type==='dropdown'" class="dropdown">
|
||||||
<button $class="nav-link \${Hash.nav===item.name?'active':''} w-100 \${this.vertical ? 'text-start d-flex justify-content-between align-items-center' : ''}" data-bs-toggle="dropdown">
|
<button $class="nav-link \${Hash.nav===item.name?'active':''}" data-bs-toggle="dropdown">
|
||||||
<span><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></span>
|
<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>
|
||||||
<i $if="this.vertical" class="bi bi-chevron-right small"></i>
|
|
||||||
</button>
|
</button>
|
||||||
<div $class="dropdown-menu \${this.vertical ? 'position-static border-0 bg-transparent shadow-none ps-3' : 'dropdown-menu-end p-3 bg-body-secondary shadow'}" $style="width: \${this.vertical ? '100%' : (item.width || 250) + 'px'};">
|
<div class="dropdown-menu dropdown-menu-end p-3 bg-body-secondary shadow" $style="width: \${item.width || 250}px;">
|
||||||
<template $each="item.list" as="subitem">
|
<template $each="item.list" as="subitem">
|
||||||
<button $if="subitem.type==='button'" class="nav-link px-0 w-100 text-start" $onclick="this.click(subitem, true)">
|
<button $if="subitem.type==='button'" class="nav-link px-0 w-100 text-start" $onclick="this.click(subitem, true)">
|
||||||
<i $class="bi bi-\${subitem.icon} me-2 d-inline-block" style="width: 16px;"></i><span $text="subitem.label"></span>
|
<i $class="bi bi-\${subitem.icon} me-2 d-inline-block" style="width: 16px;"></i><span $text="subitem.label"></span>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Component, Util, $ } from '@apigo.cc/state'
|
import { Component, Util, $ } from '@web/state'
|
||||||
|
|
||||||
export const UI = {}
|
export const UI = {}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
{
|
{
|
||||||
"status": "passed",
|
"status": "failed",
|
||||||
"failedTests": []
|
"failedTests": [
|
||||||
|
"8a84b43f13b676ea22b7-4f4df3778b9a6eb1af05"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
# Instructions
|
||||||
|
|
||||||
|
- Following Playwright test failed.
|
||||||
|
- Explain why, be concise, respect Playwright best practices.
|
||||||
|
- Provide a snippet of code with the fix, if possible.
|
||||||
|
|
||||||
|
# Test info
|
||||||
|
|
||||||
|
- Name: all.spec.js >> base project comprehensive tests and scrolling benchmarks
|
||||||
|
- Location: test/all.spec.js:3:1
|
||||||
|
|
||||||
|
# Error details
|
||||||
|
|
||||||
|
```
|
||||||
|
Error: page.evaluate: TypeError: Cannot read properties of null (reading 'scrollTop')
|
||||||
|
at eval (eval at evaluate (:302:30), <anonymous>:9:21)
|
||||||
|
at async <anonymous>:328:30
|
||||||
|
```
|
||||||
|
|
||||||
|
# Page snapshot
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- heading "All Tests Passed 🎉" [level=1] [ref=e3]
|
||||||
|
```
|
||||||
@ -3,7 +3,6 @@ import { test, expect } from '@playwright/test';
|
|||||||
test('base project comprehensive tests and scrolling benchmarks', async ({ page }) => {
|
test('base project comprehensive tests and scrolling benchmarks', async ({ page }) => {
|
||||||
test.setTimeout(60000);
|
test.setTimeout(60000);
|
||||||
page.on('console', msg => console.log('BROWSER LOG:', msg.text()));
|
page.on('console', msg => console.log('BROWSER LOG:', msg.text()));
|
||||||
page.on('pageerror', err => console.log('BROWSER EXCEPTION:', err.message, err.stack));
|
|
||||||
await page.goto('http://127.0.0.1:8082/test/index.html');
|
await page.goto('http://127.0.0.1:8082/test/index.html');
|
||||||
|
|
||||||
// Wait for testStatus to be set (includes basic unit tests and scrolling refresh test)
|
// Wait for testStatus to be set (includes basic unit tests and scrolling refresh test)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { HTTP, UI, State } from '@apigo.cc/base';
|
import { HTTP, UI, State } from '@web/base';
|
||||||
|
|
||||||
export async function runTests() {
|
export async function runTests() {
|
||||||
console.log('Starting comprehensive Base.js tests...');
|
console.log('Starting comprehensive Base.js tests...');
|
||||||
@ -30,7 +30,7 @@ export async function runTests() {
|
|||||||
|
|
||||||
// 5. AutoForm & TagsInput Test
|
// 5. AutoForm & TagsInput Test
|
||||||
console.log('Testing AutoForm...');
|
console.log('Testing AutoForm...');
|
||||||
const { NewState } = await import('@apigo.cc/state');
|
const { NewState } = await import('@web/state');
|
||||||
const form = document.createElement('AutoForm');
|
const form = document.createElement('AutoForm');
|
||||||
document.body.appendChild(form);
|
document.body.appendChild(form);
|
||||||
await new Promise(r => setTimeout(r, 200));
|
await new Promise(r => setTimeout(r, 200));
|
||||||
@ -89,7 +89,7 @@ export async function runTests() {
|
|||||||
|
|
||||||
// 7. List Components Basic Verification
|
// 7. List Components Basic Verification
|
||||||
console.log('Verifying List Components...');
|
console.log('Verifying List Components...');
|
||||||
const { Component } = await import('@apigo.cc/state');
|
const { Component } = await import('@web/state');
|
||||||
console.log('FastList exists:', Component.exists('FastList'));
|
console.log('FastList exists:', Component.exists('FastList'));
|
||||||
|
|
||||||
const listIds = ['ll', 'gl', 'tt', 'ct'];
|
const listIds = ['ll', 'gl', 'tt', 'ct'];
|
||||||
@ -111,13 +111,9 @@ export async function runTests() {
|
|||||||
await new Promise(r => setTimeout(r, 100)); // wait for render
|
await new Promise(r => setTimeout(r, 100)); // wait for render
|
||||||
const el = document.getElementById(id);
|
const el = document.getElementById(id);
|
||||||
const start = performance.now();
|
const start = performance.now();
|
||||||
console.log(`[MEASURE] ${name} setting scrollTop to 5000`);
|
|
||||||
el.scrollTop = 5000;
|
el.scrollTop = 5000;
|
||||||
console.log(`[MEASURE] ${name} calling refresh`);
|
|
||||||
el.refresh?.();
|
el.refresh?.();
|
||||||
console.log(`[MEASURE] ${name} refresh called, waiting 50ms`);
|
|
||||||
await new Promise(r => setTimeout(r, 50));
|
await new Promise(r => setTimeout(r, 50));
|
||||||
console.log(`[MEASURE] ${name} wait done`);
|
|
||||||
const time = performance.now() - start;
|
const time = performance.now() - start;
|
||||||
window.benchResults[name] = time;
|
window.benchResults[name] = time;
|
||||||
console.log(`BENCHMARK: ${name} scroll & refresh: ${time.toFixed(2)}ms`);
|
console.log(`BENCHMARK: ${name} scroll & refresh: ${time.toFixed(2)}ms`);
|
||||||
|
|||||||
@ -8,17 +8,13 @@
|
|||||||
<script type="importmap">
|
<script type="importmap">
|
||||||
{
|
{
|
||||||
"imports": {
|
"imports": {
|
||||||
"@apigo.cc/state": "../../state/src/index.js",
|
"@web/state": "../../state/src/index.js",
|
||||||
"@apigo.cc/base": "../src/index.js"
|
"@web/base": "../src/index.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body class="d-flex flex-column vh-100">
|
<body class="d-flex flex-column vh-100">
|
||||||
<script>
|
|
||||||
window.addEventListener('error', e => console.log('GLOBAL ERROR:', e.message, e.filename, e.lineno, e.colno, e.error));
|
|
||||||
window.addEventListener('unhandledrejection', e => console.log('UNHANDLED REJECTION:', e.reason));
|
|
||||||
</script>
|
|
||||||
<div id="results" class="p-2 bg-light border-bottom">Running tests...</div>
|
<div id="results" class="p-2 bg-light border-bottom">Running tests...</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@ -5,9 +5,8 @@ import terser from '@rollup/plugin-terser';
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@apigo.cc/state': resolve(__dirname, '../state/src/index.js'),
|
'@web/state': resolve(__dirname, '../state/src/index.js'),
|
||||||
'@apigo.cc/bootstrap': resolve(__dirname, '../bootstrap/src/index.js'),
|
'@web/base': resolve(__dirname, 'src/index.js')
|
||||||
'@apigo.cc/base': resolve(__dirname, 'src/index.js')
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
@ -18,38 +17,20 @@ export default defineConfig({
|
|||||||
build: {
|
build: {
|
||||||
lib: {
|
lib: {
|
||||||
entry: resolve(__dirname, 'src/index.js'),
|
entry: resolve(__dirname, 'src/index.js'),
|
||||||
name: 'ApigoBase',
|
name: 'Base',
|
||||||
formats: ['umd', 'es']
|
formats: ['es']
|
||||||
},
|
},
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
external: ['@apigo.cc/state', '@apigo.cc/bootstrap'],
|
external: ['@web/state'],
|
||||||
output: [
|
output: [
|
||||||
{
|
{
|
||||||
format: 'umd',
|
format: 'es',
|
||||||
name: 'ApigoBase',
|
|
||||||
entryFileNames: 'base.js',
|
entryFileNames: 'base.js',
|
||||||
globals: {
|
minifyInternalExports: false
|
||||||
'@apigo.cc/state': 'ApigoState',
|
|
||||||
'@apigo.cc/bootstrap': 'bootstrap'
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
format: 'umd',
|
format: 'es',
|
||||||
name: 'ApigoBase',
|
|
||||||
entryFileNames: 'base.min.js',
|
entryFileNames: 'base.min.js',
|
||||||
globals: {
|
|
||||||
'@apigo.cc/state': 'ApigoState',
|
|
||||||
'@apigo.cc/bootstrap': 'bootstrap'
|
|
||||||
},
|
|
||||||
plugins: [terser()]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
format: 'es',
|
|
||||||
entryFileNames: 'base.mjs'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
format: 'es',
|
|
||||||
entryFileNames: 'base.min.mjs',
|
|
||||||
plugins: [terser()]
|
plugins: [terser()]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user