@neatui/vue 5.1.10 → 5.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ # @neatui/vue 更新记录
2
+
3
+ ## 5.1.11
4
+
5
+ - FormLoader 新增 `mode="read"`:保留表单控件,统一设置 `disabled`;禁止字段写回、数组增删和排序,隐藏校验提示。插槽的 `bind.disabled` 同步为 `true`,自定义控件需绑定该属性。
6
+ - FormLoader 的 `字段名-value` 插槽仅在 `view` 模式使用;`read` 使用普通控件或字段插槽。
7
+ - Select、树选择、多选、分页选择和搜索选择的搜索区域在移动端撑满宽度。
8
+ - CSS 依赖更新为 `@neatui/css@^5.1.11`。该版本包含不兼容的 Grid 间距语法调整:`ui-grid="3 mm"` 改为 `ui-grid="3" class="gap-mm"`,裸 `lm` 改为左侧垂直居中。响应式间距的前缀规则不同,请阅读 CSS 包随附的 `CHANGELOG.md` 进行迁移。
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @neatui/vue
2
2
 
3
+ 升级说明见 [更新记录](./CHANGELOG.md),5.1.11 包含 FormLoader read 模式及 CSS Grid 间距迁移说明。
4
+
3
5
  NeatUI Vue component package for Vue 3 and Nuxt projects.
4
6
 
5
7
  Nuxt users should use `@neatui/vue` directly.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neatui/vue",
3
- "version": "5.1.10",
3
+ "version": "5.1.11",
4
4
  "description": "basic component ui",
5
5
  "main": "./src/index.ts",
6
6
  "license": "MIT",
@@ -36,7 +36,7 @@
36
36
  "@fekit/scrollto": "^3.0.3",
37
37
  "@fekit/toast": "^2.7.2",
38
38
  "@fekit/utils": "^6.0.0",
39
- "@neatui/css": "^5.1.10",
39
+ "@neatui/css": "^5.1.11",
40
40
  "@rollup/plugin-commonjs": "^26.0.1",
41
41
  "@rollup/plugin-node-resolve": "^15.2.3",
42
42
  "@vue/compiler-sfc": "^3.4.30",
@@ -52,6 +52,7 @@
52
52
  "files": [
53
53
  "src",
54
54
  "README.md",
55
+ "CHANGELOG.md",
55
56
  "MIGRATION-5.0.md"
56
57
  ]
57
58
  }
@@ -13,7 +13,7 @@
13
13
  </Input>
14
14
  </slot>
15
15
  <template #tips>
16
- <div v-if="search" class="ny-sm nx-sl">
16
+ <div v-if="search" class="ny-sm nx-sl mob:w-full">
17
17
  <Input v-model="state.search" :theme="theme" sz="s">
18
18
  <template #suffix>
19
19
  <i ui-form-search="" class="co-note"></i>
@@ -9,7 +9,7 @@
9
9
  <template #suffix><i ui-form-select=""></i></template>
10
10
  </Input>
11
11
  <template #tips v-if="!disabled && !readonly">
12
- <div v-if="search" class="nb-sm nx-sl">
12
+ <div v-if="search" class="nb-sm nx-sl mob:w-full">
13
13
  <Input v-model="state.search" :theme="theme" sz="s">
14
14
  <template #suffix>
15
15
  <i ui-form-search="" class="co-note"></i>
@@ -7,7 +7,7 @@
7
7
  </Input>
8
8
  </slot>
9
9
  <template #tips>
10
- <div v-if="filterable" class="ny-sm nx-sl" style="height: 3em">
10
+ <div v-if="filterable" class="ny-sm nx-sl mob:w-full" style="height: 3em">
11
11
  <div :ui-form="`${theme} type:input sz:s :block`">
12
12
  <input type="text" :placeholder="props.placeholder" v-model="state.search" />
13
13
  <div ui-form-suffix="">
@@ -5,7 +5,7 @@
5
5
  <template #suffix><i ui-form-select=""></i></template>
6
6
  </Input>
7
7
  <template #tips>
