# 组件接口

浏览器全局为 `NJWLTable`，版本为 `NJWLTable.version`。

## 新表：mount(element, config)

- `key`：当前账号内的唯一表标识；账号隔离由存储接口负责。
- `rows`：当前数据数组，记录推荐有唯一字符串 `id` 或 `uid`。不要把同一个表重复 id 当不同记录。
- `columns`：字段数组，每项 `id, label, value(record)`；操作列配置 `actions:true` 并使用 `render(record)` 返回 DOM Node。
- `type`：text / number / date（日期字符串）/ timestamp（数字时间戳）；`format(value)` 控制显示；`enumOrder` 控制枚举自然顺序；`categorical:true` 标记分类分组。
- `customValue(record)`：可选，用于多选字段自定义排序；`options`：完整候选；`directionLabels:[正序文案,倒序文案]`；`defaultDirection`；`widthSpec:[最小宽度,弹性权重]`。
- `edit(record)`：返回 `value, options:[[值,标签]], save(value):Promise`；或配置 type/required 的基础输入。没有 edit 即只读。保存失败应 reject，组件保留草稿。调用者负责版本校验、提交后更新内存记录，必要时重新读取。
- `defaults`：`search,filters,sortRules:[{key,direction,order}],columns,group,scroll`。direction 为 asc/desc/custom；order 是自定义值顺序。默认分组只在有分类主项或重复值时显示。
- `loadState():Promise<view|null>`、`saveState(view,{keepalive}):Promise`：业务适配层提供，组件不内置 URL 或 Token。
- `maxSortRules`：可选；与业务存储协议允许的上限一致。
- `watermark():Node|null`：可选水印来源。

返回 `ready` Promise、`filtered()`、`view`、`render()`、`flush()`、`destroy()`。替换数据集时重新 mount；销毁会断开观察器并释放弹窗。

## 既有复杂表：createControls(host)

让业务保留现有行渲染、整行编辑、标签和表单，只把通用控件交给组件。参考随交付提供的 `adapters/crm-host.js`。

host 提供 `element(slot)`（sortRules、sortAnnounce、listHead、rows）、`field(key)`、`rules/setRules`、`columns/orderedColumns/setColumns`、`records/render/locked`、`table/scroll`。可选 `candidates, groupPath, addInGroup, watermark, editingColumn, widthSpec, beforeColumnMove, afterSortRender, defaultSortKey, defaultHint`。

组件返回 renderSortRules、addSortRule、moveSortRule、renderVisualGroups、applyColumnOrder、fitColumnWidths、openCustomOrder、openColumns、compareSortRule 等。CRM 与新表 mount 使用的正是这一实现，不是两份同名代码。

`guardDialog(dialog)`：未保存关闭提示、提交锁定；调用返回对象 begin() 开始提交，end(true) 成功关闭，end() 失败后恢复控件。`lockControls(elements,on)` 用于既有业务编辑流程。

## 修改边界

应用样式只管理外层布局和业务字段呈现。不要在应用 CSS 中覆盖 `.sort-rule`、`.custom-order-*`、`.visual-group`、`.group-caption`、`.column-drag` 等组件内部类。需要改变这些实现，登记 fork 并修改组件源文件；构建输出可由脚本生成，不能作为第二份手工源码。


## 1.1 自动网址与显示偏好

- urlHref(value)：整格字符串识别，返回规范化 http/https 地址或 null。无协议域名补 https；保留显示原文。拒绝邮箱、用户信息、空白/控制字符、非 HTTP(S) 协议、协议相对地址；只对显式 HTTP(S) 支持 IP 和 localhost。无法仅靠语法区分域名与类似 readme.md 的文件名，误识别可单项或整列关闭。
- linkNode(value,{enabled=true,label=value})：返回安全 DOM 链接或文本节点，可给 createControls 宿主使用。不解析 HTML、不探测外网。链接新标签页打开并设置 noopener noreferrer。
- columns[].autoLink:false：应用强制禁用该字段。自定义 render 返回的 DOM 保持原样，应用自行使用 linkNode，不修改既有按钮或链接。
- view.links = {disabledFields:[fieldId], disabledCells:[[rowId,fieldId,originalValue]]}。字段关闭优先；恢复字段不会清除单项关闭。后端按账号和表 key 校验并保留此结构，限制大小；本版本不引入业务数据字段。recordKey 必须稳定且唯一，缺少稳定 ID 时单项设置禁用，字段设置仍可用。
- 编辑取消不保存偏好；业务保存失败保留草稿和勾选状态，成功后提交偏好到视图存储。视图保存失败显示错误，不能当作服务端持久化成功；业务保存与视图存储为两个独立操作。


## 2.0：多选首项、选项维护、删除与信息日期

### 多选排序（不兼容变更）
compareValues 对数组默认只比较第一个已选值，包括 A–Z、Z–A、自定义顺序；相同首项返回相等，交给下一排序规则，空值仍置后。分组也按首项。字段 value(record) 应返回按选择顺序保存的数组，不要先排序或拼接。需要旧式全组选项比较时显式设置 multiOrder:'all'；现有CRM/SSO已选择新默认。

