@neeloong/form 0.20.0 → 0.21.1

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/index.full.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /*!
2
- * @neeloong/form v0.20.0
3
- * (c) 2024-2025 Fierflame
2
+ * @neeloong/form v0.21.1
3
+ * (c) 2024-2026 Fierflame
4
4
  * @license Apache-2.0
5
5
  */
6
6
 
@@ -729,7 +729,6 @@
729
729
  * @param {string | number | null} [options.index]
730
730
  * @param {boolean} [options.new]
731
731
  * @param {(value: any, index: any, store: Store) => void} [options.onUpdate]
732
- * @param {(value: any, index: any, store: Store) => void} [options.onUpdateState]
733
732
  */
734
733
  function create(schema, options) {
735
734
  const type = schema.type;
@@ -860,13 +859,14 @@
860
859
  *
861
860
  * @template {Store} T
862
861
  * @param {T} store
863
- * @param {((store: T) => any) | any} def
862
+ * @param {((store: T, value?: any) => any) | any} def
863
+ * @returns {(value?: any) => unknown}
864
864
  */
865
865
  function makeDefault(store, def) {
866
866
  if (typeof def !== 'function') {
867
867
  return () => structuredClone(def);
868
868
  }
869
- return () => structuredClone(def(store));
869
+ return (value) => structuredClone(def(store, value));
870
870
  }
871
871
 
872
872
  /** @import { Ref } from './ref.mjs' */
@@ -879,7 +879,7 @@
879
879
  */
880
880
  class Store {
881
881
  /** @type {Map<string, Set<(value: any, store: any) => void | boolean | null>>} */
882
- #events = new Map()
882
+ #events = new Map();
883
883
  /**
884
884
  * 触发事件并通知监听器
885
885
  * @template {keyof Schema.Events} K
@@ -912,7 +912,7 @@
912
912
  events.set(key, set);
913
913
  }
914
914
  set.add(fn);
915
- return () => { set?.delete(fn); }
915
+ return () => { set?.delete(fn); };
916
916
 
917
917
  }
918
918
  /**
@@ -923,7 +923,7 @@
923
923
  * @param {boolean} [options.new] 是否为新建环境
924
924
  */
925
925
  static create(schema, options = {}) {
926
- return create({type: schema}, { ...options, parent: null });
926
+ return create({ type: schema }, { ...options, parent: null });
927
927
  }
928
928
  /**
929
929
  * 设置自定义类型的存储类
@@ -945,8 +945,7 @@
945
945
  * @param {Schema.Field<M>} schema 字段的 Schema 定义
946
946
  * @param {object} [options] 可选配置
947
947
  * @param {Store?} [options.parent]
948
- * @param {((store: Store) => any) | any} [options.default]
949
- * @param {*} [options.state]
948
+ * @param {((store: Store, value?: any) => any) | object | number | string | boolean | null | undefined} [options.default]
950
949
  * @param {number | string | null} [options.index]
951
950
  * @param {number | Signal.State<number> | Signal.Computed<number>} [options.size]
952
951
  * @param {boolean} [options.null]
@@ -974,22 +973,19 @@
974
973
  * @param {Ref?} [options.ref]
975
974
  *
976
975
  * @param {((value: any) => any)?} [options.setValue]
977
- * @param {((value: any) => any)?} [options.setState]
978
- * @param {((value: any, state: any) => [value: any, state: any])?} [options.convert]
976
+ * @param {((value: any) => any)?} [options.convert]
979
977
  *
980
978
  * @param {((value: T?, index: any, store: Store) => void)?} [options.onUpdate]
981
- * @param {((value: T?, index: any, store: Store) => void)?} [options.onUpdateState]
982
979
  */
983
980
  constructor(schema, {
984
- null: isNull, state, ref, default: defaultValue,
985
- setValue, setState, convert, onUpdate, onUpdateState,
981
+ null: isNull, ref, default: defaultValue,
982
+ setValue, convert, onUpdate,
986
983
  validator, validators,
987
984
  index, size, new: isNew, parent: parentNode,
988
985
  hidden, clearable, required, disabled, readonly, removable,
989
986
  label, description, placeholder, min, max, step, minLength, maxLength, pattern, values: values$1
990
987
  } = {}) {
991
988
  this.schema = schema;
992
- this.#state.set(typeof state === 'object' && state || {});
993
989
  const parent = parentNode instanceof Store ? parentNode : null;
994
990
  if (parent) {
995
991
  this.#parent = parent;
@@ -1033,7 +1029,7 @@
1033
1029
  const s = selfReadonly.get();
1034
1030
  return s === null ? readonlyScript.get() : s;
1035
1031
  };
1036
- const readonlyParent = parent ? parent.#readonly : null;
1032
+ const readonlyParent = parent ? parent.#readonly : null;
1037
1033
  this.#selfReadonly = selfReadonly;
1038
1034
  this.#readonly = readonlyParent
1039
1035
  ? new exports.Signal.Computed(() => readonlyParent.get() || getReadonly())
@@ -1062,15 +1058,15 @@
1062
1058
 
1063
1059
  const [changed, changedResult, cancelChange] = createAsyncValidator(this, schema.validators?.change, validators?.change);
1064
1060
  const [blurred, blurredResult, cancelBlur] = createAsyncValidator(this, schema.validators?.blur, validators?.blur);
1065
- this.listen('change', () => {changed();});
1066
- this.listen('blur', () => {blurred();});
1061
+ this.listen('change', () => { changed(); });
1062
+ this.listen('blur', () => { blurred(); });
1067
1063
  this.#errors = merge(validatorResult, changedResult, blurredResult);
1068
1064
  this.#validatorResult = validatorResult;
1069
1065
  this.#changed = changed;
1070
1066
  this.#blurred = blurred;
1071
1067
  this.#cancelChange = cancelChange;
1072
1068
  this.#cancelBlur = cancelBlur;
1073
-
1069
+
1074
1070
  if (size instanceof exports.Signal.State || size instanceof exports.Signal.Computed) {
1075
1071
  this.#size = size;
1076
1072
  } else {
@@ -1084,30 +1080,25 @@
1084
1080
  }
1085
1081
  this.#ref = ref || null;
1086
1082
  this.#onUpdate = onUpdate || null;
1087
- this.#onUpdateState = onUpdateState || null;
1088
1083
  this.#setValue = typeof setValue === 'function' ? setValue : null;
1089
- this.#setState = typeof setState === 'function' ? setState : null;
1090
1084
  this.#convert = typeof convert === 'function' ? convert : null;
1091
1085
  this.#index.set(index ?? '');
1092
-
1086
+
1093
1087
  for (const [k, f] of Object.entries(schema.events || {})) {
1094
1088
  if (typeof f !== 'function') { continue; }
1095
1089
  // @ts-ignore
1096
1090
  this.listen(k, f);
1097
1091
  }
1098
1092
  }
1099
- #createDefault
1100
- createDefault() { return this.#createDefault(); }
1093
+ #createDefault;
1094
+ /** @param {any} [value] @returns {any} */
1095
+ createDefault(value) { return this.#createDefault(value); }
1101
1096
  /** @type {((value: any) => any)?} */
1102
- #setValue = null
1097
+ #setValue = null;
1103
1098
  /** @type {((value: any) => any)?} */
1104
- #setState = null
1105
- /** @type {((value: any, state: any) => [value: any, state: any])?} */
1106
- #convert = null
1099
+ #convert = null;
1107
1100
  /** @type {((value: any, index: any, store: Store) => void)?} */
1108
- #onUpdate = null
1109
- /** @type {((value: any, index: any, store: Store) => void)?} */
1110
- #onUpdateState = null
1101
+ #onUpdate = null;
1111
1102
  /** @readonly @type {Store?} */
1112
1103
  #parent = null;
1113
1104
  /** @readonly @type {Store} */
@@ -1119,7 +1110,7 @@
1119
1110
  /** @readonly @type {any} */
1120
1111
  #component;
1121
1112
  /** @type {Signal.State<boolean>?} */
1122
- #selfLoading = null
1113
+ #selfLoading = null;
1123
1114
  /** @type {Signal.State<boolean>} */
1124
1115
  #loading;
1125
1116
  get loading() {
@@ -1127,7 +1118,7 @@
1127
1118
  }
1128
1119
  set loading(loading) {
1129
1120
  const s = this.#selfLoading;
1130
- if (!s) { return }
1121
+ if (!s) { return; }
1131
1122
  s.set(Boolean(loading));
1132
1123
  }
1133
1124
  /** 存储对象自身 */
@@ -1166,9 +1157,9 @@
1166
1157
  get immutable() { return this.#immutable; }
1167
1158
 
1168
1159
  /** @readonly @type {Signal.Computed<boolean>} */
1169
- #new
1160
+ #new;
1170
1161
  /** @readonly @type {Signal.State<boolean>} */
1171
- #selfNew
1162
+ #selfNew;
1172
1163
  get selfNew() { return this.#selfNew.get(); }
1173
1164
  set selfNew(v) { this.#selfNew.set(Boolean(v)); }
1174
1165
  /** 是否新建项 */
@@ -1176,9 +1167,9 @@
1176
1167
  set new(v) { this.#selfNew.set(Boolean(v)); }
1177
1168
 
1178
1169
  /** @readonly @type {Signal.State<boolean?>} */
1179
- #selfHidden
1170
+ #selfHidden;
1180
1171
  /** @readonly @type {Signal.Computed<boolean>} */
1181
- #hidden
1172
+ #hidden;
1182
1173
  get selfHidden() { return this.#selfHidden.get(); }
1183
1174
  set selfHidden(v) { this.#selfHidden.set(typeof v === 'boolean' ? v : null); }
1184
1175
  /** 是否可隐藏 */
@@ -1186,9 +1177,9 @@
1186
1177
  set hidden(v) { this.#selfHidden.set(typeof v === 'boolean' ? v : null); }
1187
1178
 
1188
1179
  /** @readonly @type {Signal.State<boolean?>} */
1189
- #selfClearable
1180
+ #selfClearable;
1190
1181
  /** @readonly @type {Signal.Computed<boolean>} */
1191
- #clearable
1182
+ #clearable;
1192
1183
  get selfClearable() { return this.#selfClearable.get(); }
1193
1184
  set selfClearable(v) { this.#selfClearable.set(typeof v === 'boolean' ? v : null); }
1194
1185
  /** 是否可清除 */
@@ -1196,9 +1187,9 @@
1196
1187
  set clearable(v) { this.#selfClearable.set(typeof v === 'boolean' ? v : null); }
1197
1188
 
1198
1189
  /** @readonly @type {Signal.State<boolean?>} */
1199
- #selfRequired
1190
+ #selfRequired;
1200
1191
  /** @readonly @type {Signal.Computed<boolean>} */
1201
- #required
1192
+ #required;
1202
1193
  get selfRequired() { return this.#selfRequired.get(); }
1203
1194
  set selfRequired(v) { this.#selfRequired.set(typeof v === 'boolean' ? v : null); }
1204
1195
  /** 是否必填 */
@@ -1206,9 +1197,9 @@
1206
1197
  set required(v) { this.#selfRequired.set(typeof v === 'boolean' ? v : null); }
1207
1198
 
1208
1199
  /** @readonly @type {Signal.State<boolean?>} */
1209
- #selfDisabled
1200
+ #selfDisabled;
1210
1201
  /** @readonly @type {Signal.Computed<boolean>} */
1211
- #disabled
1202
+ #disabled;
1212
1203
  get selfDisabled() { return this.#selfDisabled.get(); }
1213
1204
  set selfDisabled(v) { this.#selfDisabled.set(typeof v === 'boolean' ? v : null); }
1214
1205
  /** 是否禁用字段 */
@@ -1216,9 +1207,9 @@
1216
1207
  set disabled(v) { this.#selfDisabled.set(typeof v === 'boolean' ? v : null); }
1217
1208
 
1218
1209
  /** @readonly @type {Signal.State<boolean?>} */
1219
- #selfReadonly
1210
+ #selfReadonly;
1220
1211
  /** @readonly @type {Signal.Computed<boolean>} */
1221
- #readonly
1212
+ #readonly;
1222
1213
  get selfReadonly() { return this.#selfReadonly.get(); }
1223
1214
  set selfReadonly(v) { this.#selfReadonly.set(typeof v === 'boolean' ? v : null); }
1224
1215
  /** 是否只读 */
@@ -1227,9 +1218,9 @@
1227
1218
 
1228
1219
 
1229
1220
  /** @readonly @type {Signal.State<boolean?>} */
1230
- #selfRemovable
1221
+ #selfRemovable;
1231
1222
  /** @readonly @type {Signal.Computed<boolean>} */
1232
- #removable
1223
+ #removable;
1233
1224
  get selfRemovable() { return this.#selfRemovable.get(); }
1234
1225
  set selfRemovable(v) { this.#selfRemovable.set(typeof v === 'boolean' ? v : null); }
1235
1226
  /** 是否只读 */
@@ -1238,9 +1229,9 @@
1238
1229
 
1239
1230
 
1240
1231
  /** @readonly @type {Signal.State<string?>} */
1241
- #selfLabel
1232
+ #selfLabel;
1242
1233
  /** @readonly @type {Signal.Computed<string?>} */
1243
- #label
1234
+ #label;
1244
1235
  get selfLabel() { return this.#selfLabel.get(); }
1245
1236
  set selfLabel(v) { this.#selfLabel.set(string(v)); }
1246
1237
  /** 字段的标签信息 */
@@ -1249,9 +1240,9 @@
1249
1240
 
1250
1241
 
1251
1242
  /** @readonly @type {Signal.State<string?>} */
1252
- #selfDescription
1243
+ #selfDescription;
1253
1244
  /** @readonly @type {Signal.Computed<string?>} */
1254
- #description
1245
+ #description;
1255
1246
  get selfDescription() { return this.#selfDescription.get(); }
1256
1247
  set selfDescription(v) { this.#selfDescription.set(string(v)); }
1257
1248
  /** 字段的描述信息 */
@@ -1259,9 +1250,9 @@
1259
1250
  set description(v) { this.#selfDescription.set(string(v)); }
1260
1251
 
1261
1252
  /** @readonly @type {Signal.State<string?>} */
1262
- #selfPlaceholder
1253
+ #selfPlaceholder;
1263
1254
  /** @readonly @type {Signal.Computed<string?>} */
1264
- #placeholder
1255
+ #placeholder;
1265
1256
  get selfPlaceholder() { return this.#selfPlaceholder.get(); }
1266
1257
  set selfPlaceholder(v) { this.#selfPlaceholder.set(string(v)); }
1267
1258
  /** 字段的占位符信息 */
@@ -1270,9 +1261,9 @@
1270
1261
 
1271
1262
 
1272
1263
  /** @readonly @type {Signal.State<number?>} */
1273
- #selfMin
1264
+ #selfMin;
1274
1265
  /** @readonly @type {Signal.Computed<number?>} */
1275
- #min
1266
+ #min;
1276
1267
  get selfMin() { return this.#selfMin.get(); }
1277
1268
  set selfMin(v) { this.#selfMin.set(number(v)); }
1278
1269
  /** 数值字段的最小值限制 */
@@ -1281,9 +1272,9 @@
1281
1272
 
1282
1273
 
1283
1274
  /** @readonly @type {Signal.State<number?>} */
1284
- #selfMax
1275
+ #selfMax;
1285
1276
  /** @readonly @type {Signal.Computed<number?>} */
1286
- #max
1277
+ #max;
1287
1278
  get selfMax() { return this.#selfMax.get(); }
1288
1279
  set selfMax(v) { this.#selfMax.set(number(v)); }
1289
1280
  /** 数值字段的最大值限制 */
@@ -1292,9 +1283,9 @@
1292
1283
 
1293
1284
 
1294
1285
  /** @readonly @type {Signal.State<number?>} */
1295
- #selfStep
1286
+ #selfStep;
1296
1287
  /** @readonly @type {Signal.Computed<number?>} */
1297
- #step
1288
+ #step;
1298
1289
  get selfStep() { return this.#selfStep.get(); }
1299
1290
  set selfStep(v) { this.#selfStep.set(number(v)); }
1300
1291
  /** 数值字段的步长 */
@@ -1302,9 +1293,9 @@
1302
1293
  set step(v) { this.#selfStep.set(number(v)); }
1303
1294
 
1304
1295
  /** @readonly @type {Signal.State<number?>} */
1305
- #selfMinLength
1296
+ #selfMinLength;
1306
1297
  /** @readonly @type {Signal.Computed<number?>} */
1307
- #minLength
1298
+ #minLength;
1308
1299
  get selfMinLength() { return this.#selfMinLength.get(); }
1309
1300
  set selfMinLength(v) { this.#selfMinLength.set(number(v)); }
1310
1301
  /** 最小长度 */
@@ -1312,9 +1303,9 @@
1312
1303
  set minLength(v) { this.#selfMinLength.set(number(v)); }
1313
1304
 
1314
1305
  /** @readonly @type {Signal.State<number?>} */
1315
- #selfMaxLength
1306
+ #selfMaxLength;
1316
1307
  /** @readonly @type {Signal.Computed<number?>} */
1317
- #maxLength
1308
+ #maxLength;
1318
1309
  get selfMaxLength() { return this.#selfMaxLength.get(); }
1319
1310
  set selfMaxLength(v) { this.#selfMaxLength.set(number(v)); }
1320
1311
  /** 最大长度 */
@@ -1322,9 +1313,9 @@
1322
1313
  set maxLength(v) { this.#selfMaxLength.set(number(v)); }
1323
1314
 
1324
1315
  /** @readonly @type {Signal.State<RegExp?>} */
1325
- #selfPattern
1316
+ #selfPattern;
1326
1317
  /** @readonly @type {Signal.Computed<RegExp?>} */
1327
- #pattern
1318
+ #pattern;
1328
1319
  get selfPattern() { return this.#selfPattern.get(); }
1329
1320
  set selfPattern(v) { this.#selfPattern.set(regex(v)); }
1330
1321
  /** 模式 */
@@ -1333,9 +1324,9 @@
1333
1324
 
1334
1325
 
1335
1326
  /** @readonly @type {Signal.State<(Schema.Value.Group | Schema.Value)[] | null>} */
1336
- #selfValues
1327
+ #selfValues;
1337
1328
  /** @readonly @type {Signal.Computed<(Schema.Value.Group | Schema.Value)[] | null>} */
1338
- #values
1329
+ #values;
1339
1330
  get selfValues() { return this.#selfValues.get(); }
1340
1331
  set selfValues(v) { this.#selfValues.set(values(v)); }
1341
1332
  /** 可选值列表 */
@@ -1344,24 +1335,24 @@
1344
1335
 
1345
1336
 
1346
1337
  /** @type {Signal.Computed<string[]>} */
1347
- #errors
1338
+ #errors;
1348
1339
  /** @type {Signal.Computed<string[]>} */
1349
- #validatorResult
1340
+ #validatorResult;
1350
1341
  /** @type {() => Promise<string[]>} */
1351
- #changed
1342
+ #changed;
1352
1343
  /** @type {() => Promise<string[]>} */
1353
- #blurred
1344
+ #blurred;
1354
1345
  /** @type {() => void} */
1355
- #cancelChange
1346
+ #cancelChange;
1356
1347
  /** @type {() => void} */
1357
- #cancelBlur
1348
+ #cancelBlur;
1358
1349
  /** 所有校验错误列表 */
1359
1350
  get errors() { return this.#errors.get(); }
1360
1351
  /** 字段校验错误信息 */
1361
1352
  get error() { return this.#errors.get()[0]; }
1362
1353
 
1363
1354
  /** @returns {IterableIterator<[key: string | number, value: Store]>} */
1364
- *[Symbol.iterator]() {}
1355
+ *[Symbol.iterator]() { }
1365
1356
  /**
1366
1357
  * 获取子存储
1367
1358
  * @param {string | number} key
@@ -1373,11 +1364,9 @@
1373
1364
  #initValue = new exports.Signal.State(/** @type {T?} */(null));
1374
1365
  #value = new exports.Signal.State(this.#initValue.get());
1375
1366
 
1376
-
1377
- #state = new exports.Signal.State(/** @type {any} */(null));
1378
1367
 
1379
1368
  /** 内容是否已改变 */
1380
- get changed() { return this.#value.get() === this.#initValue.get(); }
1369
+ get changed() { return !Object.is(this.#value.get(), this.#initValue.get()); }
1381
1370
 
1382
1371
  /** 字段当前值 */
1383
1372
  get value() { return this.#value.get(); }
@@ -1392,34 +1381,26 @@
1392
1381
  this.#requestUpdate();
1393
1382
  }
1394
1383
 
1395
- /** 字段状态 */
1396
- get state() { return this.#state.get(); }
1397
- set state(v) {
1398
- const newState = this.#setState?.(v);
1399
- const sta = newState === undefined ? v : newState;
1400
- this.#state.set(sta);
1401
- this.#onUpdateState?.(sta, this.#index.get(), this);
1402
- this.#requestUpdate();
1403
- }
1404
1384
  #requestUpdate() {
1405
1385
  if (this.#needUpdate) { return; }
1406
1386
  this.#needUpdate = true;
1407
1387
  queueMicrotask(() => {
1408
1388
  const oldValue = this.#value.get();
1409
- const oldState = this.#state.get();
1410
- this.#runUpdate(oldValue, oldState);
1389
+ this.#runUpdate(oldValue);
1411
1390
  });
1412
1391
  }
1413
1392
  /** 重置数据 */
1414
- reset(value = this.#initValue.get()) {
1415
- this.#reset(value);
1393
+ reset(value = this.#set ? this.#initValue.get() : this.#createDefault(), isNew = this.#selfNew.get()) {
1394
+ this.#reset(value, Boolean(isNew));
1416
1395
  }
1417
1396
  /**
1418
1397
  *
1419
1398
  * @param {*} v
1399
+ * @param {boolean} isNew
1420
1400
  * @returns
1421
1401
  */
1422
- #reset(v) {
1402
+ #reset(v, isNew) {
1403
+ this.#selfNew.set(isNew);
1423
1404
  const newValue = this.#setValue?.(v);
1424
1405
  const value = newValue === undefined ? v : newValue;
1425
1406
  this.#cancelChange();
@@ -1427,16 +1408,17 @@
1427
1408
  this.#set = true;
1428
1409
  if (!value || typeof value !== 'object') {
1429
1410
  for (const [, field] of this) {
1430
- field.#reset(null);
1411
+ field.#reset(null, false);
1431
1412
  }
1432
1413
  this.#value.set(value);
1433
1414
  this.#initValue.set(value);
1415
+ this.#onUpdate?.(value, this.#index.get(), this);
1434
1416
  return value;
1435
1417
  }
1436
1418
  /** @type {*} */
1437
- const newValues = Array.isArray(value) ? [...value] : {...value};
1419
+ const newValues = Array.isArray(value) ? [...value] : { ...value };
1438
1420
  for (const [key, field] of this) {
1439
- newValues[key] = field.#reset(newValues[key]);
1421
+ newValues[key] = field.#reset(Object.hasOwn(newValues, key) ? newValues[key] : undefined, false);
1440
1422
  }
1441
1423
  this.#value.set(newValues);
1442
1424
  this.#initValue.set(newValues);
@@ -1449,59 +1431,48 @@
1449
1431
  /**
1450
1432
  *
1451
1433
  * @param {T} value
1452
- * @param {*} state
1453
1434
  * @returns
1454
1435
  */
1455
- #toUpdate(value, state) {
1456
- const [val,sta] = this.#convert?.(value, state) || [value, state];
1457
- if(this.#value.get() === val && this.#state.get() === sta) { return [val,sta] }
1458
- this.#value.set(val);
1459
- this.#state.set(sta);
1460
- return this.#runUpdate(val, sta);
1436
+ #toUpdate(value) {
1437
+ let val = this.#convert?.(value) ?? value;
1438
+ if (val === undefined) { val = value; }
1439
+ if (Object.is(this.#value.get(), val)) { return val; }
1440
+ return this.#runUpdate(val);
1461
1441
  }
1462
1442
  /**
1463
1443
  *
1464
1444
  * @param {*} val
1465
- * @param {*} sta
1466
1445
  * @returns {[any, any]}
1467
1446
  */
1468
- #runUpdate(val, sta) {
1447
+ #runUpdate(val) {
1469
1448
  this.#needUpdate = false;
1470
1449
  let initValue = val;
1471
1450
  if (val && typeof val === 'object') {
1472
1451
  /** @type {T} */
1473
1452
  // @ts-ignore
1474
- let newValues = Array.isArray(val) ? [...val] : {...val};
1475
- let newStates = Array.isArray(val) ? Array.isArray(sta) ? [...sta] : [] : {...sta};
1453
+ let newValues = Array.isArray(val) ? [...val] : { ...val };
1476
1454
  let updated = false;
1477
1455
  for (const [key, field] of this) {
1478
1456
  // @ts-ignore
1479
- const data = val[key];
1480
- const state = sta?.[key];
1481
- const [newData, newState] = field.#toUpdate(data, state);
1482
- if (data !== newData) {
1483
- // @ts-ignore
1484
- newValues[key] = newData;
1485
- updated = true;
1486
- }
1487
- if (state !== newState) {
1488
- newStates[key] = newState;
1489
- updated = true;
1490
- }
1457
+ const data = Object.hasOwn(val, key) ? val[key] : undefined;
1458
+ const newData = field.#toUpdate(data);
1459
+ if (Object.is(data, newData)) { continue; }
1460
+ // @ts-ignore
1461
+ newValues[key] = newData;
1462
+ updated = true;
1491
1463
  }
1492
1464
  if (updated) {
1493
1465
  val = newValues;
1494
- sta = newStates;
1495
1466
  initValue = val;
1496
1467
  this.#value.set(val);
1497
- this.#state.set(newStates);
1498
1468
  }
1499
1469
  }
1500
1470
  if (!this.#set) {
1501
1471
  this.#set = true;
1502
1472
  this.#initValue.set(initValue);
1503
1473
  }
1504
- return [val, sta];
1474
+ this.#value.set(val);
1475
+ return val;
1505
1476
  }
1506
1477
  /**
1507
1478
  * 异步校验
@@ -1525,18 +1496,18 @@
1525
1496
  return Promise.all([this.#validatorResult.get(), this.#changed(), this.#blurred()])
1526
1497
  .then(v => {
1527
1498
  const errors = v.flat();
1528
- return errors.length ? errors : null
1499
+ return errors.length ? errors : null;
1529
1500
  });
1530
1501
  }
1531
1502
  const selfPath = Array.isArray(path) ? path : [];
1532
- const list = [this.validate().then(errors => {
1533
- if (!errors?.length) {return [];}
1534
- return [{path: [...selfPath], store: /** @type {Store} */(this), errors}]
1503
+ const list = [this.validate(true).then(errors => {
1504
+ if (!errors?.length) { return []; }
1505
+ return [{ path: [...selfPath], store: /** @type {Store} */(this), errors }];
1535
1506
  })];
1536
1507
  for (const [key, field] of this) {
1537
1508
  list.push(field.validate([...selfPath, key]));
1538
1509
  }
1539
- return Promise.all(list).then(v => v.flat())
1510
+ return Promise.all(list).then(v => v.flat());
1540
1511
  }
1541
1512
  }
1542
1513
 
@@ -1550,8 +1521,8 @@
1550
1521
  class ObjectStore extends Store {
1551
1522
  get kind() { return 'object'; }
1552
1523
  /** @type {Record<string, Store>} */
1553
- #children
1554
- *[Symbol.iterator]() {yield* Object.entries(this.#children);}
1524
+ #children;
1525
+ *[Symbol.iterator]() { yield* Object.entries(this.#children); }
1555
1526
  /**
1556
1527
  *
1557
1528
  * @param {string | number} key
@@ -1565,26 +1536,25 @@
1565
1536
  * @param {number | string | null} [options.index]
1566
1537
  * @param {boolean} [options.new]
1567
1538
  * @param {((value: T?, index: any, store: Store) => void)?} [options.onUpdate]
1568
- * @param {((value: T?, index: any, store: Store) => void)?} [options.onUpdateState]
1569
1539
  */
1570
- constructor(schema,{ parent, index, new: isNew, onUpdate, onUpdateState } = {}) {
1540
+ constructor(schema, { parent, index, new: isNew, onUpdate } = {}) {
1571
1541
  const childrenTypes = Object.entries(schema.type);
1572
1542
  /** @type {Record<string, Store>} */
1573
1543
  const children = Object.create(null);
1574
1544
  super(schema, {
1575
- parent, index, new: isNew, onUpdate, onUpdateState,
1545
+ parent, index, new: isNew, onUpdate,
1576
1546
  size: childrenTypes.length,
1577
1547
  setValue(v) { return typeof v === 'object' ? v : null; },
1578
- setState(v) { return typeof v === 'object' ? v : null; },
1579
- convert(v, state) {
1580
- return [
1581
- typeof v === 'object' ? v : {},
1582
- typeof state === 'object' ? state : {},
1583
- ]
1548
+ convert(v) {
1549
+ return typeof v === 'object' ? v : {};
1584
1550
  },
1585
- default: schema.default ?? (() => Object.fromEntries(
1586
- Object.entries(children).map(([k,v]) => [k, v.createDefault()])
1587
- )),
1551
+ default: schema.default ?? ((store, value) => {
1552
+ const list = Object.entries(children);
1553
+ let obj = value;
1554
+ if (!obj || typeof obj !== 'object') { obj = schema.default; }
1555
+ if (!obj || typeof obj !== 'object') { obj = {}; }
1556
+ return Object.fromEntries(list.map(([k, v]) => [k, v.createDefault(Object.hasOwn(obj, k) ? obj[k] : null)]));
1557
+ }),
1588
1558
  });
1589
1559
  const childCommonOptions = {
1590
1560
  parent: this,
@@ -1592,17 +1562,12 @@
1592
1562
  onUpdate: (value, index, store) => {
1593
1563
  if (store !== this.#children[index]) { return; }
1594
1564
  // @ts-ignore
1595
- this.value = {...this.value, [index]: value};
1565
+ this.value = { ...this.value, [index]: value };
1596
1566
  },
1597
- /** @param {*} state @param {*} index @param {Store} store */
1598
- onUpdateState: (state, index, store) => {
1599
- if (store !== this.#children[index]) { return; }
1600
- this.state = {...this.state, [index]: state};
1601
- }
1602
1567
  };
1603
1568
 
1604
1569
  for (const [index, field] of childrenTypes) {
1605
- children[index] = create(field, {...childCommonOptions, index});
1570
+ children[index] = create(field, { ...childCommonOptions, index });
1606
1571
  }
1607
1572
  this.#children = children;
1608
1573
  }
@@ -1647,9 +1612,8 @@
1647
1612
  * @param {boolean} [options.new]
1648
1613
  * @param {boolean} [options.addable]
1649
1614
  * @param {(value: any, index: any, store: Store) => void} [options.onUpdate]
1650
- * @param {(value: any, index: any, store: Store) => void} [options.onUpdateState]
1651
1615
  */
1652
- constructor(schema, { parent, onUpdate, onUpdateState, index, new: isNew, addable } = {}) {
1616
+ constructor(schema, { parent, onUpdate, index, new: isNew, addable } = {}) {
1653
1617
  const childrenState = new exports.Signal.State(/** @type {Store[]} */([]));
1654
1618
  // @ts-ignore
1655
1619
  const updateChildren = (list) => {
@@ -1668,22 +1632,16 @@
1668
1632
  super(schema, {
1669
1633
  index, new: isNew, parent,
1670
1634
  size: new exports.Signal.Computed(() => childrenState.get().length),
1671
- state: [],
1672
1635
  setValue(v) { return Array.isArray(v) ? v : v == null ? null : [v]; },
1673
- setState(v) { return Array.isArray(v) ? v : v == null ? null : [v]; },
1674
- convert(v, state) {
1636
+ convert(v) {
1675
1637
  const val = Array.isArray(v) ? v : v == null ? null : [v];
1676
1638
  updateChildren(val);
1677
- return [
1678
- val,
1679
- (Array.isArray(state) ? state : v == null ? [] : [state]),
1680
- ];
1639
+ return val;
1681
1640
  },
1682
- onUpdate: (value, index, state) => {
1641
+ onUpdate: (value, index, store) => {
1683
1642
  updateChildren(value);
1684
- onUpdate?.(value, index, state);
1643
+ onUpdate?.(value, index, store);
1685
1644
  },
1686
- onUpdateState,
1687
1645
  default: schema.default ?? [],
1688
1646
  });
1689
1647
 
@@ -1702,16 +1660,6 @@
1702
1660
  val[index] = value;
1703
1661
  this.value = val;
1704
1662
  },
1705
- /** @param {*} state @param {*} index @param {Store} store */
1706
- onUpdateState: (state, index, store) => {
1707
- if (childrenState.get()[index] !== store) { return; }
1708
- const sta = [...this.state || []];
1709
- if (sta.length < index) {
1710
- sta.length = index;
1711
- }
1712
- sta[index] = state;
1713
- this.state = sta;
1714
- },
1715
1663
  };
1716
1664
  this.#create = (index, isNew) => {
1717
1665
  const child = create(schema, { ...childCommonOptions, index, new: isNew });
@@ -1753,13 +1701,7 @@
1753
1701
  children[i].index = i;
1754
1702
  }
1755
1703
  const val = [...data];
1756
- val.splice(insertIndex, 0, value ?? item.createDefault());
1757
- const state = this.state;
1758
- if (Array.isArray(state)) {
1759
- const sta = [...state];
1760
- sta.splice(insertIndex, 0, {});
1761
- this.state = sta;
1762
- }
1704
+ val.splice(insertIndex, 0, item.createDefault(value));
1763
1705
  this.#children.set(children);
1764
1706
  this.value = val;
1765
1707
  return true;
@@ -1789,12 +1731,6 @@
1789
1731
  }
1790
1732
  const val = [...data];
1791
1733
  const [value] = val.splice(removeIndex, 1);
1792
- const state = this.state;
1793
- if (Array.isArray(state)) {
1794
- const sta = [...this.state];
1795
- sta.splice(removeIndex, 1);
1796
- this.state = sta;
1797
- }
1798
1734
  this.#children.set(children);
1799
1735
  this.value = val;
1800
1736
  return value;
@@ -1834,16 +1770,6 @@
1834
1770
  while (toIndex > val.length) { val.push(null); }
1835
1771
  val.splice(toIndex, 0, ...values);
1836
1772
 
1837
- const state = this.state;
1838
- if (Array.isArray(state)) {
1839
- const sta = [...state];
1840
- const states = sta.splice(from, len);
1841
- for (let i = states.length; i < len; i++) { states.push({}); }
1842
- while (toIndex > sta.length) { sta.push({}); }
1843
- sta.splice(toIndex, 0, ...states);
1844
-
1845
- this.state = sta;
1846
- }
1847
1773
  this.#children.set(children);
1848
1774
  this.value = val;
1849
1775
  return len;
@@ -1871,15 +1797,6 @@
1871
1797
  const bValue = val[b];
1872
1798
  val[b] = aValue;
1873
1799
  val[a] = bValue;
1874
- const state = this.state;
1875
- if (Array.isArray(state)) {
1876
- const sta = [...state];
1877
- const aValue = sta[a];
1878
- const bValue = sta[b];
1879
- sta[b] = aValue;
1880
- sta[a] = bValue;
1881
- this.state = sta;
1882
- }
1883
1800
  this.#children.set(children);
1884
1801
  this.value = val;
1885
1802
  return true;
@@ -3028,7 +2945,6 @@
3028
2945
  kind: true,
3029
2946
 
3030
2947
  value: true,
3031
- state: true,
3032
2948
 
3033
2949
  store: true,
3034
2950
  parent: true,
@@ -3086,7 +3002,8 @@
3086
3002
  yield [`${key}${sign}${k}`, {get: () => val[k]}];
3087
3003
  }
3088
3004
  yield [`${key}${sign}value`, {get: () => val.value, set: v => val.value = v}];
3089
- yield [`${key}${sign}state`, {get: () => val.state, set: v => val.state = v}];
3005
+ /** @deprecated */
3006
+ yield [`${key}${sign}state`, {get: () => null, set: v => {}}];
3090
3007
  yield [`${key}${sign}reset`, {exec: () => val.reset()}];
3091
3008
  // @ts-ignore
3092
3009
  yield [`${key}${sign}validate`, {exec: v => val.validate(v ? [] : null)}];
@@ -3374,6 +3291,8 @@
3374
3291
  const res = Object.fromEntries([...bindableSet].map(v => [
3375
3292
  `$${v}`, cb => watch(() => store[v], cb, true)
3376
3293
  ]));
3294
+ /** @deprecated */
3295
+ res.$$state = cb => watch(() => null, cb, true);
3377
3296
  return res;
3378
3297
  }
3379
3298
  /**
@@ -3390,13 +3309,15 @@
3390
3309
  }
3391
3310
  /** @type {Record<string, {get(): any; set?(v: any): void}> | void} */
3392
3311
  const res = Object.fromEntries([...bindableSet].map(v => [
3393
- `$${v}`, v === 'value' || v === 'state' ? {
3312
+ `$${v}`, v === 'value' ? {
3394
3313
  get: () => store[v],
3395
3314
  set: (s)=>{store[v] = s;}
3396
3315
  } : {
3397
3316
  get: () => store[v],
3398
3317
  }
3399
3318
  ]));
3319
+ /** @deprecated */
3320
+ res.$$state = { get: () => null, set: () => {} };
3400
3321
  return res;
3401
3322
  }
3402
3323
  /**
@@ -3411,7 +3332,8 @@
3411
3332
  if (!store) { return; }
3412
3333
  switch(type) {
3413
3334
  case 'value': return v => {store.value = v; };
3414
- case 'state': return v => {store.state = v; };
3335
+ /** @deprecated */
3336
+ case 'state': return v => { };
3415
3337
  }
3416
3338
  }
3417
3339
  /**
@@ -3429,7 +3351,8 @@
3429
3351
  }
3430
3352
  return {
3431
3353
  '$value': v => {store.value = v; },
3432
- '$state': v => {store.state = v; },
3354
+ /** @deprecated */
3355
+ '$state': v => { },
3433
3356
  '$input': v => {store.emit('input', v); },
3434
3357
  '$change': v => {store.emit('change', v); },
3435
3358
  '$click': v => {store.emit('click', v); },
@@ -5324,431 +5247,85 @@
5324
5247
  /** @import { Store } from '../Store/index.mjs' */
5325
5248
  /** @import { StoreLayout } from '../types.mjs' */
5326
5249
 
5250
+
5327
5251
  /**
5328
5252
  *
5329
- * @param {Store<any, any>} store
5330
- * @param {StoreLayout.Renderer} fieldRenderer
5331
- * @param {StoreLayout.Field?} layout
5332
- * @param {object} option
5333
- * @param {(string | StoreLayout.Action[])[]} option.columns
5334
- * @param {() => void} option.remove
5335
- * @param {() => void} option.dragenter
5336
- * @param {() => void} option.dragstart
5337
- * @param {() => void} option.dragend
5338
- * @param {{get(): boolean}} option.deletable
5339
- * @param {StoreLayout.Options?} options
5340
-
5341
- * @returns {[HTMLTableSectionElement, () => void]}
5253
+ * @param {string} field
5342
5254
  */
5343
- function Line(store, fieldRenderer, layout, {
5344
- columns,
5345
- remove, dragenter, dragstart, dragend, deletable
5346
- }, options) {
5347
- const root = document.createElement('tbody');
5348
- root.addEventListener('dragenter', () => {
5349
- dragenter();
5350
- });
5351
- root.addEventListener('dragstart', (event) => {
5352
- if (event.target !== event.currentTarget) { return; }
5353
- dragstart();
5354
- });
5355
- root.addEventListener('dragend', dragend);
5356
- const head = root.appendChild(document.createElement('tr'));
5357
-
5358
- /** @type {(() => void)[]} */
5359
- const destroyList = [];
5360
-
5361
-
5362
- let trigger = () => { };
5363
- /** @type {HTMLButtonElement[]} */
5364
- const triggerList = [];
5365
- if (columns.find(v => Array.isArray(v) && v.includes('trigger'))) {
5366
- const body = root.appendChild(document.createElement('tr'));
5367
- const main = body.appendChild(document.createElement('td'));
5368
- main.colSpan = columns.length;
5369
-
5370
- const [form, destroy] = Form(store, fieldRenderer, layout, options);
5371
- main.appendChild(form);
5372
- destroyList.push(destroy);
5373
- body.hidden = true;
5374
- trigger = function click() {
5375
- if (body.hidden) {
5376
- body.hidden = false;
5377
- for (const ext of triggerList) {
5378
- ext.classList.remove('NeeloongForm-table-line-open');
5379
- ext.classList.add('NeeloongForm-table-line-close');
5380
- }
5381
- } else {
5382
- body.hidden = true;
5383
- for (const ext of triggerList) {
5384
- ext.classList.remove('NeeloongForm-table-line-close');
5385
- ext.classList.add('NeeloongForm-table-line-open');
5386
- }
5387
- }
5388
- };
5389
-
5390
- }
5255
+ function createFieldFilter(field) {
5391
5256
 
5392
5257
  /**
5393
5258
  *
5394
- * @param {PointerEvent} event
5259
+ * @param {StoreLayout.Item} v
5260
+ * @returns {v is StoreLayout.Field}
5395
5261
  */
5396
- function pointerdown({ pointerId }) {
5397
- root.draggable = true;
5398
- /** @param {PointerEvent} event */
5399
- function pointerup(event) {
5400
- if (event.pointerId !== pointerId) { return; }
5401
- if (!root) { return; }
5402
- root.draggable = false;
5403
- window.removeEventListener('pointerup', pointerup, { capture: true });
5404
- window.removeEventListener('pointercancel', pointerup, { capture: true });
5405
- }
5406
- window.addEventListener('pointerup', pointerup, { capture: true });
5407
- window.addEventListener('pointercancel', pointerup, { capture: true });
5408
-
5409
- }
5410
5262
 
5411
- for (const name of columns) {
5412
- if (!Array.isArray(name)) {
5413
- const td = head.appendChild(document.createElement('td'));
5414
- const child = store.child(name);
5415
- if (!child) { continue; }
5416
- const [el, destroy] = FormField(child, fieldRenderer, null, options, true);
5417
- destroyList.push(destroy);
5418
- td.appendChild(el);
5419
- continue;
5420
- }
5421
- const handle = head.appendChild(document.createElement('th'));
5422
- handle.classList.add('NeeloongForm-table-line-handle');
5423
- for (const k of name) {
5424
- switch (k) {
5425
- case 'trigger': {
5426
- const ext = handle.appendChild(document.createElement('button'));
5427
- ext.classList.add('NeeloongForm-table-line-open');
5428
- triggerList.push(ext);
5429
- ext.addEventListener('click', trigger);
5430
- continue;
5431
- }
5432
- case 'move': {
5433
- if (!options?.editable) { continue; }
5434
- const move = handle.appendChild(document.createElement('button'));
5435
- move.classList.add('NeeloongForm-table-move');
5436
- move.addEventListener('pointerdown', pointerdown);
5437
- destroyList.push(watch(() => store.readonly || store.disabled, disabled => {
5438
- move.disabled = disabled;
5439
- }, true));
5440
- continue;
5441
- }
5442
- case 'remove': {
5443
- if (!options?.editable) { continue; }
5444
- const del = handle.appendChild(document.createElement('button'));
5445
- del.classList.add('NeeloongForm-table-remove');
5446
- del.addEventListener('click', remove);
5447
- destroyList.push(watch(() => !deletable.get() || store.readonly || store.disabled, disabled => {
5448
- del.disabled = disabled;
5449
- }, true));
5450
- continue;
5451
- }
5452
- case 'serial': {
5453
- const serial = handle.appendChild(document.createElement('span'));
5454
- serial.classList.add('NeeloongForm-table-serial');
5455
- continue;
5456
- }
5457
- }
5458
- }
5459
- }
5263
+ return function (v) {
5264
+ if (v.type && v.type !== 'field') { return false; }
5265
+ return v.field === field;
5460
5266
 
5461
- return [root, () => {
5462
- for (const destroy of destroyList) {
5463
- destroy();
5464
- }
5465
- }];
5267
+ };
5466
5268
  }
5467
-
5468
- /** @import { Store, ArrayStore } from '../Store/index.mjs' */
5469
- /** @import { StoreLayout } from '../types.mjs' */
5470
-
5471
5269
  /**
5472
5270
  *
5473
- * @param {HTMLElement} parent
5474
- * @param {({ field: string; width: any; label: any; } | StoreLayout.Action[])[]} columns
5475
- * @param {() => any} add
5476
- * @param {{get(): boolean}} addable
5477
- * @param {boolean?} [editable]
5271
+ * @param {StoreLayout.Renderer} fieldRenderer
5272
+ * @param {Store} store
5273
+ * @param {Node} node
5274
+ * @param {StoreLayout.Options?} options
5275
+ * @param {StoreLayout?} [layout]
5276
+ * @param {Node} [anchor]
5277
+ * @param {(child?: Store<any, any> | undefined) => void} [dragenter]
5478
5278
  */
5479
- function renderHead(parent, columns, add, addable, editable) {
5480
- const tr = parent.appendChild(document.createElement('tr'));
5279
+ function renderHtml(store, fieldRenderer, node, options, layout, anchor, dragenter) {
5481
5280
  /** @type {(() => void)[]} */
5482
5281
  const destroyList = [];
5483
- for (const col of columns) {
5484
- if (!Array.isArray(col)) {
5485
- const { width, label } = col;
5486
- const td = tr.appendChild(document.createElement('th'));
5487
- if (width) {
5488
- td.setAttribute('width', width);
5282
+ if (node instanceof Element) {
5283
+ const tagName = node.tagName.toLowerCase();
5284
+ if (!node.parentNode) { return () => { }; }
5285
+ if (tagName === 'nl-form-field') {
5286
+ const field = node.getAttribute('name') || '';
5287
+ const mode = node.getAttribute('mode') || '';
5288
+ const fieldStore = field ? store.child(field) : store;
5289
+ const fieldLayout = field ? layout?.fields?.find(createFieldFilter(field)) || null : null;
5290
+ if (!fieldStore) { return () => { }; }
5291
+ switch (mode) {
5292
+ case 'grid': {
5293
+ const [el, destroy] = Form(store, fieldRenderer, fieldLayout, options);
5294
+ node.replaceWith(el);
5295
+ return destroy;
5296
+ }
5297
+ }
5298
+ const component = fieldStore.component;
5299
+ if (component) {
5300
+ const res = fieldRenderer(fieldStore, component, options);
5301
+ if (res) {
5302
+ const [el, destroy] = res;
5303
+ node.replaceWith(el);
5304
+ return destroy;
5489
5305
  }
5490
- td.innerText = label;
5491
- continue;
5492
- }
5493
- const th = tr.appendChild(document.createElement('th'));
5494
- if (!editable) { continue; }
5495
- for (const it of col) {
5496
- switch (it) {
5497
- case 'add':
5498
- const button = th.appendChild(document.createElement('button'));
5499
- button.addEventListener('click', add);
5500
- button.classList.add('NeeloongForm-table-add');
5501
- destroyList.push(watch(() => !addable.get(), disabled => { button.disabled = disabled; }, true));
5502
- continue;
5503
5306
  }
5307
+ const value = node.getAttribute('placeholder') || '';
5308
+ node.replaceWith(document.createTextNode(value));
5309
+ return () => { };
5504
5310
  }
5505
- }
5506
- return () => {
5507
- for (const destroy of destroyList) {
5508
- destroy();
5311
+ if (tagName === 'nl-form-button') {
5312
+ const button = document.createElement('button');
5313
+ button.className = 'NeeloongForm-item-button';
5314
+ const click = node.getAttribute('click') || '';
5315
+ const call = options?.call;
5316
+ if (click && typeof call === 'function') {
5317
+ button.addEventListener('click', e => call(click, e, store, options));
5318
+ }
5319
+ for (const n of [...node.childNodes]) {
5320
+ button.appendChild(n);
5321
+ }
5322
+ node.replaceWith(button);
5323
+ return () => { };
5509
5324
  }
5510
- };
5511
- }
5512
- /**
5513
- *
5514
- * @param {ArrayStore} store
5515
- * @param {StoreLayout.Renderer} fieldRenderer
5516
- * @param {StoreLayout.Field?} layout
5517
- * @param {StoreLayout.Options?} options
5518
- * @returns {[HTMLTableElement, () => void]}
5519
- */
5520
- function Table(store, fieldRenderer, layout, options) {
5521
- const headerColumns = layout?.columns;
5522
- const fieldList = Object.entries(store.type || {})
5523
- .filter(([k, v]) => typeof v?.type !== 'object')
5524
- .map(([field, {width, label}]) => ({field, width, label}));
5525
- /** @type {({ field: string; width: any; label: any; } | StoreLayout.Action[])[]} */
5526
- let columns = [];
5527
- if (Array.isArray(headerColumns)) {
5528
- const map = new Map(fieldList.map(v => [v.field, v]));
5529
- columns = headerColumns.map(v => {
5530
- if (typeof v === 'number') { return [] }
5531
- if (typeof v === 'string') { return map.get(v) || [] }
5532
- if (!Array.isArray(v)) { return []; }
5533
- /** @type {Set<StoreLayout.Action>} */
5534
- const options = new Set(['add', 'move', 'trigger', 'remove', 'serial']);
5535
- return v.filter(v => options.delete(v));
5536
- }).filter(v => !Array.isArray(v) || v.length);
5537
- }
5538
- if (!columns.length) {
5539
- columns = [['add', 'trigger', 'move', 'remove', 'serial']];
5540
- }
5541
- if (!columns.find(v => !Array.isArray(v))) {
5542
- columns.push(...fieldList.slice(0, 3));
5543
- }
5544
-
5545
-
5546
-
5547
- const table = document.createElement('table');
5548
- table.className = 'NeeloongForm-table';
5549
- const thead = table.appendChild(document.createElement('thead'));
5550
-
5551
-
5552
-
5553
- const addable = new exports.Signal.Computed(() => store.addable);
5554
- const deletable = { get: () => Boolean(options?.editable) };
5555
- function add() {
5556
- const data = {};
5557
- store.add(data);
5558
-
5559
- }
5560
- /**
5561
- *
5562
- * @param {Store} child
5563
- */
5564
- function remove(child) {
5565
- store.remove(Number(child.index));
5566
- }
5567
- let dragRow = -1;
5568
- /**
5569
- *
5570
- * @param {Store} [child]
5571
- */
5572
- function dragenter(child) {
5573
- if (dragRow < 0) { return; }
5574
- const index = child ? Number(child.index) : store.children.length;
5575
- if (index < 0 || dragRow < 0 || dragRow === index) { return; }
5576
- if (store.move(dragRow, index)) {
5577
- dragRow = index;
5578
- }
5579
- }
5580
- /**
5581
- *
5582
- * @param {Store} child
5583
- */
5584
- function dragstart(child) {
5585
- dragRow = Number(child.index);
5586
-
5587
- }
5588
- function dragend() {
5589
- dragRow = -1;
5590
-
5591
- }
5592
- /** @type {(() => void)[]} */
5593
- const destroyList = [];
5594
- destroyList.push(renderHead(thead, columns, add, addable, Boolean(options?.editable)));
5595
- switch (layout?.tableFoot) {
5596
- default:
5597
- case 'header': {
5598
- const tfoot = table.appendChild(document.createElement('tfoot'));
5599
- tfoot.addEventListener('dragenter', () => { dragenter(); });
5600
- destroyList.push(renderHead(tfoot, columns, add, addable, Boolean(options?.editable)));
5601
- break;
5602
- }
5603
- case 'add': {
5604
- const tfoot = table.appendChild(document.createElement('tfoot'));
5605
- tfoot.addEventListener('dragenter', () => { dragenter(); });
5606
- const tr = tfoot.appendChild(document.createElement('tr'));
5607
- const th = tr.appendChild(document.createElement('th'));
5608
- th.colSpan = columns.length;
5609
- const button = th.appendChild(document.createElement('button'));
5610
- button.addEventListener('click', add);
5611
- button.classList.add('NeeloongForm-table-foot-add');
5612
- destroyList.push(watch(() => !addable.get(), disabled => { button.disabled = disabled; }, true));
5613
- break;
5614
- }
5615
- case 'none':
5616
- }
5617
- const start = thead;
5618
- /** @type {Map<Store, [HTMLTableSectionElement, () => void]>} */
5619
- let seMap = new Map();
5620
- /** @param {Map<Store, [tbody: HTMLTableSectionElement, destroy: () => void]>} map */
5621
- function destroyMap(map) {
5622
- for (const [el, destroy] of map.values()) {
5623
- destroy();
5624
- el.remove();
5625
- }
5626
-
5627
- }
5628
- const columnNames = columns.map((v) => Array.isArray(v) ? v : v.field);
5629
- const childrenResult = watch(() => store.children, function render(children) {
5630
- let nextNode = thead.nextSibling;
5631
- const oldSeMap = seMap;
5632
- seMap = new Map();
5633
- for (let child of children) {
5634
- const old = oldSeMap.get(child);
5635
- if (!old) {
5636
- const [el, destroy] = Line(child, fieldRenderer, layout, {
5637
- columns: columnNames,
5638
- remove: remove.bind(null, child),
5639
- dragenter: dragenter.bind(null, child),
5640
- dragstart: dragstart.bind(null, child),
5641
- dragend,
5642
- deletable,
5643
- }, options);
5644
- table.insertBefore(el, nextNode);
5645
- seMap.set(child, [el, destroy]);
5646
- continue;
5647
- }
5648
- oldSeMap.delete(child);
5649
- seMap.set(child, old);
5650
- if (nextNode === old[0]) {
5651
- nextNode = nextNode.nextSibling;
5652
- continue;
5653
- }
5654
- table.insertBefore(old[0], nextNode);
5655
- }
5656
- destroyMap(oldSeMap);
5657
- }, true);
5658
-
5659
- return [table, () => {
5660
- start.remove();
5661
- thead.remove();
5662
- destroyMap(seMap);
5663
- childrenResult();
5664
- for (const destroy of destroyList) {
5665
- destroy();
5666
- }
5667
- }];
5668
- }
5669
-
5670
- /** @import { Store } from '../Store/index.mjs' */
5671
- /** @import { StoreLayout } from '../types.mjs' */
5672
-
5673
-
5674
- /**
5675
- *
5676
- * @param {string} field
5677
- */
5678
- function createFieldFilter(field) {
5679
-
5680
- /**
5681
- *
5682
- * @param {StoreLayout.Item} v
5683
- * @returns {v is StoreLayout.Field}
5684
- */
5685
-
5686
- return function (v) {
5687
- if (v.type && v.type !== 'field') { return false; }
5688
- return v.field === field;
5689
-
5690
- };
5691
- }
5692
- /**
5693
- *
5694
- * @param {StoreLayout.Renderer} fieldRenderer
5695
- * @param {Store} store
5696
- * @param {Node} node
5697
- * @param {StoreLayout.Options?} options
5698
- * @param {StoreLayout?} [layout]
5699
- * @param {Node} [anchor]
5700
- * @param {(child?: Store<any, any> | undefined) => void} [dragenter]
5701
- */
5702
- function renderHtml(store, fieldRenderer, node, options, layout, anchor, dragenter) {
5703
- /** @type {(() => void)[]} */
5704
- const destroyList = [];
5705
- if (node instanceof Element) {
5706
- const tagName = node.tagName.toLowerCase();
5707
- if (!node.parentNode) { return () => { }; }
5708
- if (tagName === 'nl-form-field') {
5709
- const field = node.getAttribute('name') || '';
5710
- const mode = node.getAttribute('mode') || '';
5711
- const fieldStore = field ? store.child(field) : store;
5712
- const fieldLayout = field ? layout?.fields?.find(createFieldFilter(field)) || null : null;
5713
- if (!fieldStore) { return () => { }; }
5714
- switch (mode) {
5715
- case 'grid': {
5716
- const [el, destroy] = Form(store, fieldRenderer, fieldLayout, options);
5717
- node.replaceWith(el);
5718
- return destroy;
5719
- }
5720
- }
5721
- const component = fieldStore.component;
5722
- if (component) {
5723
- const res = fieldRenderer(fieldStore, component, options);
5724
- if (res) {
5725
- const [el, destroy] = res;
5726
- node.replaceWith(el);
5727
- return destroy;
5728
- }
5729
- }
5730
- const value = node.getAttribute('placeholder') || '';
5731
- node.replaceWith(document.createTextNode(value));
5732
- return () => { };
5733
- }
5734
- if (tagName === 'nl-form-button') {
5735
- const button = document.createElement('button');
5736
- button.className = 'NeeloongForm-item-button';
5737
- const click = node.getAttribute('click') || '';
5738
- const call = options?.call;
5739
- if (click && typeof call === 'function') {
5740
- button.addEventListener('click', e => call(click, e, store, options));
5741
- }
5742
- for (const n of [...node.childNodes]) {
5743
- button.appendChild(n);
5744
- }
5745
- node.replaceWith(button);
5746
- return () => { };
5747
- }
5748
- const name = node.getAttribute('nl-form-field');
5749
- if (name) {
5750
- const array = name.endsWith('[]');
5751
- const field = array ? name.slice(0, name.length - 2) : name;
5325
+ const name = node.getAttribute('nl-form-field');
5326
+ if (name) {
5327
+ const array = name.endsWith('[]');
5328
+ const field = array ? name.slice(0, name.length - 2) : name;
5752
5329
 
5753
5330
  const fieldStore = field ? store.child(field) : store;
5754
5331
  if (!fieldStore) {
@@ -5884,34 +5461,406 @@
5884
5461
  }
5885
5462
  }
5886
5463
  }
5887
- if (node instanceof Element || node instanceof DocumentFragment) {
5888
- for (const n of [...node.children]) {
5889
- destroyList.push(renderHtml(store, fieldRenderer, n, options, layout, anchor));
5464
+ if (node instanceof Element || node instanceof DocumentFragment) {
5465
+ for (const n of [...node.children]) {
5466
+ destroyList.push(renderHtml(store, fieldRenderer, n, options, layout, anchor));
5467
+ }
5468
+ }
5469
+ return () => {
5470
+ for (const destroy of destroyList) {
5471
+ destroy();
5472
+ }
5473
+ };
5474
+
5475
+ }
5476
+
5477
+ /**
5478
+ *
5479
+ * @param {string | ParentNode | null} [html]
5480
+ * @returns {ParentNode}
5481
+ */
5482
+ function getHtmlContent(html) {
5483
+ if (!html) {
5484
+ return document.createElement('template').content;
5485
+ }
5486
+ if (typeof html !== 'string') {
5487
+ return /** @type {ParentNode} */(html.cloneNode(true));
5488
+ }
5489
+ const template = document.createElement('template');
5490
+ template.innerHTML = html;
5491
+ return template.content;
5492
+ }
5493
+
5494
+ /**
5495
+ *
5496
+ * @param {Store<any, any>} store
5497
+ * @param {StoreLayout.Renderer} fieldRenderer
5498
+ * @param {StoreLayout.Field?} layout
5499
+ * @param {StoreLayout.Options?} options
5500
+ * @returns {[ParentNode, () => void]}
5501
+ */
5502
+ function FormFieldInline(store, fieldRenderer, layout, options) {
5503
+ const { component } = store;
5504
+ return component
5505
+ && fieldRenderer(store, component, options)
5506
+ || [document.createElement('div'), () => { }];
5507
+ }
5508
+
5509
+ /** @import { Store } from '../Store/index.mjs' */
5510
+ /** @import { StoreLayout } from '../types.mjs' */
5511
+
5512
+ /**
5513
+ *
5514
+ * @param {Store<any, any>} store
5515
+ * @param {StoreLayout.Renderer} fieldRenderer
5516
+ * @param {StoreLayout.Field?} layout
5517
+ * @param {object} option
5518
+ * @param {StoreLayout.Column[]} option.columns
5519
+ * @param {() => void} option.remove
5520
+ * @param {() => void} option.dragenter
5521
+ * @param {() => void} option.dragstart
5522
+ * @param {() => void} option.dragend
5523
+ * @param {{get(): boolean}} option.deletable
5524
+ * @param {StoreLayout.Options?} options
5525
+
5526
+ * @returns {[HTMLTableSectionElement, () => void]}
5527
+ */
5528
+ function Line(store, fieldRenderer, layout, {
5529
+ columns,
5530
+ remove, dragenter, dragstart, dragend, deletable
5531
+ }, options) {
5532
+ const root = document.createElement('tbody');
5533
+ root.addEventListener('dragenter', () => {
5534
+ dragenter();
5535
+ });
5536
+ root.addEventListener('dragstart', (event) => {
5537
+ if (event.target !== event.currentTarget) { return; }
5538
+ dragstart();
5539
+ });
5540
+ root.addEventListener('dragend', dragend);
5541
+ const head = root.appendChild(document.createElement('tr'));
5542
+
5543
+ /** @type {(() => void)[]} */
5544
+ const destroyList = [];
5545
+
5546
+
5547
+ let trigger = () => { };
5548
+ /** @type {HTMLButtonElement[]} */
5549
+ const triggerList = [];
5550
+ if (columns.find(v => v.actions?.includes('trigger'))) {
5551
+ const body = root.appendChild(document.createElement('tr'));
5552
+ const main = body.appendChild(document.createElement('td'));
5553
+ main.colSpan = columns.length;
5554
+
5555
+ const [form, destroy] = Form(store, fieldRenderer, layout, options);
5556
+ main.appendChild(form);
5557
+ destroyList.push(destroy);
5558
+ body.hidden = true;
5559
+ trigger = function click() {
5560
+ if (body.hidden) {
5561
+ body.hidden = false;
5562
+ for (const ext of triggerList) {
5563
+ ext.classList.remove('NeeloongForm-table-line-open');
5564
+ ext.classList.add('NeeloongForm-table-line-close');
5565
+ }
5566
+ } else {
5567
+ body.hidden = true;
5568
+ for (const ext of triggerList) {
5569
+ ext.classList.remove('NeeloongForm-table-line-close');
5570
+ ext.classList.add('NeeloongForm-table-line-open');
5571
+ }
5572
+ }
5573
+ };
5574
+
5575
+ }
5576
+
5577
+ /**
5578
+ *
5579
+ * @param {PointerEvent} event
5580
+ */
5581
+ function pointerdown({ pointerId }) {
5582
+ root.draggable = true;
5583
+ /** @param {PointerEvent} event */
5584
+ function pointerup(event) {
5585
+ if (event.pointerId !== pointerId) { return; }
5586
+ if (!root) { return; }
5587
+ root.draggable = false;
5588
+ window.removeEventListener('pointerup', pointerup, { capture: true });
5589
+ window.removeEventListener('pointercancel', pointerup, { capture: true });
5590
+ }
5591
+ window.addEventListener('pointerup', pointerup, { capture: true });
5592
+ window.addEventListener('pointercancel', pointerup, { capture: true });
5593
+
5594
+ }
5595
+
5596
+ for (const name of columns) {
5597
+ const { actions, field, pattern } = name;
5598
+ if (!actions?.length) {
5599
+ const td = head.appendChild(document.createElement('td'));
5600
+ const child = field && store.child(field);
5601
+ if (child) {
5602
+ const [el, destroy] = FormFieldInline(child, fieldRenderer, null, options);
5603
+ destroyList.push(destroy);
5604
+ td.appendChild(el);
5605
+ }
5606
+ continue;
5607
+ }
5608
+ const handle = head.appendChild(document.createElement('th'));
5609
+ handle.classList.add('NeeloongForm-table-line-handle');
5610
+ for (const k of actions) {
5611
+ switch (k) {
5612
+ case 'trigger': {
5613
+ const ext = handle.appendChild(document.createElement('button'));
5614
+ ext.classList.add('NeeloongForm-table-line-open');
5615
+ triggerList.push(ext);
5616
+ ext.addEventListener('click', trigger);
5617
+ continue;
5618
+ }
5619
+ case 'move': {
5620
+ if (!options?.editable) { continue; }
5621
+ const move = handle.appendChild(document.createElement('button'));
5622
+ move.classList.add('NeeloongForm-table-move');
5623
+ move.addEventListener('pointerdown', pointerdown);
5624
+ destroyList.push(watch(() => store.readonly || store.disabled, disabled => {
5625
+ move.disabled = disabled;
5626
+ }, true));
5627
+ continue;
5628
+ }
5629
+ case 'remove': {
5630
+ if (!options?.editable) { continue; }
5631
+ const del = handle.appendChild(document.createElement('button'));
5632
+ del.classList.add('NeeloongForm-table-remove');
5633
+ del.addEventListener('click', remove);
5634
+ destroyList.push(watch(() => !deletable.get() || store.readonly || store.disabled, disabled => {
5635
+ del.disabled = disabled;
5636
+ }, true));
5637
+ continue;
5638
+ }
5639
+ case 'serial': {
5640
+ const serial = handle.appendChild(document.createElement('span'));
5641
+ serial.classList.add('NeeloongForm-table-serial');
5642
+ continue;
5643
+ }
5644
+ }
5645
+ }
5646
+ }
5647
+
5648
+ return [root, () => {
5649
+ for (const destroy of destroyList) {
5650
+ destroy();
5651
+ }
5652
+ }];
5653
+ }
5654
+
5655
+ /** @import { Store, ArrayStore } from '../Store/index.mjs' */
5656
+ /** @import { StoreLayout } from '../types.mjs' */
5657
+
5658
+ /**
5659
+ *
5660
+ * @param {HTMLElement} parent
5661
+ * @param {StoreLayout.Column[]} columns
5662
+ * @param {() => any} add
5663
+ * @param {{get(): boolean}} addable
5664
+ * @param {boolean?} [editable]
5665
+ */
5666
+ function renderHead(parent, columns, add, addable, editable) {
5667
+ const tr = parent.appendChild(document.createElement('tr'));
5668
+ /** @type {(() => void)[]} */
5669
+ const destroyList = [];
5670
+ for (const { action, actions, width, label } of columns) {
5671
+ const th = tr.appendChild(document.createElement('th'));
5672
+ if (width) { th.setAttribute('width', `${width}`); }
5673
+ if (![action, actions].flat().includes('add')) {
5674
+ th.innerText = label || '';
5675
+ continue;
5890
5676
  }
5677
+ if (!editable) { continue; }
5678
+ const button = th.appendChild(document.createElement('button'));
5679
+ button.addEventListener('click', add);
5680
+ button.classList.add('NeeloongForm-table-add');
5681
+ destroyList.push(watch(() => !addable.get(), disabled => { button.disabled = disabled; }, true));
5891
5682
  }
5892
5683
  return () => {
5893
5684
  for (const destroy of destroyList) {
5894
5685
  destroy();
5895
5686
  }
5896
5687
  };
5897
-
5898
5688
  }
5899
-
5900
5689
  /**
5901
- *
5902
- * @param {string | ParentNode | null} [html]
5903
- * @returns {ParentNode}
5690
+ *
5691
+ * @param {ArrayStore} store
5692
+ * @param {StoreLayout.Renderer} fieldRenderer
5693
+ * @param {StoreLayout.Field?} layout
5694
+ * @param {StoreLayout.Options?} options
5695
+ * @returns {[HTMLTableElement, () => void]}
5904
5696
  */
5905
- function getHtmlContent(html) {
5906
- if (!html) {
5907
- return document.createElement('template').content;
5697
+ function Table(store, fieldRenderer, layout, options) {
5698
+ const headerColumns = layout?.columns;
5699
+ const fieldList = Object.entries(store.type || {})
5700
+ .filter(([k, v]) => typeof v?.type !== 'object')
5701
+ .map(([field, { width, label }]) => ({ field, width, label }));
5702
+ /** @type {StoreLayout.Column[]} */
5703
+ let columns = [];
5704
+ if (Array.isArray(headerColumns)) {
5705
+ const map = new Map(fieldList.map(v => [v.field, v]));
5706
+
5707
+ /** @type {(StoreLayout.Column | null)[]} */
5708
+ const allColumns = headerColumns.map(v => {
5709
+ if (!v) { return null; }
5710
+ if (typeof v === 'number') { return { placeholder: v }; }
5711
+ if (typeof v === 'string') { return map.get(v) || null; }
5712
+ if (typeof v !== 'object') { return null; }
5713
+ if (Array.isArray(v)) {
5714
+ /** @type {Set<StoreLayout.Action>} */
5715
+ const options = new Set(['add', 'move', 'trigger', 'remove', 'serial', 'open', 'collapse']);
5716
+ const actions = v.filter(v => options.delete(v));
5717
+ if (!actions) { return null; }
5718
+ return { actions };
5719
+ }
5720
+ const { action, actions, field, placeholder, pattern, width, label } = v;
5721
+ if (field) {
5722
+ const define = map.get(field);
5723
+ if (define) {
5724
+ return { field, placeholder, width, label: label || define.label };
5725
+ }
5726
+ }
5727
+ const options = new Set(['add', 'move', 'trigger', 'remove', 'serial']);
5728
+ const allActions = [action, actions].flat().filter(v => v && options.delete(v));
5729
+ if (allActions.length) {
5730
+ return { actions: /** @type {StoreLayout.Action[]} */(allActions), width, label };
5731
+ }
5732
+ // if (pattern) {
5733
+ // return { pattern, placeholder, width, label };
5734
+ // }
5735
+ return null;
5736
+ });
5737
+ columns = /** @type {StoreLayout.Column[]} */(allColumns.filter(Boolean));
5738
+
5908
5739
  }
5909
- if (typeof html !== 'string') {
5910
- return /** @type {ParentNode} */(html.cloneNode(true));
5740
+ if (!columns.length) {
5741
+ columns = [
5742
+ { actions: ['add', 'trigger', 'move', 'remove', 'serial'] },
5743
+ ...fieldList.slice(0, 3),
5744
+ ];
5911
5745
  }
5912
- const template = document.createElement('template');
5913
- template.innerHTML = html;
5914
- return template.content;
5746
+
5747
+ const table = document.createElement('table');
5748
+ table.className = 'NeeloongForm-table';
5749
+ const thead = table.appendChild(document.createElement('thead'));
5750
+
5751
+ const addable = new exports.Signal.Computed(() => store.addable);
5752
+ const deletable = { get: () => Boolean(options?.editable) };
5753
+ function add() {
5754
+ const data = {};
5755
+ store.add(data);
5756
+ }
5757
+ /**
5758
+ *
5759
+ * @param {Store} child
5760
+ */
5761
+ function remove(child) {
5762
+ store.remove(Number(child.index));
5763
+ }
5764
+ let dragRow = -1;
5765
+ /**
5766
+ *
5767
+ * @param {Store} [child]
5768
+ */
5769
+ function dragenter(child) {
5770
+ if (dragRow < 0) { return; }
5771
+ const index = child ? Number(child.index) : store.children.length;
5772
+ if (index < 0 || dragRow < 0 || dragRow === index) { return; }
5773
+ if (store.move(dragRow, index)) {
5774
+ dragRow = index;
5775
+ }
5776
+ }
5777
+ /**
5778
+ *
5779
+ * @param {Store} child
5780
+ */
5781
+ function dragstart(child) {
5782
+ dragRow = Number(child.index);
5783
+
5784
+ }
5785
+ function dragend() {
5786
+ dragRow = -1;
5787
+
5788
+ }
5789
+ /** @type {(() => void)[]} */
5790
+ const destroyList = [];
5791
+ destroyList.push(renderHead(thead, columns, add, addable, Boolean(options?.editable)));
5792
+ switch (layout?.tableFoot) {
5793
+ default:
5794
+ case 'header': {
5795
+ const tfoot = table.appendChild(document.createElement('tfoot'));
5796
+ tfoot.addEventListener('dragenter', () => { dragenter(); });
5797
+ destroyList.push(renderHead(tfoot, columns, add, addable, Boolean(options?.editable)));
5798
+ break;
5799
+ }
5800
+ case 'add': {
5801
+ const tfoot = table.appendChild(document.createElement('tfoot'));
5802
+ tfoot.addEventListener('dragenter', () => { dragenter(); });
5803
+ const tr = tfoot.appendChild(document.createElement('tr'));
5804
+ const th = tr.appendChild(document.createElement('th'));
5805
+ th.colSpan = columns.length;
5806
+ const button = th.appendChild(document.createElement('button'));
5807
+ button.addEventListener('click', add);
5808
+ button.classList.add('NeeloongForm-table-foot-add');
5809
+ destroyList.push(watch(() => !addable.get(), disabled => { button.disabled = disabled; }, true));
5810
+ break;
5811
+ }
5812
+ case 'none':
5813
+ }
5814
+ const start = thead;
5815
+ /** @type {Map<Store, [HTMLTableSectionElement, () => void]>} */
5816
+ let seMap = new Map();
5817
+ /** @param {Map<Store, [tbody: HTMLTableSectionElement, destroy: () => void]>} map */
5818
+ function destroyMap(map) {
5819
+ for (const [el, destroy] of map.values()) {
5820
+ destroy();
5821
+ el.remove();
5822
+ }
5823
+
5824
+ }
5825
+ const childrenResult = watch(() => store.children, function render(children) {
5826
+ let nextNode = thead.nextSibling;
5827
+ const oldSeMap = seMap;
5828
+ seMap = new Map();
5829
+ for (let child of children) {
5830
+ const old = oldSeMap.get(child);
5831
+ if (!old) {
5832
+ const [el, destroy] = Line(child, fieldRenderer, layout, {
5833
+ columns,
5834
+ remove: remove.bind(null, child),
5835
+ dragenter: dragenter.bind(null, child),
5836
+ dragstart: dragstart.bind(null, child),
5837
+ dragend,
5838
+ deletable,
5839
+ }, options);
5840
+ table.insertBefore(el, nextNode);
5841
+ seMap.set(child, [el, destroy]);
5842
+ continue;
5843
+ }
5844
+ oldSeMap.delete(child);
5845
+ seMap.set(child, old);
5846
+ if (nextNode === old[0]) {
5847
+ nextNode = nextNode.nextSibling;
5848
+ continue;
5849
+ }
5850
+ table.insertBefore(old[0], nextNode);
5851
+ }
5852
+ destroyMap(oldSeMap);
5853
+ }, true);
5854
+
5855
+ return [table, () => {
5856
+ start.remove();
5857
+ thead.remove();
5858
+ destroyMap(seMap);
5859
+ childrenResult();
5860
+ for (const destroy of destroyList) {
5861
+ destroy();
5862
+ }
5863
+ }];
5915
5864
  }
5916
5865
 
5917
5866
  /** @import { StoreLayout } from '../types.mjs' */
@@ -6157,7 +6106,7 @@
6157
6106
  * @param {StoreLayout.Field?} layout
6158
6107
  * @param {State} initState
6159
6108
  * @param {object} option
6160
- * @param {(string | number | StoreLayout.Action[])[]} option.columns
6109
+ * @param {StoreLayout.Column[]} option.columns
6161
6110
  * @param {() => void} option.remove
6162
6111
  * @param {(el: HTMLElement) => () => void} option.dragenter
6163
6112
  * @param {() => void} option.dragstart
@@ -6252,38 +6201,40 @@
6252
6201
  dropChildren.addEventListener('dragover', (e) => e.preventDefault());
6253
6202
  dropFront.addEventListener('drop', () => drop());
6254
6203
  dropChildren.addEventListener('drop', () => drop(true));
6255
- destroyList.push(effect(() => {
6204
+ destroyList.push(effect(() => {
6256
6205
  dropFront.hidden = dropChildren.hidden = !state.get().droppable;
6257
- }));
6206
+ }));
6258
6207
 
6259
- let dragleave = () => {};
6208
+ let dragleave = () => { };
6260
6209
  dropFront.addEventListener('dragenter', () => dragleave = dragenter(dropFront));
6261
6210
  dropChildren.addEventListener('dragenter', () => dragleave = dragenter(dropChildren));
6262
6211
  dropFront.addEventListener('dragleave', () => dragleave());
6263
6212
  dropChildren.addEventListener('dragleave', () => dragleave());
6264
6213
 
6265
- for (const name of columns) {
6266
- if (typeof name === 'number') {
6267
- const td = line.appendChild(document.createElement('div'));
6268
- td.classList.add('NeeloongForm-tree-placeholder');
6269
- td.style.flex = `${name}`;
6270
- if (click) { td.addEventListener('click', click); }
6271
- if (moveStart) { td.addEventListener('pointerdown', moveStart); }
6272
- continue;
6273
- }
6274
- if (!Array.isArray(name)) {
6214
+ for (const { actions, pattern, placeholder, width, field } of columns) {
6215
+ if (!actions?.length) {
6275
6216
  const td = line.appendChild(document.createElement('div'));
6276
6217
  td.classList.add('NeeloongForm-tree-cell');
6277
- const child = store.child(name);
6278
- if (!child) { continue; }
6279
- const [el, destroy] = FormField(child, fieldRenderer, null, { ...options, editable: false }, true);
6280
- destroyList.push(destroy);
6281
- td.appendChild(el);
6282
6218
  if (click) { td.addEventListener('click', click); }
6283
6219
  if (moveStart) { td.addEventListener('pointerdown', moveStart); }
6220
+ if (field) {
6221
+ const child = store.child(field);
6222
+ if (!child) { continue; }
6223
+ const [el, destroy] = FormFieldInline(child, fieldRenderer, null, { ...options, editable: false });
6224
+ destroyList.push(destroy);
6225
+ td.appendChild(el);
6226
+ continue;
6227
+ }
6228
+ if (typeof placeholder === 'number') {
6229
+ td.style.flex = `${placeholder}`;
6230
+ }
6231
+ if (typeof width === 'number') {
6232
+ td.style.width = `${width}px`;
6233
+ }
6284
6234
  continue;
6235
+
6285
6236
  }
6286
- for (const k of name) {
6237
+ for (const k of actions) {
6287
6238
  switch (k) {
6288
6239
  case 'trigger': {
6289
6240
  const btn = line.appendChild(document.createElement('button'));
@@ -6490,28 +6441,54 @@
6490
6441
  const fieldList = Object.entries(store.type || {})
6491
6442
  .filter(([k, v]) => typeof v?.type !== 'object')
6492
6443
  .map(([field, { width, label }]) => ({ field, width, label }));
6493
- /** @type {({ field: string; width: any; label: any; } | number | StoreLayout.Action[])[]} */
6444
+ /** @type {StoreLayout.Column[]} */
6494
6445
  let columns = [];
6495
6446
  if (Array.isArray(headerColumns)) {
6496
6447
  const map = new Map(fieldList.map(v => [v.field, v]));
6497
- columns = headerColumns.map(v => {
6498
- if (typeof v === 'number') { return v; }
6499
- if (typeof v === 'string') { return map.get(v) || []; }
6500
- if (!Array.isArray(v)) { return []; }
6501
- /** @type {Set<StoreLayout.Action>} */
6448
+ /** @type {(StoreLayout.Column | null)[]} */
6449
+ const allColumns = headerColumns.map(v => {
6450
+ if (!v) { return null; }
6451
+ if (typeof v === 'number') { return { placeholder: v }; }
6452
+ if (typeof v === 'string') { return map.get(v) || null; }
6453
+ if (typeof v !== 'object') { return null; }
6454
+ if (Array.isArray(v)) {
6455
+ /** @type {Set<StoreLayout.Action>} */
6456
+ const options = new Set(['add', 'move', 'trigger', 'remove', 'serial', 'open', 'collapse']);
6457
+ const actions = v.filter(v => options.delete(v));
6458
+ if (!actions) { return null; }
6459
+ return { actions };
6460
+ }
6461
+ const { action, actions, field, placeholder, pattern, width, label } = v;
6462
+ if (field) {
6463
+ const define = map.get(field);
6464
+ if (define) {
6465
+ return { field, placeholder, width, label: label || define.label };
6466
+ }
6467
+ }
6502
6468
  const options = new Set(['add', 'move', 'trigger', 'remove', 'serial', 'open', 'collapse']);
6503
- return v.filter(v => options.delete(v));
6504
- }).filter(v => !Array.isArray(v) || v.length);
6469
+ const allActions = [action, actions].flat().filter(v => v && options.delete(v));
6470
+ if (allActions.length) {
6471
+ return { actions: /** @type {StoreLayout.Action[]} */(allActions), width, label };
6472
+ }
6473
+ if (pattern) {
6474
+ return { pattern, placeholder, width, label };
6475
+ }
6476
+ if (placeholder || width) {
6477
+ return { placeholder, width, label };
6478
+ }
6479
+ return null;
6480
+ });
6481
+ columns = /** @type {StoreLayout.Column[]} */(allColumns.filter(Boolean));
6505
6482
  }
6506
6483
  if (!columns.length) {
6507
- columns = [['collapse', 'move'], fieldList[0], ['add', 'remove']];
6508
- }
6509
- if (!columns.find(v => !Array.isArray(v))) {
6510
- columns.push(...fieldList.slice(0, 3));
6484
+ columns = [
6485
+ { actions: ['collapse', 'move'] },
6486
+ fieldList[0],
6487
+ { actions: ['add', 'remove'] },
6488
+ ];
6511
6489
  }
6512
6490
 
6513
6491
 
6514
-
6515
6492
  const root = document.createElement('div');
6516
6493
  root.className = 'NeeloongForm-tree';
6517
6494
  const main = root.appendChild(document.createElement('div'));
@@ -6781,7 +6758,6 @@
6781
6758
  }
6782
6759
  /** @type {State[]} */
6783
6760
  const states = [];
6784
- const columnNames = columns.map((v) => Array.isArray(v) || typeof v === 'number' ? v : v.field);
6785
6761
  const childrenResult = watch(() => store.children, function render(children) {
6786
6762
  let nextNode = start.nextSibling;
6787
6763
  const oldSeMap = seMap;
@@ -6798,7 +6774,7 @@
6798
6774
  const old = oldSeMap.get(child);
6799
6775
  if (!old) {
6800
6776
  const [el, destroy, setState] = TreeLine(child, detailsStore, fieldRenderer, layout, state, {
6801
- columns: columnNames,
6777
+ columns,
6802
6778
  remove: remove.bind(null, child),
6803
6779
  dragenter,
6804
6780
  dragstart: dragstart.bind(null, child),
@@ -6869,22 +6845,10 @@
6869
6845
  * @param {StoreLayout.Renderer} fieldRenderer
6870
6846
  * @param {StoreLayout.Field?} layout
6871
6847
  * @param {StoreLayout.Options?} options
6872
- * @param {boolean} [inline]
6873
6848
  * @returns {[ParentNode, () => void]}
6874
6849
  */
6875
- function FormField(store, fieldRenderer, layout, options, inline = false) {
6850
+ function FormField(store, fieldRenderer, layout, options) {
6876
6851
  const { type, component } = store;
6877
- if (inline) {
6878
- const html = layout?.inlineHtml;
6879
- if (html) {
6880
- const content = getHtmlContent(html);
6881
- const destroy = renderHtml(store, fieldRenderer, content, options, layout);
6882
- return [content, destroy];
6883
- }
6884
- return component
6885
- && fieldRenderer(store, component, options)
6886
- || [document.createElement('div'), () => { }];
6887
- }
6888
6852
  const isObject = type && typeof type === 'object';
6889
6853
  const html = layout?.html;
6890
6854
  /** @type {StoreLayout.Grid['cell']} */