Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/nullable-text-small-switch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@inkcre/ui-web": patch
---

AutoForm 的普通 nullable 文本直接显示输入,未编辑时保留 null;清空值使用字段旁次级动作。Boolean 字段改用现有小号开关。
4 changes: 4 additions & 0 deletions packages/web/MIGRATION.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 待发布:AutoForm 字段交互

普通 nullable 文本现在直接显示输入,不需要先点击“设置值”。未编辑的 null、未设置字段与空字符串仍各自保留;“清空值”显式置 null。Boolean 字段使用已有 sm 开关。公开模型与事件不变,重新构建即可采用;nullable object、array、Boolean、enum 与日期的状态选择沿用原语义。

# 未发布:场景化加载、Dialog 动作与相对间距

InkLoading 默认仍为三个方块,用于内容区、图谱和预览等待。新增 `variant="spinner"` 与可见 `label`;没有可见标签时继续支持 aria-label。新增 InkSkeleton 单块骨架,用 class/style 定义具体宽高,整个区域由宿主提供一次加载说明。Loading、Skeleton 与 Button 等待图形响应 prefers-reduced-motion。
Expand Down
2 changes: 1 addition & 1 deletion packages/web/skill.seed.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
],
"composeWith": ["InkForm", "InkInput", "InkSwitch"],
"apiCaveats": [
"支持明确的对象、数组、nullable、本地 $defs/$ref 子集;不支持的 schema 使用 JSON 编辑。v-model:formData 保留已有值及额外属性,只为缺失值补默认值。",
"支持明确的对象、数组、nullable、本地 $defs/$ref 子集;不支持的 schema 使用 JSON 编辑。v-model:formData 保留已有值及额外属性,只为缺失值补默认值。普通 nullable 文本直接输入,未编辑保留 null,清空值显式置 null;Boolean 使用 sm 开关。",
"embedded 允许字段嵌入现有 InkForm 而不嵌套 form;format: password 只遮蔽输入,不改变读写语义。",
"数字为 number,空数字删除字段;日期保留 JSON 字符串,Picker 确认才序列化。",
"validation(FormValidation) 的 valid/status/errors/rootErrors 控制保存,pending/invalid/error 均不可保存。实例隔离,过期结果不应用。"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export interface FormValidation {

## API Caveats

- 支持明确的对象、数组、nullable、本地 $defs/$ref 子集;不支持的 schema 使用 JSON 编辑。v-model:formData 保留已有值及额外属性,只为缺失值补默认值。
- 支持明确的对象、数组、nullable、本地 $defs/$ref 子集;不支持的 schema 使用 JSON 编辑。v-model:formData 保留已有值及额外属性,只为缺失值补默认值。普通 nullable 文本直接输入,未编辑保留 null,清空值显式置 null;Boolean 使用 sm 开关。
- embedded 允许字段嵌入现有 InkForm 而不嵌套 form;format: password 只遮蔽输入,不改变读写语义。
- 数字为 number,空数字删除字段;日期保留 JSON 字符串,Picker 确认才序列化。
- validation(FormValidation) 的 valid/status/errors/rootErrors 控制保存,pending/invalid/error 均不可保存。实例隔离,过期结果不应用。
Expand Down
4 changes: 4 additions & 0 deletions packages/web/src/components/inkAutoForm/inkAutoForm.scss
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@
gap: sys-var(space, sm);
}

&__clear {
margin-top: sys-var(space, xs);
}

&__field-errors {
display: flex;
flex-direction: column;
Expand Down
11 changes: 10 additions & 1 deletion packages/web/src/components/inkAutoForm/inkAutoFormField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ const resolved = computed(() => resolveFormSchema(props.schema, props.root));
const mapping = computed(() =>
resolved.value ? mapSchemaPropertyToComponent(resolved.value) : null,
);
const directText = computed(
() =>
resolved.value?.type === "string" &&
(mapping.value?.component === "inkInput" || mapping.value?.component === "inkTextarea"),
);
const entries = computed(() => Object.entries(resolved.value?.properties ?? {}));
const items = computed(() => (Array.isArray(props.value) ? props.value : []));
function updateChild(key: string, value: unknown) {
Expand Down Expand Up @@ -70,7 +75,7 @@ function updateScalar(value: unknown) {

<template>
<div class="ink-auto-form__field">
<div v-if="resolved?.nullable && value === null" class="ink-auto-form__nullable">
<div v-if="resolved?.nullable && value === null && !directText" class="ink-auto-form__nullable">
<span>{{ label }}</span>
<InkButton
size="sm"
Expand Down Expand Up @@ -186,6 +191,7 @@ function updateScalar(value: unknown) {
>
<InkSwitch
:id="id"
size="sm"
:aria-label="label"
:aria-describedby="error ? `${id}-error` : undefined"
:aria-invalid="!!error || undefined"
Expand Down Expand Up @@ -229,6 +235,9 @@ function updateScalar(value: unknown) {
resolved.type !== 'array'
"
size="sm"
theme="subtle"
class="ink-auto-form__clear"
:aria-label="`${message('clearValue', 'Clear value')}: ${label}`"
:disabled="disabled"
@click="emit('update:value', null)"
>
Expand Down
4 changes: 4 additions & 0 deletions packages/web/stories/forms/inkAutoForm.story.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ InkAutoForm 将 object schema 的 string、number、integer、boolean、嵌套 o

使用 `v-model:formData` 接收对象。初始化或 schema 改变时只给缺失字段补 default,已有值(包括 0、false、空字符串和 null)优先;schema 中未列出的已有属性保留,是否允许由 schema 校验。外部 formData 更新不重新套默认值,因此清空数字不会被默认值重新填回。

普通 nullable 文本直接显示输入框;未编辑时 null 和未设置字段保持原样,实际输入或删除文字才生成字符串,包括空字符串。字段旁的“清空值”动作显式写入 null。nullable object、array、Boolean、enum 和日期仍通过“设置值”进入其非 null 状态。Boolean 字段使用小号开关,标签与普通输入保持同一阅读层级。

“Nested credentials, arrays and nullable values”用例同时展示 null、空字符串与未设置文本;修改后从调试数据检查三者,并用“禁用字段”检查输入和清空动作均不可操作。

数字使用文本输入以保留未完成的负号或指数,完整值转换为有限 number,无法转换的草稿保留为字符串并产生校验错误;空数字删除该属性,整数和上下界继续由 schema 校验。日期格式保留为 JSON 字符串:date 使用本地日历 YYYY-MM-DD,date-time/datetime 使用 ISO UTC 时间戳,time 使用带 Z 的 UTC 时间。仅在打开 Picker 时转换为 Date,确认才序列化;未经编辑的字符串保持原样。

每个实例拥有独立 schema 服务。validation 事件传出 FormValidation,包含 valid、status、errors 和 rootErrors。pending、invalid、error 均不能保存;字段与根级错误都显示。服务异常发出 error,不能当作校验成功。替换数据或 schema 后只应用最新校验结果。
Expand Down
19 changes: 18 additions & 1 deletion packages/web/stories/forms/inkAutoForm.story.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import type { FormValidation } from "../../src/index";
import { ref } from "vue";
import InkAutoForm from "../../src/components/inkAutoForm/inkAutoForm.vue";
import InkButton from "../../src/components/inkButton/inkButton.vue";
import type { JSONSchema } from "../../src/components/inkAutoForm/inkAutoForm";

// Schema for basic text fields
Expand Down Expand Up @@ -241,6 +242,10 @@ const retainedSchema: JSONSchema = {
const nestedSchema: JSONSchema = {
type: "object",
properties: {
public_url: { type: ["string", "null"], title: "Public URL" },
empty_text: { type: ["string", "null"], title: "Empty text" },
optional_text: { type: "string", title: "Optional text" },
include_private: { type: "boolean", title: "Include private" },
parameters: { $ref: "#/$defs/Parameters" },
exclusions: { anyOf: [{ $ref: "#/$defs/Exclusions" }, { type: "null" }] },
},
Expand All @@ -262,9 +267,13 @@ const nestedSchema: JSONSchema = {
},
};
const nestedData = ref<Record<string, unknown>>({
public_url: null,
empty_text: "",
include_private: false,
parameters: { host: "mail.example.com", password: "example-secret", folders: ["Inbox"] },
exclusions: null,
});
const nestedDisabled = ref(false);
</script>

<template>
Expand Down Expand Up @@ -345,7 +354,15 @@ const nestedData = ref<Record<string, unknown>>({
<pre class="story-debug">{{ retainedData }}</pre>
</Variant>
<Variant title="Nested credentials, arrays and nullable values">
<InkAutoForm v-model:form-data="nestedData" :schema="nestedSchema" />
<InkButton
:text="nestedDisabled ? '启用字段' : '禁用字段'"
@click="nestedDisabled = !nestedDisabled"
/>
<InkAutoForm
v-model:form-data="nestedData"
:schema="nestedSchema"
:disabled="nestedDisabled"
/>
<pre class="story-debug">{{ nestedData }}</pre>
</Variant>
</Story>
Expand Down
Loading