@neeloong/form 0.12.1 → 0.13.0

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/README.md CHANGED
@@ -175,6 +175,13 @@ render(store, layouts, app);
175
175
  <li !enum="items" !text="$item"></li> <!-- items 是数组 -->
176
176
  </ul>
177
177
  ```
178
+ #### 自定义排序
179
+
180
+ ```html
181
+ <ul>
182
+ <li !enum="object" !sort="$key" !text="$item"></li> <!-- object 是对象,将按照其键排序,渲染其值 -->
183
+ </ul>
184
+ ```
178
185
 
179
186
  #### 嵌套循环
180
187
 
@@ -284,7 +291,7 @@ render(store, layouts, app);
284
291
  1. 模板定义: `!template`
285
292
  1. 条件: `!if` `!else`
286
293
  1. 子属性: `!value`
287
- 1. 枚举: `!enum`
294
+ 1. 枚举: `!enum` `!sort`
288
295
  1. 别名、计算名与显式变量: `*别名` `*计算名` `+变量`
289
296
  1. 片段与模板调用: `!fragment`
290
297
  1. 属性与事件: `:绑定属性` `@事件` `普通属性` `!bind`
@@ -325,7 +332,9 @@ render(store, layouts, app);
325
332
  - `$kind` 只读 字段类别
326
333
  - `$error` 只读 字段校验错误信息
327
334
  - `$errors` 只读 所有校验错误列表
328
- 1. 数组字段扩展隐式函数(只在事件中可用)
335
+ - `$addable` 只读 是否可以为当前数组增加项目,非数组上下文总是为 `false`
336
+ - `$removable` 只读 是否可以将当前项从数组中移除,非数组成员上下文总是为 `false`
337
+ 1. 字段扩展隐式函数(只在事件中可用)
329
338
  - `$reset()` 重置数据
330
339
  - `$validate()` 触发当前项的异步校验
331
340
  - `$validate(true)` 触发当前项及其后代的异步校验
package/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @neeloong/form v0.12.1
2
+ * @neeloong/form v0.13.0
3
3
  * (c) 2024-2025 Fierflame
4
4
  * @license Apache-2.0
5
5
  */
@@ -253,6 +253,7 @@ type Enum = {
253
253
  templates?: Record<string, Template> | undefined;
254
254
  type: "enum";
255
255
  value: Node.Name | Node.Calc | Node.Value;
256
+ sort?: Node.Name | Node.Calc | Node.Value<any> | undefined;
256
257
  /**
257
258
  * 子元素
258
259
  */
@@ -653,6 +654,14 @@ declare namespace Schema {
653
654
  * 模式规则
654
655
  */
655
656
  pattern?: RegExp | ((store: Store) => RegExp | null) | null | undefined;
657
+ /**
658
+ * 数组内是否可添加
659
+ */
660
+ addable?: boolean | ((store: Store) => boolean) | null | undefined;
661
+ /**
662
+ * 数组内是否可移除
663
+ */
664
+ removable?: boolean | ((store: Store) => boolean) | null | undefined;
656
665
  /**
657
666
  * 可选值
658
667
  */
@@ -804,7 +813,7 @@ declare class Store<T = any, M = any> {
804
813
  /**
805
814
  * @param {Schema.Field<M>} schema 字段的 Schema 定义
806
815
  * @param {object} [options] 可选配置
807
- * @param {*} [options.parent]
816
+ * @param {Store?} [options.parent]
808
817
  * @param {*} [options.state]
809
818
  * @param {number | string | null} [options.index]
810
819
  * @param {number | Signal.State<number> | Signal.Computed<number>} [options.size]
@@ -813,8 +822,9 @@ declare class Store<T = any, M = any> {
813
822
  * @param {boolean} [options.hidden]
814
823
  * @param {boolean} [options.clearable]
815
824
  * @param {boolean} [options.required]
816
- * @param {boolean} [options.readonly]
817
825
  * @param {boolean} [options.disabled]
826
+ * @param {boolean} [options.readonly]
827
+ * @param {boolean} [options.removable]
818
828
  *
819
829
  * @param {string} [options.label] 字段标签
820
830
  * @param {string} [options.description] 字段描述
@@ -838,8 +848,8 @@ declare class Store<T = any, M = any> {
838
848
  * @param {((value: T?, index: any, store: Store) => void)?} [options.onUpdate]
839
849
  * @param {((value: T?, index: any, store: Store) => void)?} [options.onUpdateState]
840
850
  */
841
- constructor(schema: Schema.Field<M>, { null: isNull, state, ref, setValue, setState, convert, onUpdate, onUpdateState, validator, validators, index, size, new: isNew, parent: parentNode, hidden, clearable, required, disabled, readonly, label, description, placeholder, min, max, step, minLength, maxLength, pattern, values }?: {
842
- parent?: any;
851
+ constructor(schema: Schema.Field<M>, { null: isNull, state, ref, setValue, setState, convert, onUpdate, onUpdateState, validator, validators, index, size, new: isNew, parent: parentNode, hidden, clearable, required, disabled, readonly, removable, label, description, placeholder, min, max, step, minLength, maxLength, pattern, values }?: {
852
+ parent?: Store<any, any> | null | undefined;
843
853
  state?: any;
844
854
  index?: string | number | null | undefined;
845
855
  size?: number | Signal.State<number> | Signal.Computed<number> | undefined;
@@ -848,8 +858,9 @@ declare class Store<T = any, M = any> {
848
858
  hidden?: boolean | undefined;
849
859
  clearable?: boolean | undefined;
850
860
  required?: boolean | undefined;
851
- readonly?: boolean | undefined;
852
861
  disabled?: boolean | undefined;
862
+ readonly?: boolean | undefined;
863
+ removable?: boolean | undefined;
853
864
  label?: string | undefined;
854
865
  description?: string | undefined;
855
866
  placeholder?: string | undefined;
@@ -964,6 +975,11 @@ declare class Store<T = any, M = any> {
964
975
  set readonly(v: boolean);
965
976
  /** 是否只读 */
966
977
  get readonly(): boolean;
978
+ set selfRemovable(v: boolean | null);
979
+ get selfRemovable(): boolean | null;
980
+ set removable(v: boolean);
981
+ /** 是否只读 */
982
+ get removable(): boolean;
967
983
  set selfLabel(v: string | null);
968
984
  get selfLabel(): string | null;
969
985
  set label(v: string | null);
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @neeloong/form v0.12.1
2
+ * @neeloong/form v0.13.0
3
3
  * (c) 2024-2025 Fierflame
4
4
  * @license Apache-2.0
5
5
  */
@@ -595,17 +595,12 @@
595
595
  * @param {Signal.Computed<boolean>?} [parent]
596
596
  * @returns {[Signal.State<boolean?>, Signal.Computed<boolean>]}
597
597
  */
598
- const createBooleanStates = (self, defState, fn, parent) => {
598
+ function createBooleanStates(self, defState, fn, parent) {
599
599
 
600
600
  const selfState = new exports.Signal.State(typeof defState === 'boolean' ? defState : null);
601
- /** @type {Signal.Computed<boolean>} */
602
- let scriptState;
603
- if (typeof fn === 'function') {
604
- scriptState = new exports.Signal.Computed(() => Boolean(fn(self)));
605
- } else {
606
- const def = Boolean(fn);
607
- scriptState = new exports.Signal.Computed(() => def);
608
- }
601
+ const scriptState = typeof fn === 'function'
602
+ ? new exports.Signal.Computed(() => Boolean(fn(self)))
603
+ : new exports.Signal.State(Boolean(fn));
609
604
 
610
605
  const getState = () => {
611
606
  const s = selfState.get();
@@ -617,7 +612,7 @@
617
612
 
618
613
  return [selfState, state];
619
614
 
620
- };
615
+ }
621
616
 
622
617
  /** @import { Schema } from '../types.mjs' */
623
618
  /** @param {*} v */
@@ -717,12 +712,13 @@
717
712
  }
718
713
 
719
714
  /** @import { Schema } from '../types.mjs' */
715
+ /** @import ArrayStore from './ArrayStore.mjs' */
720
716
 
721
717
 
722
718
  /** @type {{new(...p: ConstructorParameters<typeof Store>): Store}?} */
723
719
  let ObjectStore$1 = null;
724
- /** @type {{new(...p: ConstructorParameters<typeof Store>): Store}?} */
725
- let ArrayStore$1 = null;
720
+ /** @type {{new(...p: ConstructorParameters<typeof Store>): ArrayStore}?} */
721
+ let ArrayStoreClass = null;
726
722
  /** @type {Record<string, {new(...p: ConstructorParameters<typeof Store>): Store}?>} */
727
723
  let TypeStores = Object.create(null);
728
724
  /**
@@ -739,8 +735,8 @@
739
735
  const type = schema.type;
740
736
  /** @type {{new(...p: ConstructorParameters<typeof Store>): Store}} */
741
737
  let Class = Store;
742
- if (schema.array && !(ArrayStore$1 && options?.parent instanceof ArrayStore$1)) {
743
- if (ArrayStore$1) { Class = ArrayStore$1; }
738
+ if (schema.array && !(ArrayStoreClass && options?.parent instanceof ArrayStoreClass)) {
739
+ if (ArrayStoreClass) { Class = ArrayStoreClass; }
744
740
  } else if (typeof type === 'string') {
745
741
  const C = TypeStores[type];
746
742
  if (C) { Class = C; }
@@ -755,9 +751,9 @@
755
751
  ObjectStore$1 = Class;
756
752
  }
757
753
 
758
- /** @param {{new(...p: ConstructorParameters<typeof Store>): Store}} Class */
754
+ /** @param {{new(...p: ConstructorParameters<typeof Store>): ArrayStore}} Class */
759
755
  function setArrayStore(Class) {
760
- ArrayStore$1 = Class;
756
+ ArrayStoreClass = Class;
761
757
  }
762
758
  /**
763
759
  * @param {string} type
@@ -933,7 +929,7 @@
933
929
  /**
934
930
  * @param {Schema.Field<M>} schema 字段的 Schema 定义
935
931
  * @param {object} [options] 可选配置
936
- * @param {*} [options.parent]
932
+ * @param {Store?} [options.parent]
937
933
  * @param {*} [options.state]
938
934
  * @param {number | string | null} [options.index]
939
935
  * @param {number | Signal.State<number> | Signal.Computed<number>} [options.size]
@@ -942,8 +938,9 @@
942
938
  * @param {boolean} [options.hidden]
943
939
  * @param {boolean} [options.clearable]
944
940
  * @param {boolean} [options.required]
945
- * @param {boolean} [options.readonly]
946
941
  * @param {boolean} [options.disabled]
942
+ * @param {boolean} [options.readonly]
943
+ * @param {boolean} [options.removable]
947
944
  *
948
945
  * @param {string} [options.label] 字段标签
949
946
  * @param {string} [options.description] 字段描述
@@ -972,7 +969,7 @@
972
969
  setValue, setState, convert, onUpdate, onUpdateState,
973
970
  validator, validators,
974
971
  index, size, new: isNew, parent: parentNode,
975
- hidden, clearable, required, disabled, readonly,
972
+ hidden, clearable, required, disabled, readonly, removable,
976
973
  label, description, placeholder, min, max, step, minLength, maxLength, pattern, values: values$1
977
974
  } = {}) {
978
975
  this.schema = schema;
@@ -1038,6 +1035,8 @@
1038
1035
  // @ts-ignore
1039
1036
  [this.#selfValues, this.#values] = createState(this, values, values$1, schema.values);
1040
1037
 
1038
+ [this.#selfRemovable, this.#removable] = createBooleanStates(this, removable, schema.removable ?? true);
1039
+
1041
1040
  const validatorResult = createValidator(this, schema.validator, validator);
1042
1041
 
1043
1042
  const [changed, changedResult, cancelChange] = createAsyncValidator(this, schema.validators?.change, validators?.change);
@@ -1192,6 +1191,15 @@
1192
1191
  set readonly(v) { this.#selfReadonly.set(typeof v === 'boolean' ? v : null); }
1193
1192
 
1194
1193
 
1194
+ /** @readonly @type {Signal.State<boolean?>} */
1195
+ #selfRemovable
1196
+ /** @readonly @type {Signal.Computed<boolean>} */
1197
+ #removable
1198
+ get selfRemovable() { return this.#selfRemovable.get(); }
1199
+ set selfRemovable(v) { this.#selfRemovable.set(typeof v === 'boolean' ? v : null); }
1200
+ /** 是否只读 */
1201
+ get removable() { return this.#removable.get(); }
1202
+ set removable(v) { this.#selfRemovable.set(typeof v === 'boolean' ? v : null); }
1195
1203
 
1196
1204
 
1197
1205
  /** @readonly @type {Signal.State<string?>} */
@@ -1597,10 +1605,11 @@
1597
1605
  * @param {Store?} [options.parent]
1598
1606
  * @param {string | number | null} [options.index]
1599
1607
  * @param {boolean} [options.new]
1608
+ * @param {boolean} [options.addable]
1600
1609
  * @param {(value: any, index: any, store: Store) => void} [options.onUpdate]
1601
1610
  * @param {(value: any, index: any, store: Store) => void} [options.onUpdateState]
1602
1611
  */
1603
- constructor(schema, { parent, onUpdate, onUpdateState, index, new: isNew} = {}) {
1612
+ constructor(schema, { parent, onUpdate, onUpdateState, index, new: isNew, addable} = {}) {
1604
1613
  const childrenState = new exports.Signal.State(/** @type {Store[]} */([]));
1605
1614
  // @ts-ignore
1606
1615
  const updateChildren = (list) => {
@@ -1636,6 +1645,9 @@
1636
1645
  },
1637
1646
  onUpdateState,
1638
1647
  });
1648
+
1649
+ [this.#selfAddable, this.#addable] = createBooleanStates(this, addable, schema.addable ?? true);
1650
+
1639
1651
  this.#children = childrenState;
1640
1652
  const childCommonOptions = {
1641
1653
  parent: this,
@@ -1666,6 +1678,20 @@
1666
1678
  return child
1667
1679
  };
1668
1680
  }
1681
+
1682
+
1683
+ /** @readonly @type {Signal.State<boolean?>} */
1684
+ #selfAddable
1685
+ /** @readonly @type {Signal.Computed<boolean>} */
1686
+ #addable
1687
+ get selfAddable() { return this.#selfAddable.get(); }
1688
+ set selfAddable(v) { this.#selfAddable.set(typeof v === 'boolean' ? v : null); }
1689
+ /** 是否禁用字段 */
1690
+ get addable() { return this.#addable.get(); }
1691
+ set addable(v) { this.#selfAddable.set(typeof v === 'boolean' ? v : null); }
1692
+
1693
+
1694
+
1669
1695
  /**
1670
1696
  *
1671
1697
  * @param {number} index
@@ -1674,6 +1700,7 @@
1674
1700
  * @returns
1675
1701
  */
1676
1702
  insert(index, value = null, isNew) {
1703
+ if (!this.addable) { return false; }
1677
1704
  const data = this.value || [];
1678
1705
  if (!Array.isArray(data)) { return false; }
1679
1706
  const children = [...this.#children.get()];
@@ -1944,7 +1971,7 @@
1944
1971
  }
1945
1972
  const enumValue = layout.enum;
1946
1973
  const name = layout.value;
1947
- if (enumValue) { child = { type: 'enum', value: enumValue, vars, children: child ? [child] : [] }; vars = null; }
1974
+ if (enumValue) { child = { type: 'enum', value: enumValue, sort: layout.sort, vars, children: child ? [child] : [] }; vars = null; }
1948
1975
  if (name) { child = { type: 'value', name, vars, children: child ? [child] : [] }; vars = null; }
1949
1976
  if (vars?.length) {
1950
1977
  child = { type: 'fragment', vars, children: child ? [child] : [] };
@@ -2138,6 +2165,9 @@
2138
2165
  case 'enum':
2139
2166
  node.enum = value ? parse$1(value, createCalc) : {value: true};
2140
2167
  break;
2168
+ case 'sort':
2169
+ node.sort = value ? parse$1(value, createCalc) : {value: true};
2170
+ break;
2141
2171
  case 'if':
2142
2172
  node.if = parse$1(value, createCalc);
2143
2173
  break;
@@ -2179,6 +2209,7 @@
2179
2209
  *
2180
2210
  * @property {string} [value] 值关联
2181
2211
  * @property {Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value} [enum] 列表属性枚举
2212
+ * @property {Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value} [sort]
2182
2213
  *
2183
2214
  * @property {boolean | string} [bind] 绑定内容
2184
2215
  * @property {Layout.Node.Name | Layout.Node.Value | Layout.Node.Calc} [text] 文本渲染
@@ -2805,6 +2836,7 @@
2805
2836
  * @property {Record<string, Template>} [templates]
2806
2837
  * @property {'enum'} type
2807
2838
  * @property {Node.Name | Node.Calc | Node.Value} value
2839
+ * @property {Node.Name | Node.Calc | Node.Value} [sort]
2808
2840
  * @property {Child[]} children 子元素
2809
2841
  * @property {string} [comment] 注释
2810
2842
  */
@@ -3004,7 +3036,11 @@
3004
3036
  yield [`${key}${sign}reset`, {exec: () => val.reset()}];
3005
3037
  // @ts-ignore
3006
3038
  yield [`${key}${sign}validate`, {exec: v => val.validate(v ? [] : null)}];
3007
- if (!(val instanceof ArrayStore)) { return; }
3039
+ if (!(val instanceof ArrayStore)) {
3040
+ yield [`${key}${sign}addable`, {get: () => false}];
3041
+ return;
3042
+ }
3043
+ yield [`${key}${sign}addable`, {get: () => val.addable}];
3008
3044
  yield [`${key}${sign}insert`, {exec: (index, value) => val.insert(index, value)}];
3009
3045
  yield [`${key}${sign}add`, {exec: (v) => val.add(v)}];
3010
3046
  yield [`${key}${sign}remove`, {exec: (index) => val.remove(index)}];
@@ -3023,30 +3059,53 @@
3023
3059
  */
3024
3060
  function *toParentItem(parent, val, key = '', sign = '$') {
3025
3061
  if (!(parent instanceof ArrayStore)) {
3062
+ yield [`${key}${sign}removable`, {get: () => false}];
3026
3063
  yield [`${key}${sign}upMovable`, {get: () => false}];
3027
3064
  yield [`${key}${sign}downMovable`, {get: () => false}];
3065
+ yield [`${key}${sign}remove`, {exec: () => {}}];
3066
+ yield [`${key}${sign}upMove`, {exec: () => {}}];
3067
+ yield [`${key}${sign}downMove`, {exec: () => {}}];
3028
3068
  return
3029
3069
  }
3070
+ yield [`${key}${sign}removable`, {get: () => {
3071
+ if (parent.readonly) { return false; }
3072
+ if (parent.disabled) { return false; }
3073
+ if (!val.removable) { return false; }
3074
+ return true;
3075
+ }}];
3030
3076
  yield [`${key}${sign}upMovable`, {get: () => {
3077
+ if (parent.readonly) { return false; }
3078
+ if (parent.disabled) { return false; }
3031
3079
  const s = val.index;
3032
3080
  if (typeof s !== 'number') { return false; }
3033
3081
  if (s <= 0) { return false; }
3034
3082
  return true;
3035
3083
  }}];
3036
3084
  yield [`${key}${sign}downMovable`, {get: () => {
3085
+ if (parent.readonly) { return false; }
3086
+ if (parent.disabled) { return false; }
3037
3087
  const s = val.index;
3038
3088
  if (typeof s !== 'number') { return false; }
3039
3089
  if (s >= parent.size - 1) { return false; }
3040
3090
  return true;
3041
3091
  }}];
3042
- yield [`${key}${sign}remove`, {exec: () => parent.remove(Number(val.index))}];
3092
+ yield [`${key}${sign}remove`, {exec: () => {
3093
+ if (parent.readonly) { return; }
3094
+ if (parent.disabled) { return; }
3095
+ if (!val.removable) { return; }
3096
+ parent.remove(Number(val.index));
3097
+ }}];
3043
3098
  yield [`${key}${sign}upMove`, {exec: () => {
3099
+ if (parent.readonly) { return; }
3100
+ if (parent.disabled) { return; }
3044
3101
  const s = val.index;
3045
3102
  if (typeof s !== 'number') { return; }
3046
3103
  if (s <= 0) { return; }
3047
3104
  parent.move(s, s - 1);
3048
3105
  }}];
3049
3106
  yield [`${key}${sign}downMove`, {exec: () => {
3107
+ if (parent.readonly) { return; }
3108
+ if (parent.disabled) { return; }
3050
3109
  const s = val.index;
3051
3110
  if (typeof s !== 'number') { return; }
3052
3111
  if (s >= parent.size - 1) { return; }
@@ -4525,6 +4584,7 @@
4525
4584
  return node;
4526
4585
  }
4527
4586
 
4587
+ /** @import * as Layout from '../Layout/index.mjs' */
4528
4588
  /** @import Store, { ArrayStore } from '../Store/index.mjs' */
4529
4589
 
4530
4590
  /**
@@ -4534,8 +4594,9 @@
4534
4594
  * @param {ArrayStore} store
4535
4595
  * @param {Environment} env
4536
4596
  * @param {(next: Node | null, env: any) => () => void} renderItem
4597
+ * @param {Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value} [sort]
4537
4598
  */
4538
- function renderArray(parent, next, store, env, renderItem) {
4599
+ function renderArray(parent, next, store, env, renderItem, sort) {
4539
4600
  const start = parent.insertBefore(document.createComment(''), next);
4540
4601
  /** @type {Map<Store, [Comment, Comment, () => void]>} */
4541
4602
  let seMap = new Map();
@@ -4651,7 +4712,26 @@
4651
4712
  };
4652
4713
  }
4653
4714
 
4715
+ /**
4716
+ *
4717
+ * @param {any} a
4718
+ * @param {any} b
4719
+ * @returns
4720
+ */
4721
+ function compare(a, b) {
4722
+ if (typeof a === 'bigint' && typeof b === 'bigint') {
4723
+ return Number(a - b);
4724
+ }
4725
+ if ((typeof a === 'number' || typeof a === 'bigint') && (typeof b === 'number' || typeof b === 'bigint')) {
4726
+ return Number(a) - Number(b);
4727
+ }
4728
+ const sa = String(a);
4729
+ const sb = String(b);
4730
+ return sa > sb ? 1 : sa < sb ? -1 : 0;
4731
+ }
4732
+
4654
4733
  /** @import { ObjectStore, Store } from '../Store/index.mjs' */
4734
+ /** @import * as Layout from '../Layout/index.mjs' */
4655
4735
 
4656
4736
  /**
4657
4737
  *
@@ -4660,14 +4740,21 @@
4660
4740
  * @param {ObjectStore} store
4661
4741
  * @param {Environment} env
4662
4742
  * @param {(next: Node | null, env: any) => () => void} renderItem
4743
+ * @param {Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value} [sort]
4663
4744
  */
4664
- function renderObject(parent, next, store, env, renderItem) {
4745
+ function renderObject(parent, next, store, env, renderItem, sort) {
4665
4746
  /** @type {(() => void)[]} */
4666
4747
  const children = [];
4667
- /** @type {[string, Store<any, any>, number][]} */
4668
- const childStores = [...store].map(([k,v], i) => [k,v,i]);
4748
+ const childStores = [...store];
4669
4749
  const count = childStores.length;
4670
- for (const [key, child, index] of childStores) {
4750
+ /** @type {[string, Store<any, any>, number][]} */
4751
+ const stores = sort
4752
+ ? childStores
4753
+ .map(([k,v]) => [k,v,env.setStore(v, store).exec(sort)])
4754
+ .sort(([,,a], [,,b]) => compare(a, b))
4755
+ .map(([k,v], i) => [k,v,i])
4756
+ : childStores.map(([k,v], i) => [k,v,i]);
4757
+ for (const [key, child, index] of stores) {
4671
4758
  children.push(renderItem(next, env.setStore(child, store, {
4672
4759
  get count() { return count; },
4673
4760
  get key() { return key; },
@@ -4683,6 +4770,8 @@
4683
4770
  };
4684
4771
  }
4685
4772
 
4773
+ /** @import * as Layout from '../Layout/index.mjs' */
4774
+
4686
4775
  /**
4687
4776
  *
4688
4777
  * @param {Element} parent
@@ -4690,24 +4779,38 @@
4690
4779
  * @param {() => any} getter
4691
4780
  * @param {Environment} env
4692
4781
  * @param {(next: Node | null, env: any) => () => void} renderItem
4782
+ * @param {Layout.Node.Name | Layout.Node.Calc | Layout.Node.Value} [sort]
4693
4783
  */
4694
- function renderEnum(parent, next, getter, env, renderItem) {
4784
+ function renderEnum(parent, next, getter, env, renderItem, sort) {
4695
4785
 
4696
- /** @type {Signal.Computed<[value: any, index: number, kKey: any][]>} */
4786
+ /** @type {Signal.Computed<[value: any, kKey: any][]>} */
4697
4787
  const list = new exports.Signal.Computed(() => {
4698
4788
  const values = getter();
4699
4789
  if (typeof values === 'number') {
4700
4790
  const n = Math.floor(values);
4701
4791
  if (!n) { return []; }
4702
- return Array(n).fill(0).map((_, i) => [i + 1, i, i]);
4792
+ return Array(n).fill(0).map((_, i) => [i + 1, i]);
4703
4793
  }
4704
4794
  if (!values || typeof values !== 'object') { return []; }
4705
4795
  if (Array.isArray(values)) {
4706
- return values.map((v, i) => [v, i, i]);
4796
+ return values.map((v, i) => [v, i]);
4707
4797
  }
4708
4798
  // TODO: 转列表
4709
- return Object.entries(values).map(([k,v], i) => [v, i, k]);
4799
+ return Object.entries(values).map(([k,v]) => [v, k]);
4710
4800
  });
4801
+ /** @type {Signal.Computed<[value: any, index: number, kKey: any][]>} */
4802
+ const slotted = sort ? new exports.Signal.Computed(() => {
4803
+ const values = list.get();
4804
+ return values
4805
+ .map(([k,v], i) => [v, k, env.setObject({
4806
+ get count() { return values.length },
4807
+ get key() { return k; },
4808
+ get item() { return v; },
4809
+ get index() { return i; },
4810
+ }).exec(sort)])
4811
+ .sort(([,,a], [,,b]) => compare(a, b))
4812
+ .map(([k, v], i) => [v, i, k]);
4813
+ }) : new exports.Signal.Computed(() => list.get().map(([k,v], i) => [v, i, k]));
4711
4814
  const start = parent.insertBefore(document.createComment(''), next);
4712
4815
  /** @type {[Comment, Comment, () => void, key: any, Signal.State<any>, Signal.State<any>][]} */
4713
4816
  let seMap = [];
@@ -4720,7 +4823,7 @@
4720
4823
  }
4721
4824
  }
4722
4825
  const count = new exports.Signal.State(0);
4723
- const childrenResult = watch(() => list.get(), function render(children) {
4826
+ const childrenResult = watch(() => slotted.get(), function render(children) {
4724
4827
  if (!start.parentNode) { return; }
4725
4828
  let nextNode = start.nextSibling;
4726
4829
  const oldSeMap = seMap;
@@ -5091,13 +5194,13 @@
5091
5194
  /** @type {(next: Node | null, env: any) => () => void} */
5092
5195
  const r = (next, env) => renderChildren(layout.children, parent, next, env, templates, componentPath, enhancements, relate, getComponent);
5093
5196
  if (list instanceof ArrayStore) {
5094
- return renderArray(parent, next, list, env, r);
5197
+ return renderArray(parent, next, list, env, r, layout.sort);
5095
5198
  }
5096
5199
  if (list instanceof ObjectStore) {
5097
- return renderObject(parent, next, list, env, r);
5200
+ return renderObject(parent, next, list, env, r, layout.sort);
5098
5201
  }
5099
5202
  if (typeof list === 'function') {
5100
- return renderEnum(parent, next, list, env, r);
5203
+ return renderEnum(parent, next, list, env, r, layout.sort);
5101
5204
  }
5102
5205
  return () => { };
5103
5206
  }