8
- <div v-if="filterable" class="ny-sm nx-sl" style="height: 3em">
8
+ <div v-if="filterable" class="ny-sm nx-sl mob:w-full" style="height: 3em">
9
9
  <div :ui-form="`${theme} type:input sz:s :block`">
10
10
  <input type="text" :placeholder="props.placeholder" v-model="state.search" />
11
11
  <div ui-form-suffix="">
@@ -5,7 +5,7 @@
5
5
  <template #suffix><i ui-form-select=""></i></template>
6
6
  </Input>
7
7
  <template #tips>
8
- <div class="ny-sm nx-sl" style="height: 3em">
8
+ <div class="ny-sm nx-sl mob:w-full" style="height: 3em">
9
9
  <div :ui-form="`${theme} type:input sz:s :block`">
10
10
  <input type="text" :placeholder="props.placeholder" v-model="state.search" />
11
11
  <div ui-form-suffix="">
@@ -9,7 +9,7 @@
9
9
  <template #suffix><i ui-form-select=""></i></template>
10
10
  </Input>
11
11
  <template #tips v-if="!readonly || !disabled">
12
- <div v-if="search" class="ny-sm nx-sl">
12
+ <div v-if="search" class="ny-sm nx-sl mob:w-full">
13
13
  <Input v-model="state.search" :theme="theme" sz="s">
14
14
  <template #suffix>
15
15
  <i ui-form-search="" class="co-note"></i>
@@ -12,7 +12,7 @@
12
12
  </template>
13
13
  </Input>
14
14
  <template #tips v-if="!readonly && !disabled">
15
- <div v-if="search" class="ny-sm nx-sl">
15
+ <div v-if="search" class="ny-sm nx-sl mob:w-full">
16
16
  <Input v-model="state.search" :theme="theme" :sz="sz">
17
17
  <template #suffix>
18
18
  <i ui-form-search="" class="co-note"></i>
@@ -9,7 +9,7 @@
9
9
  <template #suffix><i ui-form-select=""></i></template>
10
10
  </Input>
11
11
  <template #tips v-if="!readonly || !disabled">
12
- <div v-if="search" class="nb-sm nx-sl">
12
+ <div v-if="search" class="nb-sm nx-sl mob:w-full">
13
13
  <Input v-model="state.search" :theme="theme" sz="s">
14
14
  <template #suffix>
15
15
  <i ui-form-search="" class="co-note"></i>
@@ -92,8 +92,8 @@
92
92
  <FieldMessage :col="col" :mode="mode" />
93
93
  </div>
94
94
 
95
- <FormRender v-else :col="col" :mode="mode" :entry="resolveEntry(col)" :interacted="touched" :ctx="context()" :custom="(!editable && hasSlot(`${col.field}-value`)) || !resolveEntry(col)">
96
- <slot v-if="!editable && hasSlot(`${col.field}-value`)" :name="`${col.field}-value`" v-bind="fieldScope(col)"></slot>
95
+ <FormRender v-else :col="col" :mode="mode" :entry="resolveEntry(col)" :interacted="touched" :ctx="context()" :custom="(mode === 'view' && hasSlot(`${col.field}-value`)) || !resolveEntry(col)">
96
+ <slot v-if="mode === 'view' && hasSlot(`${col.field}-value`)" :name="`${col.field}-value`" v-bind="fieldScope(col)"></slot>
97
97
  <template v-else>
98
98
  <slot v-if="col.slot" :name="col.slot" v-bind="fieldScope(col)"></slot>
99
99
  <slot v-else-if="col.model[0]" :name="col.model[0]" v-bind="fieldScope(col)"></slot>
@@ -591,7 +591,7 @@
591
591
  type: Boolean,
592
592
  default: false,
593
593
  },
594
- // edit 编辑 | view 查看
594
+ // edit 编辑 | read 禁用控件 | view 查看
595
595
  mode: {
596
596
  type: String,
597
597
  default: 'edit',
@@ -693,7 +693,7 @@
693
693
  shared.revision++;
694
694
  if (!props.root) shared.dataVersion.value++;
695
695
  }, { deep: true, flush: 'sync' });