### multiSelect(input, config)
将现有 input 转为搜索、多选标签和候选创建/删除控件。config: label、values（初值数组）、options():string[]、onChange(values)、可选 create(value):Promise、remove(value):Promise、preserved():string[]、busy():boolean、onBusy(boolean)、notify(message)、position(pop,toggle)。返回 element、values()、refresh()。
输入筛选，Enter或添加按钮新增并选中，已存在值去重；中文输入法组合期间不提交。创建/删除回调成功后，宿主更新 options 返回值。候选删除保留 preserved 中的已存值，未保存的新选择移除。宿主负责版本、权限、选项去重、历史值兼容及事务审计；组件不直连任何业务接口。
标准 mount 的 edit(record) 可返回 {multi:true,value:[],options:()=>[],create,remove,save(values)}，复用相同控件。没有 create/remove 时不显示相应操作。

### deleteRecord(config)
异步通用流程，传入 label、message（明确关联影响与撤销能力）、remove():Promise；可选 allowed、busy()、onBusy(on)、onSuccess()、onError(error)。取消不调用 remove；确认后加锁，成功通知宿主，失败恢复并交给宿主显示。返回是否成功。服务端必须校验记录ID、归属、版本和删除权限并保存审计。
标准 mount 提供 remove(record)、canDelete(record)、recordLabel(record)、deleteMessage(record) 时自动生成删除操作列；readOnly:true 强制禁用删除及业务编辑。没有 remove 不生成删除入口。删除接口不能只靠前端鉴权。

### infoDate(config) / infoDateValue(config)
value 为手动 YYYY-MM-DD；recordedAt 为实际录入 Unix 秒；默认时区 Asia/Shanghai。返回日期小字 DOM / 日期字符串。提供 save(value):Promise 才可点开编辑；空字符串表示恢复 recordedAt，没有两个日期则显示未记录。可选 label、inputLabel、disabled、busy()、watermark()。
标准字段可配置 infoDate(record) 返回上述参数。readOnly 禁止日期保存。服务器负责真实录入时间和日期校验，其他字段编辑不能刷新默认录入时间。历史未知不能伪造。

### 接入限制
功能通过公开回调按需启用，不增删真实业务字段或凭空添加后端权限。SSO现有页面没有CRM式选项维护/记录删除接口，升级后保持原有权限与只读范围；可用能力已经来自同一模块，不在只读审计页强行出现写入入口。

## 2.1：组合条件筛选

标准 mount 的“筛选”支持包含、不包含、为空、不为空，多条条件为 AND。分类/数组/有 options 的字段使用可搜索多选候选；其他字段使用文字子串，不区分大小写。多选命中任意候选即包含，不包含表示均未命中；检查所有已选值，和排序首项规则独立。数字 0 不为空。

`view.conditions` 保存 `{key,op,values:[],text:''}[]`。op 为 contains/notContains/empty/notEmpty。宿主必须在 loadState/saveState 与后端校验中保留该字段；未适配的既有应用传 `conditionFilters:false` 保留旧筛选器。条件与已有 search/filters 共同生效；只读表也可筛选，不开放业务写入。取消不应用，空查询禁止应用，清空条件后应用。最多30条条件。

- normalizeConditions(rules)：清洗条件结构。
- matchesConditions(record,rules,fields)：返回是否同时满足。fields 每项 `{key,label,type:'text'|'choice',value(record),options():string[]}`，忽略已移除字段。
- conditionDialog({id?,fields,rules,onApply,watermark?})：显示紧凑筛选弹窗，应用后回调规范条件；返回 dialog，关闭自动移除。应用负责保存视图和刷新。

多选选项名称本来就支持数字或混合文字，如“10”“月饼2份”，按字符串保存并显示。这是共用候选名称，不代表每条记录拥有独立份数；不得把它误报为数量统计功能。

## 2.2 顶部横向滚动条与冻结表头

标准 mount 默认启用 topScrollbar:true 和 stickyHeader:true。长表在表内纵向滚动，真实列标题固定，页面外层继续正常滚动。短表自然高度，不强制撑高。

- topScrollbar:false：关闭顶部滚动条。默认仅横向溢出时显示14px原生滚动条，拖动、键盘左右键和表体水平滚动双向同步。
- stickyHeader:false：关闭冻结和默认高度限制，恢复旧版页面滚动行为。
- maxHeight：CSS长度或数字像素，默认 min(68vh,720px)，仅冻结启用时生效。
- attachViewport({scroll,table?,topScrollbar?,stickyHeader?,maxHeight?})：供自定义createControls宿主复用；容器应具有njwl-table类，返回refresh()/destroy()。须在重建DOM和卸载时销毁。
- mount自动处理窗口尺寸、表宽变化、筛选、重绘、销毁。表头使用原有真实节点，保留排序和列拖动事件。
- 不新增业务权限，不读取数据接口。已有视图scroll保留，纵向滚动不触发视图保存。

迁移：固定新版本JS/CSS。1.x升级时可设置 links:false,conditionFilters:false 保持旧工具栏。组合筛选仍遵循2.1视图协议。此版本不包含共享开发目录尚未发布的新增字段提案；开发目录原有提案保留。