696
- const editable = computed(() => props.mode !== 'view');
696
+ const editable = computed(() => props.mode !== 'view' && props.mode !== 'read');
697
697
  // 调用过 validate 后所有字段展示校验状态
698
698
  const touched = computed(() => props.interacted || shared.state.validated);
699
699
 
@@ -252,7 +252,7 @@
252
252
  };
253
253
  // 内置控件和插槽使用相同写回规则,查看模式不接受写入。
254
254
  export const writeValue = (col: any, value: any, ctx: FormContext) => {
255
- if (!col.field || ctx.mode === 'view') return;
255
+ if (!col.field || ctx.mode === 'view' || ctx.mode === 'read') return;
256
256
  const binding = col.value;
257
257
  col._data[col.field] = valueBinding(binding) && typeof binding.set === 'function' ? binding.set(value, ctx) : value;
258
258
  };
@@ -281,7 +281,7 @@
281
281
  // native:控件自己用 ui-form-tips 显示错误,这里只留隐藏文本给读屏关联
282
282
  props: ['col', 'mode', 'native'],
283
283
  // 没有内容时返回空数组:客户端渲染不留占位注释
284
- setup: (props) => () => props.mode !== 'view' ? [
284
+ setup: (props) => () => props.mode !== 'view' && props.mode !== 'read' ? [
285
285
  props.col.model?.[1]?.verify && props.col._checked
286
286
  ? h('i', { 'ui-form-verify': props.col._error ? 'no' : isEmpty(props.col._data?.[props.col.field]) ? '' : 'ok', 'aria-hidden': 'true' }) : null,
287
287
  props.col._error ? h('p', { id: `${props.col._fid}-error`, 'data-field-error': props.col.field,
@@ -466,6 +466,7 @@
466
466
  return rest;
467
467
  });
468
468
  const update = (value: any) => {
469
+ if (props.mode === 'view' || props.mode === 'read') return;
469
470
  props.col._touched = true;
470
471
  writeValue(props.col, value, props.ctx);
471
472
  events.value['update:modelValue']?.(value);
@@ -483,7 +484,7 @@
483
484
  const controlProps = computed(() => {
484
485
  const { col, entry, mode } = props;
485
486
  const options = col.model?.[1] || {};
486
- const error = mode !== 'view' && nativeTips.value ? col._error : null;
487
+ const error = mode !== 'view' && mode !== 'read' && nativeTips.value ? col._error : null;
487
488
  const merged: Record<string, any> = {
488
489
  interacted: props.interacted,
489
490
  options: entry?.options ? col.enums : undefined,
@@ -494,7 +495,8 @@
494
495
  tips: error?.msg || options.tips,
495
496
  ...(error ? { interacted: true } : {}),
496
497
  readonly: mode === 'view' ? true : options.readonly,
497
- disabled: mode === 'view' ? true : options.disabled,
498
+ // read 保留控件,统一使用 disabled 呈现不可编辑状态。
499
+ disabled: mode === 'view' || mode === 'read' ? true : options.disabled,
498
500
  };
499
501
  // 没有声明 props 的自定义注册控件可能通过 $attrs 读取这些参数,保持透传。
500
502
  formOnly.forEach((key) => { if (!declaresOf(entry, key)) delete merged[key]; });
@@ -516,7 +518,7 @@
516
518
  const el = root.value;
517
519
  // 非 DOM 渲染环境(SSR、自定义渲染器)没有查询接口
518
520
  if (!el || typeof el.querySelectorAll !== 'function') return;
519
- const errorId = props.col._error && props.mode !== 'view' ? `${props.col._fid}-error` : '';
521
+ const errorId = props.col._error && props.mode !== 'view' && props.mode !== 'read' ? `${props.col._fid}-error` : '';
520
522
  el.querySelectorAll<HTMLElement>(inputs).forEach((node) => {
521
523
  if (errorId) {
522
524
  node.setAttribute('aria-invalid', 'true');